Code

Merge branch 'lt/maint-diff-reduce-lstat' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 26 May 2009 02:04:08 +0000 (19:04 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 26 May 2009 02:04:08 +0000 (19:04 -0700)
* lt/maint-diff-reduce-lstat:
  Teach 'git checkout' to preload the index contents
  Avoid unnecessary 'lstat()' calls in 'get_stat_data()'

588 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.2.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.3.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.4.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.5.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.2.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.3.1.txt [new file with mode: 0644]
Documentation/RelNotes-1.6.3.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/asciidoc.conf
Documentation/blame-options.txt
Documentation/callouts.xsl [deleted file]
Documentation/cat-texi.perl
Documentation/config.txt
Documentation/diff-options.txt
Documentation/docbook-xsl.css
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-annotate.txt
Documentation/git-apply.txt
Documentation/git-archive.txt
Documentation/git-bisect.txt
Documentation/git-blame.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-cat-file.txt
Documentation/git-check-attr.txt
Documentation/git-check-ref-format.txt
Documentation/git-checkout.txt
Documentation/git-cherry.txt
Documentation/git-clean.txt
Documentation/git-clone.txt
Documentation/git-config.txt
Documentation/git-cvsimport.txt
Documentation/git-daemon.txt
Documentation/git-describe.txt
Documentation/git-difftool.txt [new file with mode: 0644]
Documentation/git-filter-branch.txt
Documentation/git-for-each-ref.txt
Documentation/git-format-patch.txt
Documentation/git-gc.txt
Documentation/git-grep.txt
Documentation/git-imap-send.txt
Documentation/git-init.txt
Documentation/git-ls-files.txt
Documentation/git-ls-tree.txt
Documentation/git-merge.txt
Documentation/git-mergetool--lib.txt [new file with mode: 0644]
Documentation/git-mergetool.txt
Documentation/git-pack-refs.txt
Documentation/git-patch-id.txt
Documentation/git-push.txt
Documentation/git-rebase.txt
Documentation/git-remote.txt
Documentation/git-reset.txt
Documentation/git-rev-parse.txt
Documentation/git-send-email.txt
Documentation/git-shell.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-submodule.txt
Documentation/git-svn.txt
Documentation/git-tag.txt
Documentation/git-update-server-info.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitcli.txt
Documentation/gitcore-tutorial.txt
Documentation/gitcvs-migration.txt
Documentation/githooks.txt
Documentation/gitk.txt
Documentation/gittutorial-2.txt
Documentation/gittutorial.txt
Documentation/glossary-content.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/manpage-1.72.xsl
Documentation/manpage-base.xsl [new file with mode: 0644]
Documentation/manpage-bold-literal.xsl [new file with mode: 0644]
Documentation/manpage-normal.xsl [new file with mode: 0644]
Documentation/manpage-suppress-sp.xsl [new file with mode: 0644]
Documentation/merge-config.txt
Documentation/merge-options.txt
Documentation/merge-strategies.txt
Documentation/pretty-formats.txt
Documentation/pretty-options.txt
Documentation/rev-list-options.txt
Documentation/technical/api-parse-options.txt
Documentation/technical/api-run-command.txt
Documentation/technical/api-strbuf.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
INSTALL
Makefile
README
RelNotes
alias.c
alloc.c
archive-tar.c
archive.c
archive.h
attr.c
attr.h
bisect.c [new file with mode: 0644]
bisect.h [new file with mode: 0644]
branch.c
branch.h
builtin-add.c
builtin-apply.c
builtin-archive.c
builtin-bisect--helper.c [new file with mode: 0644]
builtin-blame.c
builtin-branch.c
builtin-cat-file.c
builtin-check-ref-format.c
builtin-checkout-index.c
builtin-checkout.c
builtin-clean.c
builtin-clone.c
builtin-commit.c
builtin-config.c
builtin-count-objects.c
builtin-describe.c
builtin-diff-tree.c
builtin-fast-export.c
builtin-fetch--tool.c
builtin-fetch-pack.c
builtin-fetch.c
builtin-fmt-merge-msg.c
builtin-for-each-ref.c
builtin-fsck.c
builtin-gc.c
builtin-grep.c
builtin-help.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-ls-tree.c
builtin-mailinfo.c
builtin-merge-recursive.c
builtin-merge.c
builtin-pack-objects.c
builtin-prune-packed.c
builtin-prune.c
builtin-push.c
builtin-read-tree.c
builtin-receive-pack.c
builtin-reflog.c
builtin-remote.c
builtin-rerere.c
builtin-reset.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-revert.c
builtin-rm.c
builtin-send-pack.c
builtin-shortlog.c
builtin-show-branch.c
builtin-show-ref.c
builtin-symbolic-ref.c
builtin-tag.c
builtin-tar-tree.c
builtin-update-index.c
builtin-upload-archive.c
builtin-verify-pack.c
builtin-verify-tag.c
builtin.h
bundle.c
cache-tree.c
cache-tree.h
cache.h
color.c
color.h
combine-diff.c
command-list.txt
commit.c
commit.h
compat/cygwin.c
compat/fnmatch/fnmatch.c
compat/memmem.c
compat/mingw.c
compat/mingw.h
compat/regex/regex.c
compat/win32mmap.c [new file with mode: 0644]
compat/winansi.c
config.c
config.mak.in
configure.ac
connect.c
contrib/completion/git-completion.bash
contrib/convert-objects/convert-objects.c
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/fast-import/import-tars.perl
contrib/git-resurrect.sh [new file with mode: 0755]
contrib/hooks/post-receive-email
contrib/thunderbird-patch-inline/README
contrib/vim/README
ctype.c
daemon.c
date.c
decorate.c
diff-lib.c
diff-no-index.c
diff.c
diff.h
diffcore-break.c
diffcore-pickaxe.c
diffcore-rename.c
dir.c
dir.h
entry.c
environment.c
exec_cmd.c
exec_cmd.h
fast-import.c
fsck.c
fsck.h
git-add--interactive.perl
git-am.sh
git-bisect.sh
git-compat-util.h
git-cvsserver.perl
git-difftool--helper.sh [new file with mode: 0755]
git-difftool.perl [new file with mode: 0755]
git-filter-branch.sh
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/branch_delete.tcl
git-gui/lib/checkout_op.tcl
git-gui/lib/choose_repository.tcl
git-gui/lib/commit.tcl
git-gui/lib/diff.tcl
git-gui/lib/mergetool.tcl
git-gui/lib/remote_branch_delete.tcl
git-gui/lib/shortcut.tcl
git-gui/lib/tools.tcl
git-gui/po/de.po
git-gui/po/fr.po
git-gui/po/git-gui.pot
git-gui/po/hu.po
git-gui/po/it.po
git-gui/po/ja.po
git-gui/po/nb.po
git-gui/po/ru.po
git-gui/po/sv.po
git-gui/po/zh_cn.po
git-gui/windows/git-gui.sh
git-instaweb.sh
git-merge-one-file.sh
git-merge-resolve.sh
git-mergetool--lib.sh [new file with mode: 0644]
git-mergetool.sh
git-parse-remote.sh
git-pull.sh
git-quiltimport.sh
git-rebase--interactive.sh
git-rebase.sh
git-send-email.perl
git-sh-setup.sh
git-submodule.sh
git-svn.perl
git-web--browse.sh
git.c
gitk-git/gitk
gitk-git/po/ru.po [new file with mode: 0644]
gitweb/README
gitweb/gitweb.perl
graph.c
grep.c
grep.h
hash-object.c
http-push.c
http-walker.c
http.c
http.h
imap-send.c
index-pack.c
levenshtein.c
list-objects.c
list-objects.h
ll-merge.c
lockfile.c
log-tree.c
log-tree.h
mailmap.c
mailmap.h
merge-index.c
merge-recursive.c
merge-tree.c
mktag.c
mktree.c
object.c
pack-redundant.c
pack-refs.c
pager.c
parse-options.c
parse-options.h
patch-id.c
patch-ids.c
path.c
perl/Git.pm
pretty.c
progress.c
quote.c
quote.h
read-cache.c
reflog-walk.c
reflog-walk.h
refs.c
refs.h
remote.c
remote.h
rerere.c
rerere.h
revision.c
revision.h
run-command.c
run-command.h
send-pack.h
server-info.c
setup.c
sha1-lookup.c
sha1-lookup.h
sha1_file.c
sha1_name.c
shell.c
sideband.c
sideband.h
sigchain.c [new file with mode: 0644]
sigchain.h [new file with mode: 0644]
strbuf.c
strbuf.h
string-list.c
string-list.h
symlinks.c
t/Makefile
t/README
t/annotate-tests.sh
t/lib-git-svn.sh
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/lib-rebase.sh [new file with mode: 0644]
t/t0000-basic.sh
t/t0004-unwritable.sh
t/t0005-signals.sh [new file with mode: 0755]
t/t0020-crlf.sh
t/t0024-crlf-archive.sh
t/t0050-filesystem.sh
t/t0055-beyond-symlinks.sh
t/t0060-path-utils.sh
t/t0070-fundamental.sh [new file with mode: 0755]
t/t0100-previous.sh [new file with mode: 0755]
t/t1004-read-tree-m-u-wf.sh
t/t1008-read-tree-overlay.sh [new file with mode: 0755]
t/t1020-subdirectory.sh
t/t1100-commit-tree-options.sh
t/t1300-repo-config.sh
t/t1301-shared-repo.sh
t/t1400-update-ref.sh
t/t1401-symbolic-ref.sh [new file with mode: 0755]
t/t1410-reflog.sh
t/t1411-reflog-show.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/t2001-checkout-cache-clash.sh
t/t2003-checkout-cache-mkdir.sh
t/t2004-checkout-cache-temp.sh
t/t2007-checkout-symlink.sh
t/t2012-checkout-last.sh [new file with mode: 0755]
t/t2013-checkout-submodule.sh [new file with mode: 0755]
t/t2014-switch.sh [new file with mode: 0755]
t/t2100-update-cache-badpath.sh
t/t2200-add-update.sh
t/t2201-add-update-typechange.sh
t/t2300-cd-to-toplevel.sh
t/t3000-ls-files-others.sh
t/t3010-ls-files-killed-modified.sh
t/t3031-merge-criscross.sh [new file with mode: 0755]
t/t3100-ls-tree-restrict.sh
t/t3200-branch.sh
t/t3203-branch-output.sh [new file with mode: 0755]
t/t3400-rebase.sh
t/t3404-rebase-interactive.sh
t/t3406-rebase-message.sh
t/t3409-rebase-hook.sh [deleted file]
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/t3413-rebase-hook.sh [new file with mode: 0755]
t/t3600-rm.sh
t/t3700-add.sh
t/t3701-add-interactive.sh
t/t4002-diff-basic.sh
t/t4004-diff-rename-symlink.sh
t/t4006-diff-mode.sh
t/t4008-diff-break-rewrite.sh
t/t4011-diff-symlink.sh
t/t4012-diff-binary.sh
t/t4013-diff-various.sh
t/t4013/diff.diff_--dirstat_master~1_master~2 [new file with mode: 0644]
t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side [new file with mode: 0644]
t/t4013/diff.format-patch_--attach_--stdout_initial..master
t/t4013/diff.format-patch_--attach_--stdout_initial..master^
t/t4013/diff.format-patch_--attach_--stdout_initial..side
t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master [new file with mode: 0644]
t/t4013/diff.format-patch_--inline_--stdout_--subject-prefix=TESTCASE_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master
t/t4013/diff.format-patch_--inline_--stdout_initial..master^
t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
t/t4013/diff.format-patch_--inline_--stdout_initial..side
t/t4013/diff.log_--decorate_--all [new file with mode: 0644]
t/t4013/diff.log_--patch-with-stat_--summary_master_--_dir_
t/t4013/diff.log_--patch-with-stat_master
t/t4013/diff.log_--patch-with-stat_master_--_dir_
t/t4013/diff.log_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_--patch-with-stat_master
t/t4013/diff.log_--root_-c_--patch-with-stat_--summary_master
t/t4013/diff.log_--root_-p_master
t/t4013/diff.log_--root_master
t/t4013/diff.log_-p_master
t/t4013/diff.log_master
t/t4013/diff.rev-list_--children_HEAD [new file with mode: 0644]
t/t4013/diff.rev-list_--parents_HEAD [new file with mode: 0644]
t/t4013/diff.show_master
t/t4013/diff.whatchanged_--root_--cc_--patch-with-stat_--summary_master
t/t4013/diff.whatchanged_--root_-c_--patch-with-stat_--summary_master
t/t4014-format-patch.sh
t/t4017-quiet.sh [deleted file]
t/t4018-diff-funcname.sh
t/t4020-diff-external.sh
t/t4021-format-patch-numbered.sh
t/t4021-format-patch-signer-mime.sh [deleted file]
t/t4023-diff-rename-typechange.sh
t/t4029-diff-trailing-space.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/t4035-diff-quiet.sh [new file with mode: 0755]
t/t4036-format-patch-signer-mime.sh [new file with mode: 0755]
t/t4102-apply-rename.sh
t/t4106-apply-stdin.sh [new file with mode: 0755]
t/t4114-apply-typechange.sh
t/t4115-apply-symlink.sh
t/t4118-apply-empty-context.sh
t/t4122-apply-symlink-inside.sh
t/t4129-apply-samemode.sh
t/t4130-apply-criss-cross-rename.sh [new file with mode: 0755]
t/t4150-am.sh
t/t4200-rerere.sh
t/t4202-log.sh
t/t4203-mailmap.sh [new file with mode: 0755]
t/t4204-patch-id.sh [new file with mode: 0755]
t/t4252-am-options.sh
t/t4252/am-test-5-1 [new file with mode: 0644]
t/t4252/am-test-5-2 [new file with mode: 0644]
t/t4252/am-test-6-1 [new file with mode: 0644]
t/t5000-tar-tree.sh
t/t5001-archive-attr.sh [new file with mode: 0755]
t/t5100/info0001
t/t5100/rfc2047-info-0004
t/t5100/sample.mbox
t/t5300-pack-object.sh
t/t5302-pack-index.sh
t/t5303-pack-corruption-resilience.sh
t/t5304-prune.sh
t/t5400-send-pack.sh
t/t5403-post-checkout-hook.sh
t/t5503-tagfollow.sh
t/t5505-remote.sh
t/t5506-remote-groups.sh [new file with mode: 0755]
t/t5510-fetch.sh
t/t5511-refspec.sh
t/t5515-fetch-merge-logic.sh
t/t5516-fetch-push.sh
t/t5521-pull-symlink.sh [deleted file]
t/t5522-pull-symlink.sh [new file with mode: 0755]
t/t5540-http-push.sh
t/t5550-http-fetch.sh [new file with mode: 0755]
t/t5601-clone.sh
t/t5602-clone-remote-exec.sh
t/t5701-clone-local.sh
t/t5704-bundle.sh [new file with mode: 0755]
t/t5705-clone-2gb.sh [new file with mode: 0755]
t/t6006-rev-list-format.sh
t/t6023-merge-rename-nocruft.sh [deleted file]
t/t6030-bisect-porcelain.sh
t/t6031-merge-recursive.sh
t/t6034-merge-rename-nocruft.sh [new file with mode: 0755]
t/t6040-tracking-info.sh
t/t6200-fmt-merge-msg.sh
t/t6300-for-each-ref.sh
t/t7001-mv.sh
t/t7003-filter-branch.sh
t/t7004-tag.sh
t/t7005-editor.sh
t/t7201-co.sh
t/t7300-clean.sh
t/t7400-submodule-basic.sh
t/t7405-submodule-merge.sh [new file with mode: 0755]
t/t7500-commit.sh
t/t7501-commit.sh
t/t7502-commit.sh
t/t7502-status.sh [deleted file]
t/t7503-pre-commit-hook.sh
t/t7504-commit-msg-hook.sh
t/t7508-status.sh [new file with mode: 0755]
t/t7610-mergetool.sh
t/t7700-repack.sh
t/t7701-repack-unpack-unreachable.sh
t/t7800-difftool.sh [new file with mode: 0755]
t/t8005-blame-i18n.sh
t/t9001-send-email.sh
t/t9100-git-svn-basic.sh
t/t9106-git-svn-dcommit-clobber-series.sh [deleted file]
t/t9108-git-svn-multi-glob.sh [deleted file]
t/t9109-git-svn-multi-glob.sh [new file with mode: 0755]
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/t9137-git-svn-dcommit-clobber-series.sh [new file with mode: 0755]
t/t9200-git-cvsexportcommit.sh
t/t9301-fast-export.sh
t/t9400-git-cvsserver-server.sh
t/t9401-git-cvsserver-crlf.sh
t/t9500-gitweb-standalone-no-errors.sh
t/t9600-cvsimport.sh
t/t9700-perl-git.sh
t/test-lib.sh
t/valgrind/.gitignore [new file with mode: 0644]
t/valgrind/analyze.sh [new file with mode: 0755]
t/valgrind/default.supp [new file with mode: 0644]
t/valgrind/valgrind.sh [new file with mode: 0755]
templates/hooks--pre-commit.sample
templates/hooks--update.sample
templates/this--description
test-ctype.c [new file with mode: 0644]
test-path-utils.c
test-sigchain.c [new file with mode: 0644]
trace.c
transport.c
transport.h
tree-diff.c
tree.c
unimplemented.sh [new file with mode: 0644]
unpack-file.c
unpack-trees.c
update-server-info.c
upload-pack.c
usage.c
userdiff.c
userdiff.h
var.c
walker.c
wrapper.c
wt-status.c
xdiff-interface.c
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xemit.c
xdiff/xpatience.c [new file with mode: 0644]
xdiff/xprepare.c

index d9adce585af99e617a2906a89029a92045a79538..41c0b20a76ce0fd47c7cafc51777336ce99ce1b0 100644 (file)
@@ -11,6 +11,7 @@ git-apply
 git-archimport
 git-archive
 git-bisect
+git-bisect--helper
 git-blame
 git-branch
 git-bundle
@@ -35,6 +36,8 @@ git-diff
 git-diff-files
 git-diff-index
 git-diff-tree
+git-difftool
+git-difftool--helper
 git-describe
 git-fast-export
 git-fast-import
@@ -78,6 +81,7 @@ git-merge-recursive
 git-merge-resolve
 git-merge-subtree
 git-mergetool
+git-mergetool--lib
 git-mktag
 git-mktree
 git-name-rev
@@ -144,6 +148,7 @@ git-core-*/?*
 gitk-wish
 gitweb/gitweb.cgi
 test-chmtime
+test-ctype
 test-date
 test-delta
 test-dump-cache-tree
@@ -152,6 +157,7 @@ test-match-trees
 test-parse-options
 test-path-utils
 test-sha1
+test-sigchain
 common-cmds.h
 *.tar.gz
 *.dsc
index f628c1f3b7b706f9d585b96041e5a4b12bc0f62c..b8bf618a30fd32a014e41e1ba9914f5e652bdefd 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):
 
@@ -124,3 +129,6 @@ For C programs:
    used in the git core command set (unless your command is clearly
    separate from it, such as an importer to convert random-scm-X
    repositories to git).
+
+ - When we pass <string, length> pair to functions, we should try to
+   pass them in that order.
index c34c1cae2072fa27de32f553866426bbc01436d5..7a8037f586773c00004e34079b48bb514db2515f 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
@@ -40,7 +41,8 @@ man7dir=$(mandir)/man7
 
 ASCIIDOC=asciidoc
 ASCIIDOC_EXTRA =
-MANPAGE_XSL = callouts.xsl
+MANPAGE_XSL = manpage-normal.xsl
+XMLTO_EXTRA =
 INSTALL?=install
 RM ?= rm -f
 DOC_REF = origin/man
@@ -50,6 +52,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
@@ -57,14 +60,53 @@ endif
 -include ../config.mak.autogen
 -include ../config.mak
 
+#
+# For asciidoc ...
+#      -7.1.2, no extra settings are needed.
+#      8.0-,   set ASCIIDOC8.
+#
+
+#
+# For docbook-xsl ...
+#      -1.68.1,        set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0)
+#      1.69.0,         no extra settings are needed?
+#      1.69.1-1.71.0,  set DOCBOOK_SUPPRESS_SP?
+#      1.71.1,         no extra settings are needed?
+#      1.72.0,         set DOCBOOK_XSL_172.
+#      1.73.0-,        set ASCIIDOC_NO_ROFF
+#
+
+#
+# If you had been using DOCBOOK_XSL_172 in an attempt to get rid
+# of 'the ".ft C" problem' in your generated manpages, and you
+# instead ended up with weird characters around callouts, try
+# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8).
+#
+
 ifdef ASCIIDOC8
 ASCIIDOC_EXTRA += -a asciidoc7compatible
 endif
 ifdef DOCBOOK_XSL_172
-ASCIIDOC_EXTRA += -a docbook-xsl-172
+ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
 MANPAGE_XSL = manpage-1.72.xsl
+else
+       ifdef ASCIIDOC_NO_ROFF
+       # docbook-xsl after 1.72 needs the regular XSL, but will not
+       # pass-thru raw roff codes from asciidoc.conf, so turn them off.
+       ASCIIDOC_EXTRA += -a git-asciidoc-no-roff
+       endif
+endif
+ifdef MAN_BOLD_LITERAL
+XMLTO_EXTRA += -m manpage-bold-literal.xsl
+endif
+ifdef DOCBOOK_SUPPRESS_SP
+XMLTO_EXTRA += -m manpage-suppress-sp.xsl
 endif
 
+SHELL_PATH ?= $(SHELL)
+# Shell quote;
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
 #
 # Please note that there is a minor bug in asciidoc.
 # The version after 6.0.3 _will_ include the patch found here:
@@ -74,6 +116,32 @@ endif
 # yourself - yes, all 6 characters of it!
 #
 
+QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1  =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifndef V
+       QUIET_ASCIIDOC  = @echo '   ' ASCIIDOC $@;
+       QUIET_XMLTO     = @echo '   ' XMLTO $@;
+       QUIET_DB2TEXI   = @echo '   ' DB2TEXI $@;
+       QUIET_MAKEINFO  = @echo '   ' MAKEINFO $@;
+       QUIET_DBLATEX   = @echo '   ' DBLATEX $@;
+       QUIET_XSLTPROC  = @echo '   ' XSLTPROC $@;
+       QUIET_GEN       = @echo '   ' GEN $@;
+       QUIET_STDERR    = 2> /dev/null
+       QUIET_SUBDIR0   = +@subdir=
+       QUIET_SUBDIR1   = ;$(NO_SUBDIR) echo '   ' SUBDIR $$subdir; \
+                         $(MAKE) $(PRINT_DIR) -C $$subdir
+       export V
+endif
+endif
+
 all: html man
 
 html: $(DOC_HTML)
@@ -87,6 +155,8 @@ man7: $(DOC_MAN7)
 
 info: git.info gitman.info
 
+pdf: user-manual.pdf
+
 install: install-man
 
 install-man: man
@@ -107,11 +177,15 @@ 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)
+       '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir)
 
 ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
-       $(MAKE) -C ../ GIT-VERSION-FILE
+       $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
 
 -include ../GIT-VERSION-FILE
 
@@ -119,8 +193,8 @@ install-html: html
 # Determine "include::" file references in asciidoc files.
 #
 doc.dep : $(wildcard *.txt) build-docdep.perl
-       $(RM) $@+ $@
-       $(PERL_PATH) ./build-docdep.perl >$@+
+       $(QUIET_GEN)$(RM) $@+ $@ && \
+       $(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
        mv $@+ $@
 
 -include doc.dep
@@ -138,96 +212,105 @@ cmds_txt = cmds-ancillaryinterrogators.txt \
 $(cmds_txt): cmd-list.made
 
 cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
-       $(RM) $@
-       $(PERL_PATH) ./cmd-list.perl ../command-list.txt
+       $(QUIET_GEN)$(RM) $@ && \
+       $(PERL_PATH) ./cmd-list.perl ../command-list.txt $(QUIET_STDERR) && \
        date >$@
 
 clean:
        $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7
-       $(RM) *.texi *.texi+ git.info gitman.info
+       $(RM) *.texi *.texi+ *.texi++ git.info gitman.info
        $(RM) howto-index.txt howto/*.html doc.dep
        $(RM) technical/api-*.html technical/api-index.txt
        $(RM) $(cmds_txt) *.made
 
 $(MAN_HTML): %.html : %.txt
-       $(RM) $@+ $@
+       $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
        $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
-               $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
+               $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \
        mv $@+ $@
 
 %.1 %.5 %.7 : %.xml
-       $(RM) $@
-       xmlto -m $(MANPAGE_XSL) man $<
+       $(QUIET_XMLTO)$(RM) $@ && \
+       xmlto -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
 
 %.xml : %.txt
-       $(RM) $@+ $@
+       $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
        $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \
-               $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
+               $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $< && \
        mv $@+ $@
 
 user-manual.xml: user-manual.txt user-manual.conf
-       $(ASCIIDOC) -b docbook -d book $<
+       $(QUIET_ASCIIDOC)$(ASCIIDOC) $(ASCIIDOC_EXTRA) -b docbook -d book $<
 
 technical/api-index.txt: technical/api-index-skel.txt \
        technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
-       cd technical && sh ./api-index.sh
+       $(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh
 
 $(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt
-       $(ASCIIDOC) -b xhtml11 -f asciidoc.conf \
+       $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \
                $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) $*.txt
 
 XSLT = docbook.xsl
 XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
 
 user-manual.html: user-manual.xml
-       xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
+       $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
 
 git.info: user-manual.texi
-       $(MAKEINFO) --no-split -o $@ user-manual.texi
+       $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi
 
 user-manual.texi: user-manual.xml
-       $(RM) $@+ $@
-       $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout | $(PERL_PATH) fix-texi.perl >$@+
+       $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+       $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \
+       $(PERL_PATH) fix-texi.perl <$@++ >$@+ && \
+       rm $@++ && \
+       mv $@+ $@
+
+user-manual.pdf: user-manual.xml
+       $(QUIET_DBLATEX)$(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 $@ >$@+
+       $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+       ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
+               --to-stdout $(xml) &&) true) > $@++ && \
+       $(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \
+       rm $@++ && \
        mv $@+ $@
 
 gitman.info: gitman.texi
-       $(MAKEINFO) --no-split $*.texi
+       $(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi
 
 $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
-       $(RM) $@+ $@
-       $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+
+       $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+       $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
        mv $@+ $@
 
 howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
-       $(RM) $@+ $@
-       sh ./howto-index.sh $(wildcard howto/*.txt) >$@+
+       $(QUIET_GEN)$(RM) $@+ $@ && \
+       '$(SHELL_PATH_SQ)' ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \
        mv $@+ $@
 
 $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
-       $(ASCIIDOC) -b xhtml11 $*.txt
+       $(QUIET_ASCIIDOC)$(ASCIIDOC) $(ASCIIDOC_EXTRA) -b xhtml11 $*.txt
 
 WEBDOC_DEST = /pub/software/scm/git/docs
 
 $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
-       $(RM) $@+ $@
-       sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+
+       $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+       sed -e '1,/^$$/d' $< | $(ASCIIDOC) $(ASCIIDOC_EXTRA) -b xhtml11 - >$@+ && \
        mv $@+ $@
 
 install-webdoc : html
-       sh ./install-webdoc.sh $(WEBDOC_DEST)
+       '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
 
 quick-install: quick-install-man
 
 quick-install-man:
-       sh ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
 
 quick-install-html:
-       sh ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
+       '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
 
 .PHONY: .FORCE-GIT-VERSION-FILE
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..dfa3641
--- /dev/null
@@ -0,0 +1,19 @@
+GIT v1.6.2.1 Release Notes
+==========================
+
+Fixes since v1.6.2
+------------------
+
+* .gitignore learned to handle backslash as a quoting mechanism for
+  comment introduction character "#".
+
+* timestamp output in --date=relative mode used to display timestamps that
+  are long time ago in the default mode; it now uses "N years M months
+  ago", and "N years ago".
+
+* git-add -i/-p now works with non-ASCII pathnames.
+
+* "git hash-object -w" did not read from the configuration file from the
+  correct .git directory.
+
+* git-send-email learned to correctly handle multiple Cc: addresses.
diff --git a/Documentation/RelNotes-1.6.2.2.txt b/Documentation/RelNotes-1.6.2.2.txt
new file mode 100644 (file)
index 0000000..fafa998
--- /dev/null
@@ -0,0 +1,45 @@
+GIT v1.6.2.2 Release Notes
+==========================
+
+Fixes since v1.6.2.1
+--------------------
+
+* A longstanding confusing description of what --pickaxe option of
+  git-diff does has been clarified in the documentation.
+
+* "git-blame -S" did not quite work near the commits that were given
+  on the command line correctly.
+
+* "git diff --pickaxe-regexp" did not count overlapping matches
+  correctly.
+
+* "git diff" did not feed files in work-tree representation to external
+  diff and textconv.
+
+* "git-fetch" in a repository that was not cloned from anywhere said
+  it cannot find 'origin', which was hard to understand for new people.
+
+* "git-format-patch --numbered-files --stdout" did not have to die of
+  incompatible options; it now simply ignores --numbered-files as no files
+  are produced anyway.
+
+* "git-ls-files --deleted" did not work well with GIT_DIR&GIT_WORK_TREE.
+
+* "git-read-tree A B C..." without -m option has been broken for a long
+  time.
+
+* git-send-email ignored --in-reply-to when --no-thread was given.
+
+* 'git-submodule add' did not tolerate extra slashes and ./ in the path it
+  accepted from the command line; it now is more lenient.
+
+* git-svn misbehaved when the project contained a path that began with
+  two dashes.
+
+* import-zips script (in contrib) did not compute the common directory
+  prefix correctly.
+
+* miscompilation of negated enum constants by old gcc (2.9) affected the
+  codepaths to spawn subprocesses.
+
+Many small documentation updates are included as well.
diff --git a/Documentation/RelNotes-1.6.2.3.txt b/Documentation/RelNotes-1.6.2.3.txt
new file mode 100644 (file)
index 0000000..4d3c1ac
--- /dev/null
@@ -0,0 +1,22 @@
+GIT v1.6.2.3 Release Notes
+==========================
+
+Fixes since v1.6.2.2
+--------------------
+
+* Setting an octal mode value to core.sharedrepository configuration to
+  restrict access to the repository to group members did not work as
+  advertised.
+
+* A fairly large and trivial memory leak while rev-list shows list of
+  reachable objects has been identified and plugged.
+
+* "git-commit --interactive" did not abort when underlying "git-add -i"
+  signaled a failure.
+
+* git-repack (invoked from git-gc) did not work as nicely as it should in
+  a repository that borrows objects from neighbours via alternates
+  mechanism especially when some packs are marked with the ".keep" flag
+  to prevent them from being repacked.
+
+Many small documentation updates are included as well.
diff --git a/Documentation/RelNotes-1.6.2.4.txt b/Documentation/RelNotes-1.6.2.4.txt
new file mode 100644 (file)
index 0000000..f4bf1d0
--- /dev/null
@@ -0,0 +1,39 @@
+GIT v1.6.2.4 Release Notes
+==========================
+
+Fixes since v1.6.2.3
+--------------------
+
+* The configuration parser had a buffer overflow while parsing an overlong
+  value.
+
+* pruning reflog entries that are unreachable from the tip of the ref
+  during "git reflog prune" (hence "git gc") was very inefficient.
+
+* "git-add -p" lacked a way to say "q"uit to refuse staging any hunks for
+  the remaining paths.  You had to say "d" and then ^C.
+
+* "git-checkout <tree-ish> <submodule>" did not update the index entry at
+  the named path; it now does.
+
+* "git-fast-export" choked when seeing a tag that does not point at commit.
+
+* "git init" segfaulted when given an overlong template location via
+  the --template= option.
+
+* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
+  deciding to descend into a subdirectory but they did not match the
+  individual paths correctly.  This caused pathspecs "abc/d ab" to match
+  "abc/0" ("abc/d" made them decide to descend into the directory "abc/",
+  and then "ab" incorrectly matched "abc/0" when it shouldn't).
+
+* "git-merge-recursive" was broken when a submodule entry was involved in
+  a criss-cross merge situation.
+
+Many small documentation updates are included as well.
+
+---
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.6.2.3-38-g318b847
+git shortlog --no-merges $O..maint
diff --git a/Documentation/RelNotes-1.6.2.5.txt b/Documentation/RelNotes-1.6.2.5.txt
new file mode 100644 (file)
index 0000000..b23f9e9
--- /dev/null
@@ -0,0 +1,21 @@
+GIT v1.6.2.5 Release Notes
+==========================
+
+Fixes since v1.6.2.4
+--------------------
+
+* "git apply" mishandled if you fed a git generated patch that renames
+  file A to B and file B to A at the same time.
+
+* "git diff -c -p" (and "diff --cc") did not expect to see submodule
+  differences and instead refused to work.
+
+* "git grep -e '('" segfaulted, instead of diagnosing a mismatched
+  parentheses error.
+
+* "git fetch" generated packs with offset-delta encoding when both ends of
+  the connection are capable of producing one; this cannot be read by
+  ancient git and the user should be able to disable this by setting
+  repack.usedeltabaseoffset configuration to false.
+
+
diff --git a/Documentation/RelNotes-1.6.2.txt b/Documentation/RelNotes-1.6.2.txt
new file mode 100644 (file)
index 0000000..ad060f4
--- /dev/null
@@ -0,0 +1,164 @@
+GIT v1.6.2 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+
+Updates since v1.6.1
+--------------------
+
+(subsystems)
+
+* git-svn updates.
+
+* gitweb updates, including a new patch view and RSS/Atom feed
+  improvements.
+
+* (contrib/emacs) git.el now has commands for checking out a branch,
+  creating a branch, cherry-picking and reverting commits; vc-git.el
+  is not shipped with git anymore (it is part of official Emacs).
+
+(performance)
+
+* pack-objects autodetects the number of CPUs available and uses threaded
+  version.
+
+(usability, bells and whistles)
+
+* automatic typo correction works on aliases as well
+
+* @{-1} is a way to refer to the last branch you were on.  This is
+  accepted not only where an object name is expected, but anywhere
+  a branch name is expected and acts as if you typed the branch name.
+  E.g. "git branch --track mybranch @{-1}", "git merge @{-1}", and
+  "git rev-parse --symbolic-full-name @{-1}" would work as expected.
+
+* When refs/remotes/origin/HEAD points at a remote tracking branch that
+  has been pruned away, many git operations issued warning when they
+  internally enumerated the refs.  We now warn only when you say "origin"
+  to refer to that pruned branch.
+
+* The location of .mailmap file can be configured, and its file format was
+  enhanced to allow mapping an incorrect e-mail field as well.
+
+* "git add -p" learned 'g'oto action to jump directly to a hunk.
+
+* "git add -p" learned to find a hunk with given text with '/'.
+
+* "git add -p" optionally can be told to work with just the command letter
+  without Enter.
+
+* when "git am" stops upon a patch that does not apply, it shows the
+  title of the offending patch.
+
+* "git am --directory=<dir>" and "git am --reject" passes these options
+  to underlying "git apply".
+
+* "git am" learned --ignore-date option.
+
+* "git blame" aligns author names better when they are spelled in
+  non US-ASCII encoding.
+
+* "git clone" now makes its best effort when cloning from an empty
+  repository to set up configuration variables to refer to the remote
+  repository.
+
+* "git checkout -" is a shorthand for "git checkout @{-1}".
+
+* "git cherry" defaults to whatever the current branch is tracking (if
+  exists) when the <upstream> argument is not given.
+
+* "git cvsserver" can be told not to add extra "via git-CVS emulator" to
+  the commit log message it serves via gitcvs.commitmsgannotation
+  configuration.
+
+* "git cvsserver" learned to handle 'noop' command some CVS clients seem
+  to expect to work.
+
+* "git diff" learned a new option --inter-hunk-context to coalesce close
+  hunks together and show context between them.
+
+* The definition of what constitutes a word for "git diff --color-words"
+  can be customized via gitattributes, command line or a configuration.
+
+* "git diff" learned --patience to run "patience diff" algorithm.
+
+* "git filter-branch" learned --prune-empty option that discards commits
+  that do not change the contents.
+
+* "git fsck" now checks loose objects in alternate object stores, instead
+  of misreporting them as missing.
+
+* "git gc --prune" was resurrected to allow "git gc --no-prune" and
+  giving non-default expiration period e.g. "git gc --prune=now".
+
+* "git grep -w" and "git grep" for fixed strings have been optimized.
+
+* "git mergetool" learned -y(--no-prompt) option to disable prompting.
+
+* "git rebase -i" can transplant a history down to root to elsewhere
+  with --root option.
+
+* "git reset --merge" is a new mode that works similar to the way
+  "git checkout" switches branches, taking the local changes while
+  switching to another commit.
+
+* "git submodule update" learned --no-fetch option.
+
+* "git tag" learned --contains that works the same way as the same option
+  from "git branch".
+
+
+Fixes since v1.6.1
+------------------
+
+All of the fixes in v1.6.1.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.1.X series.
+
+* "git-add sub/file" when sub is a submodule incorrectly added the path to
+  the superproject.
+
+* "git bundle" did not exclude annotated tags even when a range given
+  from the command line wanted to.
+
+* "git filter-branch" unnecessarily refused to work when you had
+  checked out a different commit from what is recorded in the superproject
+  index in a submodule.
+
+* "git filter-branch" incorrectly tried to update a nonexistent work tree
+  at the end when it is run in a bare repository.
+
+* "git gc" did not work if your repository was created with an ancient git
+  and never had any pack files in it before.
+
+* "git mergetool" used to ignore autocrlf and other attributes
+  based content rewriting.
+
+* branch switching and merges had a silly bug that did not validate
+  the correct directory when making sure an existing subdirectory is
+  clean.
+
+* "git -p cmd" when cmd is not a built-in one left the display in funny state
+  when killed in the middle.
diff --git a/Documentation/RelNotes-1.6.3.1.txt b/Documentation/RelNotes-1.6.3.1.txt
new file mode 100644 (file)
index 0000000..2400b72
--- /dev/null
@@ -0,0 +1,10 @@
+GIT v1.6.3.1 Release Notes
+==========================
+
+Fixes since v1.6.3
+------------------
+
+* "git checkout -b new-branch" with a staged change in the index
+  incorrectly primed the in-index cache-tree, resulting a wrong tree
+  object to be written out of the index.  This is a grave regression
+  since the last 1.6.2.X maintenance release.
diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes-1.6.3.txt
new file mode 100644 (file)
index 0000000..418c685
--- /dev/null
@@ -0,0 +1,182 @@
+GIT v1.6.3 Release Notes
+========================
+
+With the next major release, "git push" into a branch that is
+currently checked out will be refused by default.  You can choose
+what should happen upon such a push by setting the configuration
+variable receive.denyCurrentBranch in the receiving repository.
+
+To ease the transition plan, the receiving repository of such a
+push running this release will issue a big warning when the
+configuration variable is missing.  Please refer to:
+
+  http://git.or.cz/gitwiki/GitFaq#non-bare
+  http://thread.gmane.org/gmane.comp.version-control.git/107758/focus=108007
+
+for more details on the reason why this change is needed and the
+transition plan.
+
+For a similar reason, "git push $there :$killed" to delete the branch
+$killed in a remote repository $there, if $killed branch is the current
+branch pointed at by its HEAD, gets a large warning.  You can choose what
+should happen upon such a push by setting the configuration variable
+receive.denyDeleteCurrent in the receiving repository.
+
+When the user does not tell "git push" what to push, it has always
+pushed matching refs.  For some people it is unexpected, and a new
+configuration variable push.default has been introduced to allow
+changing a different default behaviour.  To advertise the new feature,
+a big warning is issued if this is not configured and a git push without
+arguments is attempted.
+
+
+Updates since v1.6.2
+--------------------
+
+(subsystems)
+
+* various git-svn updates.
+
+* git-gui updates, including an update to Russian translation, and a
+  fix to an infinite loop when showing an empty diff.
+
+* gitk updates, including an update to Russian translation and improved Windows
+  support.
+
+(performance)
+
+* many uses of lstat(2) in the codepath for "git checkout" have been
+  optimized out.
+
+(usability, bells and whistles)
+
+* Boolean configuration variable yes/no can be written as on/off.
+
+* rsync:/path/to/repo can be used to run git over rsync for local
+  repositories.  It may not be useful in practice; meant primarily for
+  testing.
+
+* http transport learned to prompt and use password when fetching from or
+  pushing to http://user@host.xz/ URL.
+
+* (msysgit) progress output that is sent over the sideband protocol can
+  be handled appropriately in Windows console.
+
+* "--pretty=<style>" option to the log family of commands can now be
+  spelled as "--format=<style>".  In addition, --format=%formatstring
+  is a short-hand for --pretty=tformat:%formatstring.
+
+* "--oneline" is a synonym for "--pretty=oneline --abbrev-commit".
+
+* "--graph" to the "git log" family can draw the commit ancestry graph
+  in colors.
+
+* If you realize that you botched the patch when you are editing hunks
+  with the 'edit' action in git-add -i/-p, you can abort the editor to
+  tell git not to apply it.
+
+* @{-1} is a new way to refer to the last branch you were on introduced in
+  1.6.2, but the initial implementation did not teach this to a few
+  commands.  Now the syntax works with "branch -m @{-1} newname".
+
+* git-archive learned --output=<file> option.
+
+* git-archive takes attributes from the tree being archived; strictly
+  speaking, this is an incompatible behaviour change, but is a good one.
+  Use --worktree-attributes option to allow it to read attributes from
+  the work tree as before (deprecated git-tar tree command always reads
+  attributes from the work tree).
+
+* git-bisect shows not just the number of remaining commits whose goodness
+  is unknown, but also shows the estimated number of remaining rounds.
+
+* You can give --date=<format> option to git-blame.
+
+* "git-branch -r" shows HEAD symref that points at a remote branch in
+  interest of each tracked remote repository.
+
+* "git-branch -v -v" is a new way to get list of names for branches and the
+  "upstream" branch for them.
+
+* git-config learned -e option to open an editor to edit the config file
+  directly.
+
+* git-clone runs post-checkout hook when run without --no-checkout.
+
+* git-difftool is now part of the officially supported command, primarily
+  maintained by David Aguilar.
+
+* git-for-each-ref learned a new "upstream" token.
+
+* git-format-patch can be told to use attachment with a new configuration,
+  format.attach.
+
+* git-format-patch can be told to produce deep or shallow message threads.
+
+* git-format-patch can be told to always add sign-off with a configuration
+  variable.
+
+* git-format-patch learned format.headers configuration to add extra
+  header fields to the output.  This behaviour is similar to the existing
+  --add-header=<header> option of the command.
+
+* git-format-patch gives human readable names to the attached files, when
+  told to send patches as attachments.
+
+* git-grep learned to highlight the found substrings in color.
+
+* git-imap-send learned to work around Thunderbird's inability to easily
+  disable format=flowed with a new configuration, imap.preformattedHTML.
+
+* git-rebase can be told to rebase the series even if your branch is a
+  descendant of the commit you are rebasing onto with --force-rebase
+  option.
+
+* git-rebase can be told to report diffstat with the --stat option.
+
+* Output from git-remote command has been vastly improved.
+
+* "git remote update --prune $remote" updates from the named remote and
+  then prunes stale tracking branches.
+
+* git-send-email learned --confirm option to review the Cc: list before
+  sending the messages out.
+
+(developers)
+
+* Test scripts can be run under valgrind.
+
+* Test scripts can be run with installed git.
+
+* Makefile learned 'coverage' option to run the test suites with
+  coverage tracking enabled.
+
+* Building the manpages with docbook-xsl between 1.69.1 and 1.71.1 now
+  requires setting DOCBOOK_SUPPRESS_SP to work around a docbook-xsl bug.
+  This workaround used to be enabled by default, but causes problems
+  with newer versions of docbook-xsl.  In addition, there are a few more
+  knobs you can tweak to work around issues with various versions of the
+  docbook-xsl package.  See comments in Documentation/Makefile for details.
+
+* Support for building and testing a subset of git on a system without a
+  working perl has been improved.
+
+
+Fixes since v1.6.2
+------------------
+
+All of the fixes in v1.6.2.X maintenance series are included in this
+release, unless otherwise noted.
+
+Here are fixes that this release has, but have not been backported to
+v1.6.2.X series.
+
+* "git-apply" rejected a patch that swaps two files (i.e. renames A to B
+  and B to A at the same time).  May need to be backported by cherry
+  picking d8c81df and then 7fac0ee).
+
+* The initial checkout did not read the attributes from the .gitattribute
+  file that is being checked out.
+
+* git-gc spent excessive amount of time to decide if an object appears
+  in a locally existing pack (if needed, backport by merging 69e020a).
index ba07c8c57107a8438d5b3ce1a49761dba7182d25..76fc84d8780762e083cd4ca584b9d783b8c0cd81 100644 (file)
@@ -6,9 +6,13 @@ Checklist (and a short version for the impatient):
        - check for unnecessary whitespace with "git diff --check"
          before committing
        - do not check in commented out code or unneeded files
-       - provide a meaningful commit message
        - the first line of the commit message should be a short
          description and should skip the full stop
+       - the body should provide a meaningful commit message, which:
+               - uses the imperative, present tense: "change",
+                 not "changed" or "changes".
+               - includes motivation for the change, and contrasts
+                 its implementation with previous behaviour
        - if you want your work included in git.git, add a
          "Signed-off-by: Your Name <you@example.com>" line to the
          commit message (or just use the option "-s" when
@@ -62,6 +66,14 @@ Describe the technical detail of the change(s).
 
 If your description starts to get too long, that's a sign that you
 probably need to split up your commit to finer grained pieces.
+That being said, patches which plainly describe the things that
+help reviewers check the patch, and future maintainers understand
+the code, are the most beautiful patches.  Descriptions that summarise
+the point in the subject well, and describe the motivation for the
+change, the approach taken by the change, and if relevant how this
+differs substantially from the prior version, can be found on Usenet
+archives back into the late 80's.  Consider it like good Netiquette,
+but for code.
 
 Oh, another thing.  I am picky about whitespaces.  Make sure your
 changes do not trigger errors with the sample pre-commit hook shipped
@@ -376,9 +388,36 @@ Thunderbird
 
 (A Large Angry SCM)
 
+By default, Thunderbird will both wrap emails as well as flag them as
+being 'format=flowed', both of which will make the resulting email unusable
+by git.
+
 Here are some hints on how to successfully submit patches inline using
 Thunderbird.
 
+There are two different approaches.  One approach is to configure
+Thunderbird to not mangle patches.  The second approach is to use
+an external editor to keep Thunderbird from mangling the patches.
+
+Approach #1 (configuration):
+
+This recipe is current as of Thunderbird 2.0.0.19.  Three steps:
+  1.  Configure your mail server composition as plain text
+      Edit...Account Settings...Composition & Addressing,
+        uncheck 'Compose Messages in HTML'.
+  2.  Configure your general composition window to not wrap
+      Edit..Preferences..Composition, wrap plain text messages at 0
+  3.  Disable the use of format=flowed
+      Edit..Preferences..Advanced..Config Editor.  Search for:
+        mailnews.send_plaintext_flowed
+      toggle it to make sure it is set to 'false'.
+
+After that is done, you should be able to compose email as you
+otherwise would (cut + paste, git-format-patch | git-imap-send, etc),
+and the patches should not be mangled.
+
+Approach #2 (external editor):
+
 This recipe appears to work with the current [*1*] Thunderbird from Suse.
 
 The following Thunderbird extensions are needed:
@@ -464,6 +503,12 @@ message, complete the addressing and subject fields, and press send.
 Gmail
 -----
 
+GMail does not appear to have any way to turn off line wrapping in the web
+interface, so this will mangle any emails that you send.  You can however
+use any IMAP email client to connect to the google imap server, and forward
+the emails through that.  Just make sure to disable line wrapping in that
+email client.  Alternatively, use "git send-email" instead.
+
 Submitting properly formatted patches via Gmail is simple now that
 IMAP support is available. First, edit your ~/.gitconfig to specify your
 account settings:
@@ -476,6 +521,9 @@ account settings:
        port = 993
        sslverify = false
 
+You might need to instead use: folder = "[Google Mail]/Drafts" if you get an error
+that the "Folder doesn't exist".
+
 Next, ensure that your Gmail settings are correct. In "Settings" the
 "Use Unicode (UTF-8) encoding for outgoing messages" should be checked.
 
@@ -486,3 +534,4 @@ command to send the patch emails to your Gmail Drafts folder.
 
 Go to your Gmail account, open the Drafts folder, find the patch email, fill
 in the To: and CC: fields and send away!
+
index 1e735df3bb5ba15b7f088442439e48a99ca85dbd..dc76e7f073f23cd911da0b0d0d49b72726576f1a 100644 (file)
@@ -27,7 +27,7 @@ ifdef::backend-docbook[]
 endif::backend-docbook[]
 
 ifdef::backend-docbook[]
-ifndef::docbook-xsl-172[]
+ifndef::git-asciidoc-no-roff[]
 # "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
 # v1.72 breaks with this because it replaces dots not in roff requests.
 [listingblock]
@@ -42,16 +42,16 @@ ifdef::doctype-manpage[]
 endif::doctype-manpage[]
 </literallayout>
 {title#}</example>
-endif::docbook-xsl-172[]
+endif::git-asciidoc-no-roff[]
 
-ifdef::docbook-xsl-172[]
+ifdef::git-asciidoc-no-roff[]
 ifdef::doctype-manpage[]
 # The following two small workarounds insert a simple paragraph after screen
 [listingblock]
 <example><title>{title}</title>
-<screen>
+<literallayout>
 |
-</screen><simpara></simpara>
+</literallayout><simpara></simpara>
 {title#}</example>
 
 [verseblock]
@@ -59,10 +59,11 @@ ifdef::doctype-manpage[]
 {title%}<literallayout{id? id="{id}"}>
 {title#}<literallayout>
 |
-</literallayout><simpara></simpara>
+</literallayout>
 {title#}</para></formalpara>
+{title%}<simpara></simpara>
 endif::doctype-manpage[]
-endif::docbook-xsl-172[]
+endif::git-asciidoc-no-roff[]
 endif::backend-docbook[]
 
 ifdef::doctype-manpage[]
index 7f28432254a81e4c39ce8ff204b4d6618050c5c6..1625ffce6a1910c879447705fea4e3301039debd 100644 (file)
@@ -39,7 +39,7 @@ of lines before or after the line given by <start>.
        Show raw timestamp (Default: off).
 
 -S <revs-file>::
-       Use revs from revs-file instead of calling linkgit:git-rev-list[1].
+       Use revisions from revs-file instead of calling linkgit:git-rev-list[1].
 
 --reverse::
        Walk history forward instead of backward. Instead of showing
@@ -70,11 +70,19 @@ of lines before or after the line given by <start>.
        tree copy has the contents of the named file (specify
        `-` to make the command read from the standard input).
 
+--date <format>::
+       The value is one of the following alternatives:
+       {relative,local,default,iso,rfc,short}. If --date is not
+       provided, the value of the blame.date config variable is
+       used. If the blame.date config variable is also not set, the
+       iso format is used. For more information, See the discussion
+       of the --date option at linkgit:git-log[1].
+
 -M|<num>|::
        Detect moving lines in the file as well.  When a commit
        moves a block of lines in a file (e.g. the original file
        has A and then B, and the commit changes it to B and
-       then A), traditional 'blame' algorithm typically blames
+       then A), the traditional 'blame' algorithm typically blames
        the lines that were moved up (i.e. B) to the parent and
        assigns blame to the lines that were moved down (i.e. A)
        to the child commit.  With this option, both groups of lines
@@ -90,8 +98,8 @@ commit.
        files that were modified in the same commit.  This is
        useful when you reorganize your program and move code
        around across files.  When this option is given twice,
-       the command looks for copies from all other files in the
-       parent for the commit that creates the file in addition.
+       the command additionally looks for copies from all other
+       files in the parent for the commit that creates the file.
 +
 <num> is optional but it is the lower bound on the number of
 alphanumeric characters that git must detect as moving
diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl
deleted file mode 100644 (file)
index 6a361a2..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-<!-- callout.xsl: converts asciidoc callouts to man page format -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
-<xsl:template match="co">
-       <xsl:value-of select="concat('\fB(',substring-after(@id,'-'),')\fR')"/>
-</xsl:template>
-<xsl:template match="calloutlist">
-       <xsl:text>.sp&#10;</xsl:text>
-       <xsl:apply-templates/>
-       <xsl:text>&#10;</xsl:text>
-</xsl:template>
-<xsl:template match="callout">
-       <xsl:value-of select="concat('\fB',substring-after(@arearefs,'-'),'. \fR')"/>
-       <xsl:apply-templates/>
-       <xsl:text>.br&#10;</xsl:text>
-</xsl:template>
-
-<!-- sorry, this is not about callouts, but attempts to work around
- spurious .sp at the tail of the line docbook stylesheets seem to add -->
-<xsl:template match="simpara">
-  <xsl:variable name="content">
-    <xsl:apply-templates/>
-  </xsl:variable>
-  <xsl:value-of select="normalize-space($content)"/>
-  <xsl:if test="not(ancestor::authorblurb) and
-                not(ancestor::personblurb)">
-    <xsl:text>&#10;&#10;</xsl:text>
-  </xsl:if>
-</xsl:template>
-
-</xsl:stylesheet>
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..5dcad94f841c395beb21961ebdacd341d76b25c9 100644 (file)
@@ -2,15 +2,15 @@ CONFIGURATION FILE
 ------------------
 
 The git configuration file contains a number of variables that affect
-the git command's behavior. `.git/config` file for each repository
-is used to store the information for that repository, and
-`$HOME/.gitconfig` is used to store per user information to give
-fallback values for `.git/config` file. The file `/etc/gitconfig`
-can be used to store system-wide defaults.
-
-They can be used by both the git plumbing
-and the porcelains. The variables are divided into sections, where
-in the fully qualified variable name the variable itself is the last
+the git command's behavior. The `.git/config` file in each repository
+is used to store the configuration for that repository, and
+`$HOME/.gitconfig` is used to store a per-user configuration as
+fallback values for the `.git/config` file. The file `/etc/gitconfig`
+can be used to store a system-wide default configuration.
+
+The configuration variables are used by both the git plumbing
+and the porcelains. The variables are divided into sections, wherein
+the fully qualified variable name of the variable itself is the last
 dot-separated segment and the section name is everything before the last
 dot. The variable names are case-insensitive and only alphanumeric
 characters are allowed. Some variables may appear multiple times.
@@ -25,35 +25,35 @@ blank lines are ignored.
 The file consists of sections and variables.  A section begins with
 the name of the section in square brackets and continues until the next
 section begins.  Section names are not case sensitive.  Only alphanumeric
-characters, '`-`' and '`.`' are allowed in section names.  Each variable
-must belong to some section, which means that there must be section
-header before first setting of a variable.
+characters, `-` and `.` are allowed in section names.  Each variable
+must belong to some section, which means that there must be section
+header before the first setting of a variable.
 
 Sections can be further divided into subsections.  To begin a subsection
 put its name in double quotes, separated by space from the section name,
-in the section header, like in example below:
+in the section header, like in the example below:
 
 --------
        [section "subsection"]
 
 --------
 
-Subsection names can contain any characters except newline (doublequote
-'`"`' and backslash have to be escaped as '`\"`' and '`\\`',
-respectively) and are case sensitive.  Section header cannot span multiple
+Subsection names are case sensitive and can contain any characters except
+newline (doublequote `"` and backslash have to be escaped as `\"` and `\\`,
+respectively).  Section headers cannot span multiple
 lines.  Variables may belong directly to a section or to a given subsection.
 You can have `[section]` if you have `[section "subsection"]`, but you
 don't need to.
 
-There is also (case insensitive) alternative `[section.subsection]` syntax.
-In this syntax subsection names follow the same restrictions as for section
-name.
+There is also a case insensitive alternative `[section.subsection]` syntax.
+In this syntax, subsection names follow the same restrictions as for section
+names.
 
 All the other lines are recognized as setting variables, in the form
 'name = value'.  If there is no equal sign on the line, the entire line
 is taken as 'name' and the variable is recognized as boolean "true".
 The variable names are case-insensitive and only alphanumeric
-characters and '`-`' are allowed.  There can be more than one value
+characters and `-` are allowed.  There can be more than one value
 for a given variable; we say then that variable is multivalued.
 
 Leading and trailing whitespace in a variable value is discarded.
@@ -61,26 +61,26 @@ Internal whitespace within a variable value is retained verbatim.
 
 The values following the equals sign in variable assign are all either
 a string, an integer, or a boolean.  Boolean values may be given as yes/no,
-0/1 or true/false.  Case is not significant in boolean values, when
+0/1, true/false or on/off.  Case is not significant in boolean values, when
 converting value to the canonical form using '--bool' type specifier;
 'git-config' will ensure that the output is "true" or "false".
 
 String values may be entirely or partially enclosed in double quotes.
-You need to enclose variable value in double quotes if you want to
-preserve leading or trailing whitespace, or if variable value contains
-beginning of comment characters (if it contains '#' or ';').
-Double quote '`"`' and backslash '`\`' characters in variable value must
-be escaped: use '`\"`' for '`"`' and '`\\`' for '`\`'.
-
-The following escape sequences (beside '`\"`' and '`\\`') are recognized:
-'`\n`' for newline character (NL), '`\t`' for horizontal tabulation (HT, TAB)
-and '`\b`' for backspace (BS).  No other char escape sequence, nor octal
+You need to enclose variable values in double quotes if you want to
+preserve leading or trailing whitespace, or if the variable value contains
+comment characters (i.e. it contains '#' or ';').
+Double quote `"` and backslash `\` characters in variable values must
+be escaped: use `\"` for `"` and `\\` for `\`.
+
+The following escape sequences (beside `\"` and `\\`) are recognized:
+`\n` for newline character (NL), `\t` for horizontal tabulation (HT, TAB)
+and `\b` for backspace (BS).  No other char escape sequence, nor octal
 char sequences are valid.
 
-Variable value ending in a '`\`' is continued on the next line in the
+Variable values ending in a `\` are continued on the next line in the
 customary UNIX fashion.
 
-Some variables may require special value format.
+Some variables may require special value format.
 
 Example
 ~~~~~~~
@@ -221,6 +221,11 @@ core.gitProxy::
 Can be overridden by the 'GIT_PROXY_COMMAND' environment variable
 (which always applies universally, without the special "for"
 handling).
++
+The special string `none` can be used as the proxy command to
+specify that no proxy be used for a given domain pattern.
+This is useful for excluding servers inside a firewall from
+proxy use, while defaulting to a common proxy for external domains.
 
 core.ignoreStat::
        If true, commands which modify both the working tree and the index
@@ -290,8 +295,10 @@ core.sharedRepository::
        group-shareable. When 'umask' (or 'false'), git will use permissions
        reported by umask(2). When '0xxx', where '0xxx' is an octal number,
        files in the repository will have this mode value. '0xxx' will override
-       user's umask value, and thus, users with a safe umask (0077) can use
-       this option. Examples: '0660' is equivalent to 'group'. '0640' is a
+       user's umask value (whereas the other options will only override
+       requested parts of the user's umask value). Examples: '0660' will make
+       the repo read/write-able for the owner and group, but inaccessible to
+       others (equivalent to 'group' unless umask is e.g. '0022'). '0640' is a
        repository that is group-readable but not group-writable.
        See linkgit:git-init[1]. False by default.
 
@@ -382,9 +389,9 @@ core.pager::
        to override git's default settings this way, you need
        to be explicit.  For example, to disable the S option
        in a backward compatible manner, set `core.pager`
-       to "`less -+$LESS -FRX`".  This will be passed to the
+       to `less -+$LESS -FRX`.  This will be passed to the
        shell by git, which will translate the final command to
-       "`LESS=FRSX less -+FRSX -FRX`".
+       `LESS=FRSX less -+FRSX -FRX`.
 
 core.whitespace::
        A comma separated list of common whitespace problems to
@@ -422,6 +429,15 @@ relatively high IO latencies.  With this set to 'true', git will do the
 index comparison to the filesystem data in parallel, allowing
 overlapping IO's.
 
+core.createObject::
+       You can set this to 'link', in which case a hardlink followed by
+       a delete of the source are used to make sure that object creation
+       will not overwrite existing objects.
++
+On some file system/operating system combinations, this is unreliable.
+Set this config setting to 'rename' there; However, This will remove the
+check that makes sure that existing object files will not get overwritten.
+
 alias.*::
        Command aliases for the linkgit:git[1] command wrapper - e.g.
        after defining "alias.last = cat-file commit HEAD", the invocation
@@ -468,10 +484,14 @@ branch.autosetuprebase::
        This option defaults to never.
 
 branch.<name>.remote::
-       When in branch <name>, it tells 'git-fetch' which remote to fetch.
-       If this option is not given, 'git-fetch' defaults to remote "origin".
+       When in branch <name>, it tells 'git-fetch' and 'git-push' which
+       remote to fetch from/push to.  It defaults to `origin` if no remote is
+       configured. `origin` is also used if you are not on any branch.
 
 branch.<name>.merge::
+       Defines, together with branch.<name>.remote, the upstream branch
+       for the given branch. It tells 'git-fetch'/'git-pull' which
+       branch to merge and can also affect 'git-push' (see push.default).
        When in branch <name>, it tells 'git-fetch' the default
        refspec to be marked for merging in FETCH_HEAD. The value is
        handled like the remote part of a refspec, and must match a
@@ -548,6 +568,25 @@ color.diff.<slot>::
        whitespace errors). The values of these variables may be specified as
        in color.branch.<slot>.
 
+color.grep::
+       When set to `always`, always highlight matches.  When `false` (or
+       `never`), never.  When set to `true` or `auto`, use color only
+       when the output is written to the terminal.  Defaults to `false`.
+
+color.grep.external::
+       The string value of this variable is passed to an external 'grep'
+       command as a command line option if match highlighting is turned
+       on.  If set to an empty string, no option is passed at all,
+       turning off coloring for external 'grep' calls; this is the default.
+       For GNU grep, set it to `--color=always` to highlight matches even
+       when a pager is used.
+
+color.grep.match::
+       Use customized color for matches.  The value of this variable
+       may be specified as in color.branch.<slot>.  It is passed using
+       the environment variables 'GREP_COLOR' and 'GREP_COLORS' when
+       calling an external 'grep'.
+
 color.interactive::
        When set to `always`, always use colors for interactive prompts
        and displays (such as those used by "git-add --interactive").
@@ -556,8 +595,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 +678,33 @@ diff.suppressBlankEmpty::
        A boolean to inhibit the standard behavior of printing a space
        before each empty output line. Defaults to false.
 
+diff.tool::
+       Controls which diff tool is used.  `diff.tool` overrides
+       `merge.tool` when used by linkgit:git-difftool[1] and has
+       the same valid values as `merge.tool` minus "tortoisemerge"
+       and plus "kompare".
+
+difftool.<tool>.path::
+       Override the path for the given tool.  This is useful in case
+       your tool is not in the PATH.
+
+difftool.<tool>.cmd::
+       Specify the command to invoke the specified diff tool.
+       The specified command is evaluated in shell 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.
+
+difftool.prompt::
+       Prompt before each invocation of the diff tool.
+
+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
@@ -650,6 +716,13 @@ fetch.unpackLimit::
        especially on slow filesystems.  If not set, the value of
        `transfer.unpackLimit` is used instead.
 
+format.attach::
+       Enable multipart/mixed attachments as the default for
+       'format-patch'.  The value can also be a double quoted string
+       which will enable attachments as the default and set the
+       value as the boundary.  See the --attach option in
+       linkgit:git-format-patch[1].
+
 format.numbered::
        A boolean which can enable or disable sequence numbers in patch
        subjects.  It defaults to "auto" which enables it only if there
@@ -661,6 +734,14 @@ format.headers::
        Additional email headers to include in a patch to be submitted
        by mail.  See linkgit:git-format-patch[1].
 
+format.cc::
+       Additional "Cc:" headers to include in a patch to be submitted
+       by mail.  See the --cc option in linkgit:git-format-patch[1].
+
+format.subjectprefix::
+       The default for format-patch is to output files with the '[PATCH]'
+       subject prefix. Use this variable to change that prefix.
+
 format.suffix::
        The default for format-patch is to output files with the suffix
        `.patch`. Use this variable to change that suffix (make sure to
@@ -671,6 +752,23 @@ format.pretty::
        See linkgit:git-log[1], linkgit:git-show[1],
        linkgit:git-whatchanged[1].
 
+format.thread::
+       The default threading style for 'git-format-patch'.  Can be
+       either a boolean value, `shallow` or `deep`.  `shallow`
+       threading makes every mail a reply to the head of the series,
+       where the head is chosen from the cover letter, the
+       `\--in-reply-to`, and the first patch mail, in this order.
+       `deep` threading makes every mail a reply to the previous one.
+       A true boolean value is the same as `shallow`, and a false
+       value disables threading.
+
+format.signoff::
+    A boolean value which lets you enable the `-s/--signoff` option of
+    format-patch by default. *Note:* Adding the Signed-off-by: line to a
+    patch should be a conscious act and means that you certify you have
+    the rights to submit this work under the same open source license.
+    Please see the 'SubmittingPatches' document for further discussion.
+
 gc.aggressiveWindow::
        The window size parameter used in the delta compression
        algorithm used by 'git-gc --aggressive'.  This defaults
@@ -725,6 +823,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 +1092,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 +1111,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 +1163,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.
@@ -1116,7 +1243,7 @@ pager.<cmd>::
        particular git subcommand when writing to a tty.  If
        `\--paginate` or `\--no-pager` is specified on the command line,
        it takes precedence over this option.  To disable pagination for
-       all commands, set `core.pager` or 'GIT_PAGER' to "`cat`".
+       all commands, set `core.pager` or `GIT_PAGER` to `cat`.
 
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
@@ -1125,6 +1252,23 @@ pull.octopus::
 pull.twohead::
        The default merge strategy to use when pulling a single branch.
 
+push.default::
+       Defines the action git push should take if no refspec is given
+       on the command line, no refspec is configured in the remote, and
+       no refspec is implied by any of the options given on the command
+       line. Possible values are:
++
+* `nothing` do not push anything.
+* `matching` push all matching branches.
+  All branches having the same name in both ends are considered to be
+  matching. This is the default.
+* `tracking` push the current branch to its upstream branch.
+* `current` push the current branch to a branch of the same name.
+
+rebase.stat::
+       Whether to show a diffstat of what changed upstream since the last
+       rebase. False by default.
+
 receive.fsckObjects::
        If it is set to true, git-receive-pack will check all received
        objects. It will abort in the case of a malformed object or a
index b432d2518aba3d1be9d86fb1613bbe2f5fa9da4d..9276faeb11aec2650393c56fe5edf2b571692dbd 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>.
@@ -159,7 +176,10 @@ endif::git-format-patch[]
        number.
 
 -S<string>::
-       Look for differences that contain the change in <string>.
+       Look for differences that introduce or remove an instance of
+       <string>. Note that this is different than the string simply
+       appearing in diff output; see the 'pickaxe' entry in
+       linkgit:gitdiffcore[7] for more details.
 
 --pickaxe-all::
        When -S finds a change, show all the changes in that
@@ -205,6 +225,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 b878b385c6967f4c64ba30bdfe8f9bd24bef91e3..e11c8f053ad753f218a0a79ddacfe62862603bb9 100644 (file)
@@ -16,6 +16,7 @@ body blockquote {
 html body {
   margin: 1em 5% 1em 5%;
   line-height: 1.2;
+  font-family: sans-serif;
 }
 
 body div {
@@ -128,6 +129,15 @@ body pre {
 
 tt.literal, code.literal {
   color: navy;
+  font-family: sans-serif;
+}
+
+code.literal:before { content: "'"; }
+code.literal:after { content: "'"; }
+
+em {
+  font-style: italic;
+  color: #064;
 }
 
 div.literallayout p {
@@ -137,7 +147,6 @@ div.literallayout p {
 
 div.literallayout {
   font-family: monospace;
-#  margin: 0.5em 10% 0.5em 1em;
   margin: 0em;
   color: navy;
   border: 1px solid silver;
@@ -187,7 +196,8 @@ dt {
 }
 
 dt span.term {
-  font-style: italic;
+  font-style: normal;
+  color: navy;
 }
 
 div.variablelist dd p {
index e4c711bbd2d1a95d501978673bc52f7a2957fba7..d938b422893067feb1a430edb34fb51ef7db6d85 100644 (file)
@@ -245,8 +245,11 @@ patch::
 
        y - stage this hunk
        n - do not stage this hunk
+       q - quit, do not stage this hunk nor any of the remaining ones
        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
@@ -263,13 +266,6 @@ diff::
   This lets you review what will be committed (i.e. between
   HEAD and index).
 
-Bugs
-----
-The interactive mode does not work with files whose names contain
-characters that need C-quoting.  `core.quotepath` configuration can be
-used to work this limitation around to some degree, but backslash,
-double-quote and control characters will still have problems.
-
 SEE ALSO
 --------
 linkgit:git-status[1]
index b9c6fac7483dbefba0afb60a76ac0362aa390a6d..6d92cbee6475e29660d91a97683bf5843aacab38 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,12 +27,12 @@ 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::
-       Add `Signed-off-by:` line to the commit message, using
+       Add `Signed-off-by:` line to the commit message, using
        the committer identity of yourself.
 
 -k::
@@ -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
+       value 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 the author date by using the same
+       value as the committer date.
+
 --skip::
        Skip the current patch.  This is only meaningful when
        restarting an aborted patch.
@@ -101,24 +115,24 @@ DISCUSSION
 ----------
 
 The commit author name is taken from the "From: " line of the
-message, and commit author time is taken from the "Date: " line
+message, and commit author date is taken from the "Date: " line
 of the message.  The "Subject: " line is used as the title of
 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 "Subject: " line is supposed to concisely describe what the
+commit is about in one line of 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.
+"From: " and "Subject: " lines starting the body (the rest of the
+message after the blank line terminating the RFC2822 headers)
+override the respective commit author name and title values taken
+from the headers.
 
 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
-lines are automatically stripped.
+where the patch begins.  Excess whitespace at the end of each
+line is 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 `git am`, 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..bc132c87e1a5b9f42ea1f2bbd4e3c46f67fc3852 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git archive' --format=<fmt> [--list] [--prefix=<prefix>/] [<extra>]
+             [--output=<file>] [--worktree-attributes]
              [--remote=<repo> [--exec=<git-upload-archive>]] <tree-ish>
              [path...]
 
@@ -22,7 +23,7 @@ prepended to the filenames in the archive.
 
 'git-archive' behaves differently when given a tree ID versus when
 given a commit ID or tag ID.  In the first case the current time is
-used as modification time of each file in the archive.  In the latter
+used as the modification time of each file in the archive.  In the latter
 case the commit time as recorded in the referenced commit object is
 used instead.  Additionally the commit ID is stored in a global
 extended pax header if the tar format is used; it can be extracted
@@ -47,12 +48,18 @@ OPTIONS
 --prefix=<prefix>/::
        Prepend <prefix>/ to each filename in the archive.
 
+--output=<file>::
+       Write the archive to <file> instead of stdout.
+
+--worktree-attributes::
+       Look for attributes in .gitattributes in working directory too.
+
 <extra>::
-       This can be any options that the archiver backend understand.
+       This can be any options that the archiver backend understands.
        See next section.
 
 --remote=<repo>::
-       Instead of making a tar archive from local repository,
+       Instead of making a tar archive from the local repository,
        retrieve a tar archive from a remote repository.
 
 --exec=<git-upload-archive>::
@@ -88,12 +95,24 @@ 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 -)::
 
        Create a tar archive that contains the contents of the
-       latest commit on the current branch, and extracts it in
+       latest commit on the current branch, and extract it in the
        `/var/tmp/junk` directory.
 
 git archive --format=tar --prefix=git-1.4.0/ v1.4.0 | gzip >git-1.4.0.tar.gz::
@@ -110,6 +129,11 @@ git archive --format=zip --prefix=git-docs/ HEAD:Documentation/ > git-1.4.0-docs
        Put everything in the current head's Documentation/ directory
        into 'git-1.4.0-docs.zip', with the prefix 'git-docs/'.
 
+
+SEE ALSO
+--------
+linkgit:gitattributes[5]
+
 Author
 ------
 Written by Franck Bui-Huu and Rene Scharfe.
index 147ea381977a459f1fa624408cc3af3e9b9f3bd5..ffc02c737cf0a8d2bdb3cd812e0a23d43ee27fd7 100644 (file)
@@ -3,7 +3,7 @@ git-bisect(1)
 
 NAME
 ----
-git-bisect - Find the change that introduced a bug by binary search
+git-bisect - Find by binary search the change that introduced a bug
 
 
 SYNOPSIS
@@ -39,7 +39,8 @@ help" or "git bisect -h" to get a long usage description.
 Basic bisect commands: start, bad, good
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The way you use it is:
+Using the Linux kernel tree as an example, basic use of the bisect
+command is as follows:
 
 ------------------------------------------------
 $ git bisect start
@@ -48,61 +49,63 @@ $ git bisect good v2.6.13-rc2    # v2.6.13-rc2 was the last version
                                  # tested that was good
 ------------------------------------------------
 
-When you give at least one bad and one good versions, it will bisect
-the revision tree and say something like:
+When you have specified at least one bad and one good version, the
+command bisects the revision tree and outputs something similar to
+the following:
 
 ------------------------------------------------
 Bisecting: 675 revisions left to test after this
 ------------------------------------------------
 
-and check out the state in the middle. Now, compile that kernel, and
-boot it. Now, let's say that this booted kernel works fine, then just
-do
+The state in the middle of the set of revisions is then checked out.
+You would now compile that kernel and boot it. If the booted kernel
+works correctly, you would then issue the following command:
 
 ------------------------------------------------
 $ git bisect good                      # this one is good
 ------------------------------------------------
 
-which will now say
+The output of this command would be something similar to the following:
 
 ------------------------------------------------
 Bisecting: 337 revisions left to test after this
 ------------------------------------------------
 
-and you continue along, compiling that one, testing it, and depending
-on whether it is good or bad, you say "git bisect good" or "git bisect
-bad", and ask for the next bisection.
+You keep repeating this process, compiling the tree, testing it, and
+depending on whether it is good or bad issuing the command "git bisect good"
+or "git bisect bad" to ask for the next bisection.
 
-Until you have no more left, and you'll have been left with the first
-bad kernel rev in "refs/bisect/bad".
+Eventually there will be no more revisions left to bisect, and you
+will have been left with the first bad kernel revision in "refs/bisect/bad".
 
 Bisect reset
 ~~~~~~~~~~~~
 
-Oh, and then after you want to reset to the original head, do a
+To return to the original head after a bisect session, issue the
+following command:
 
 ------------------------------------------------
 $ git bisect reset
 ------------------------------------------------
 
-to get back to the original branch, instead of being on the bisection
-commit ("git bisect start" will do that for you too, actually: it will
-reset the bisection state).
+This resets the tree to the original branch instead of being on the
+bisection commit ("git bisect start" will also do that, as it resets
+the bisection state).
 
 Bisect visualize
 ~~~~~~~~~~~~~~~~
 
-During the bisection process, you can say
+To see the currently remaining suspects in 'gitk', issue the following
+command during the bisection process:
 
 ------------
 $ git bisect visualize
 ------------
 
-to see the currently remaining suspects in 'gitk'.  `visualize` is a bit
-too long to type and `view` is provided as a synonym.
+`view` may also be used as a synonym for `visualize`.
 
-If 'DISPLAY' environment variable is not set, 'git log' is used
-instead.  You can even give command line options such as `-p` and
+If the 'DISPLAY' environment variable is not set, 'git log' is used
+instead.  You can also give command line options such as `-p` and
 `--stat`.
 
 ------------
@@ -112,57 +115,58 @@ $ git bisect view --stat
 Bisect log and bisect replay
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-The good/bad input is logged, and
+After having marked revisions as good or bad, issue the following
+command to show what has been done so far:
 
 ------------
 $ git bisect log
 ------------
 
-shows what you have done so far. You can truncate its output somewhere
-and save it in a file, and run
+If you discover that you made a mistake in specifying the status of a
+revision, you can save the output of this command to a file, edit it to
+remove the incorrect entries, and then issue the following commands to
+return to a corrected state:
 
 ------------
+$ git bisect reset
 $ git bisect replay that-file
 ------------
 
-if you find later you made a mistake telling good/bad about a
-revision.
-
-Avoiding to test a commit
+Avoiding testing a commit
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 
-If in a middle of bisect session, you know what the bisect suggested
-to try next is not a good one to test (e.g. the change the commit
+If, in the middle of a bisect session, you know that the next suggested
+revision is not a good one to test (e.g. the change the commit
 introduces is known not to work in your environment and you know it
 does not have anything to do with the bug you are chasing), you may
-want to find a near-by commit and try that instead.
+want to find a nearby commit and try that instead.
 
-It goes something like this:
+For example:
 
 ------------
-$ git bisect good/bad                  # previous round was good/bad.
+$ git bisect good/bad                  # previous round was good or bad.
 Bisecting: 337 revisions left to test after this
 $ git bisect visualize                 # oops, that is uninteresting.
-$ git reset --hard HEAD~3              # try 3 revs before what
+$ git reset --hard HEAD~3              # try 3 revisions before what
                                        # was suggested
 ------------
 
-Then compile and test the one you chose to try. After that, tell
-bisect what the result was as usual.
+Then compile and test the chosen revision, and afterwards mark
+the revision as good or bad in the usual manner.
 
 Bisect skip
 ~~~~~~~~~~~~
 
-Instead of choosing by yourself a nearby commit, you may just want git
-to do it for you using:
+Instead of choosing by yourself a nearby commit, you can ask git
+to do it for you by issuing the command:
 
 ------------
 $ git bisect skip                 # Current version cannot be tested
 ------------
 
 But computing the commit to test may be slower afterwards and git may
-eventually not be able to tell the first bad among a bad and one or
-more "skip"ped commits.
+eventually not be able to tell the first bad commit among a bad commit
+and one or more skipped commits.
 
 You can even skip a range of commits, instead of just one commit,
 using the "'<commit1>'..'<commit2>'" notation. For example:
@@ -171,33 +175,34 @@ using the "'<commit1>'..'<commit2>'" notation. For example:
 $ git bisect skip v2.5..v2.6
 ------------
 
-would mean that no commit between `v2.5` excluded and `v2.6` included
-can be tested.
+This tells the bisect process that no commit after `v2.5`, up to and
+including `v2.6`, should be tested.
 
-Note that if you want to also skip the first commit of a range you can
-use something like:
+Note that if you also want to skip the first commit of the range you
+would issue the command:
 
 ------------
 $ git bisect skip v2.5 v2.5..v2.6
 ------------
 
-and the commit pointed to by `v2.5` will be skipped too.
+This tells the bisect process that the commits between `v2.5` included
+and `v2.6` included should be skipped.
+
 
 Cutting down bisection by giving more parameters to bisect start
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-You can further cut down the number of trials if you know what part of
-the tree is involved in the problem you are tracking down, by giving
-paths parameters when you say `bisect start`, like this:
+You can further cut down the number of trials, if you know what part of
+the tree is involved in the problem you are tracking down, by specifying
+path parameters when issuing the `bisect start` command:
 
 ------------
 $ git bisect start -- arch/i386 include/asm-i386
 ------------
 
-If you know beforehand more than one good commits, you can narrow the
-bisect space down without doing the whole tree checkout every time you
-give good commits. You give the bad revision immediately after `start`
-and then you give all the good revisions you have:
+If you know beforehand more than one good commit, you can narrow the
+bisect space down by specifying all of the good commits immediately after
+the bad commit when issuing the `bisect start` command:
 
 ------------
 $ git bisect start v2.6.20-rc6 v2.6.20-rc4 v2.6.20-rc1 --
@@ -209,38 +214,38 @@ Bisect run
 ~~~~~~~~~~
 
 If you have a script that can tell if the current source code is good
-or bad, you can automatically bisect using:
+or bad, you can bisect by issuing the command:
 
 ------------
-$ git bisect run my_script
+$ git bisect run my_script arguments
 ------------
 
-Note that the "run" script (`my_script` in the above example) should
-exit with code 0 in case the current source code is good.  Exit with a
+Note that the script (`my_script` in the above example) should
+exit with code 0 if the current source code is good, and exit with a
 code between 1 and 127 (inclusive), except 125, if the current
 source code is bad.
 
-Any other exit code will abort the automatic bisect process. (A
-program that does "exit(-1)" leaves $? = 255, see exit(3) manual page,
-the value is chopped with "& 0377".)
+Any other exit code will abort the bisect process. It should be noted
+that a program that terminates via "exit(-1)" leaves $? = 255, (see the
+exit(3) manual page), as the value is chopped with "& 0377".
 
 The special exit code 125 should be used when the current source code
-cannot be tested. If the "run" script exits with this code, the current
-revision will be skipped, see `git bisect skip` above.
+cannot be tested. If the script exits with this code, the current
+revision will be skipped (see `git bisect skip` above).
 
-You may often find that during bisect you want to have near-constant
-tweaks (e.g., s/#define DEBUG 0/#define DEBUG 1/ in a header file, or
-"revision that does not have this commit needs this patch applied to
-work around other problem this bisection is not interested in")
-applied to the revision being tested.
+You may often find that during a bisect session you want to have
+temporary modifications (e.g. s/#define DEBUG 0/#define DEBUG 1/ in a
+header file, or "revision that does not have this commit needs this
+patch applied to work around another problem this bisection is not
+interested in") applied to the revision being tested.
 
 To cope with such a situation, after the inner 'git bisect' finds the
-next revision to test, with the "run" script, you can apply that tweak
-before compiling, run the real test, and after the test decides if the
-revision (possibly with the needed tweaks) passed the test, rewind the
-tree to the pristine state.  Finally the "run" script can exit with
-the status of the real test to let the "git bisect run" command loop to
-determine the outcome.
+next revision to test, the script can apply the patch
+before compiling, run the real test, and afterwards decide if the
+revision (possibly with the needed patch) passed the test and then
+rewind the tree to the pristine state.  Finally the script should exit
+with the status of the real test to let the "git bisect run" command loop
+determine the eventual outcome of the bisect session.
 
 EXAMPLES
 --------
@@ -252,44 +257,60 @@ $ git bisect start HEAD v1.2 --      # HEAD is bad, v1.2 is good
 $ git bisect run make                # "make" builds the app
 ------------
 
+* Automatically bisect a test failure between origin and HEAD:
++
+------------
+$ git bisect start HEAD origin --    # HEAD is bad, origin is good
+$ git bisect run make test           # "make test" builds and tests
+------------
+
 * Automatically bisect a broken test suite:
 +
 ------------
 $ cat ~/test.sh
 #!/bin/sh
-make || exit 125                   # this "skip"s broken builds
+make || exit 125                   # this skips broken builds
 make test                          # "make test" runs the test suite
 $ git bisect start v1.3 v1.1 --    # v1.3 is bad, v1.1 is good
 $ git bisect run ~/test.sh
 ------------
 +
 Here we use a "test.sh" custom script. In this script, if "make"
-fails, we "skip" the current commit.
+fails, we skip the current commit.
 +
-It's safer to use a custom script outside the repo to prevent
+It is safer to use a custom script outside the repository to prevent
 interactions between the bisect, make and test processes and the
 script.
 +
-And "make test" should "exit 0", if the test suite passes, and
-"exit 1" (for example) otherwise.
+"make test" should "exit 0", if the test suite passes, and
+"exit 1" otherwise.
 
 * Automatically bisect a broken test case:
 +
 ------------
 $ cat ~/test.sh
 #!/bin/sh
-make || exit 125                     # this "skip"s broken builds
+make || exit 125                     # this skips broken builds
 ~/check_test_case.sh                 # does the test case passes ?
 $ git bisect start HEAD HEAD~10 --   # culprit is among the last 10
 $ git bisect run ~/test.sh
 ------------
 +
-Here "check_test_case.sh" should "exit 0", if the test case passes,
-and "exit 1" (for example) otherwise.
+Here "check_test_case.sh" should "exit 0" if the test case passes,
+and "exit 1" otherwise.
++
+It is safer if both "test.sh" and "check_test_case.sh" scripts are
+outside the repository to prevent interactions between the bisect,
+make and test processes and the scripts.
+
+* Automatically bisect a broken test suite:
++
+------------
+$ git bisect start HEAD HEAD~10 --   # culprit is among the last 10
+$ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
+------------
 +
-It's safer if both "test.sh" and "check_test_case.sh" scripts are
-outside the repo to prevent interactions between the bisect, make and
-test processes and the scripts.
+Does the same as the previous example, but on a single line.
 
 Author
 ------
index cc934e55c38902e1bac0964e134640c910b9de96..8c7b7b08386f69aaa96df2915566208d6abf3bf8 100644 (file)
@@ -18,9 +18,9 @@ DESCRIPTION
 Annotates each line in the given file with information from the revision which
 last modified the line. Optionally, start annotating from the given revision.
 
-Also it can limit the range of lines annotated.
+The command can also limit the range of lines annotated.
 
-This report doesn't tell you anything about lines which have been deleted or
+The report does not tell you anything about lines which have been deleted or
 replaced; you need to use a tool such as 'git-diff' or the "pickaxe"
 interface briefly mentioned in the following paragraph.
 
@@ -48,26 +48,26 @@ include::blame-options.txt[]
        lines between files (see `-C`) and lines moved within a
        file (see `-M`).  The first number listed is the score.
        This is the number of alphanumeric characters detected
-       to be moved between or within files.  This must be above
+       as having been moved between or within files.  This must be above
        a certain threshold for 'git-blame' to consider those lines
        of code to have been moved.
 
 -f::
 --show-name::
-       Show filename in the original commit.  By default
-       filename is shown if there is any line that came from a
-       file with different name, due to rename detection.
+       Show the filename in the original commit.  By default
+       the filename is shown if there is any line that came from a
+       file with different name, due to rename detection.
 
 -n::
 --show-number::
-       Show line number in the original commit (Default: off).
+       Show the line number in the original commit (Default: off).
 
 -s::
-       Suppress author name and timestamp from the output.
+       Suppress the author name and timestamp from the output.
 
 -w::
-       Ignore whitespace when comparing parent's version and
-       child's to find where the lines came from.
+       Ignore whitespace when comparing the parent's version and
+       the child's to find where the lines came from.
 
 
 THE PORCELAIN FORMAT
@@ -79,17 +79,17 @@ header at the minimum has the first line which has:
 - 40-byte SHA-1 of the commit the line is attributed to;
 - the line number of the line in the original file;
 - the line number of the line in the final file;
-- on a line that starts a group of line from a different
+- on a line that starts a group of lines from a different
   commit than the previous one, the number of lines in this
   group.  On subsequent lines this field is absent.
 
 This header line is followed by the following information
 at least once for each commit:
 
-- author name ("author"), email ("author-mail"), time
+- the author name ("author"), email ("author-mail"), time
   ("author-time"), and timezone ("author-tz"); similarly
   for committer.
-- filename in the commit the line is attributed to.
+- the filename in the commit that the line is attributed to.
 - the first line of the commit log message ("summary").
 
 The contents of the actual line is output after the above
@@ -100,23 +100,23 @@ header elements later.
 SPECIFYING RANGES
 -----------------
 
-Unlike 'git-blame' and 'git-annotate' in older git, the extent
-of annotation can be limited to both line ranges and revision
+Unlike 'git-blame' and 'git-annotate' in older versions of git, the extent
+of the annotation can be limited to both line ranges and revision
 ranges.  When you are interested in finding the origin for
-ll. 40-60 for file `foo`, you can use `-L` option like these
+lines 40-60 for file `foo`, you can use the `-L` option like so
 (they mean the same thing -- both ask for 21 lines starting at
 line 40):
 
        git blame -L 40,60 foo
        git blame -L 40,+21 foo
 
-Also you can use regular expression to specify the line range.
+Also you can use a regular expression to specify the line range:
 
        git blame -L '/^sub hello {/,/^}$/' foo
 
-would limit the annotation to the body of `hello` subroutine.
+which limits the annotation to the body of the `hello` subroutine.
 
-When you are not interested in changes older than the version
+When you are not interested in changes older than version
 v2.6.18, or changes older than 3 weeks, you can use revision
 range specifiers  similar to 'git-rev-list':
 
@@ -129,7 +129,7 @@ commit v2.6.18 or the most recent commit that is more than 3
 weeks old in the above example) are blamed for that range
 boundary commit.
 
-A particularly useful way is to see if an added file have lines
+A particularly useful way is to see if an added file has lines
 created by copy-and-paste from existing files.  Sometimes this
 indicates that the developer was being sloppy and did not
 refactor the code properly.  You can first find the commit that
@@ -162,26 +162,32 @@ annotated.
 +
 Line numbers count from 1.
 
-. The first time that commit shows up in the stream, it has various
+. The first time that commit shows up in the stream, it has various
   other information about it printed out with a one-word tag at the
-  beginning of each line about that "extended commit info" (author,
-  email, committer, dates, summary etc).
+  beginning of each line describing the extra commit information (author,
+  email, committer, dates, summary, etc.).
 
-. Unlike Porcelain format, the filename information is always
+. Unlike the Porcelain format, the filename information is always
   given and terminates the entry:
 
        "filename" <whitespace-quoted-filename-goes-here>
 +
-and thus it's really quite easy to parse for some line- and word-oriented
+and thus it is really quite easy to parse for some line- and word-oriented
 parser (which should be quite natural for most scripting languages).
 +
 [NOTE]
 For people who do parsing: to make it more robust, just ignore any
-lines in between the first and last one ("<sha1>" and "filename" lines)
-where you don't recognize the tag-words (or care about that particular
+lines between the first and last one ("<sha1>" and "filename" lines)
+where you do not recognize the tag words (or care about that particular
 one) at the beginning of the "extended information" lines. That way, if
 there is ever added information (like the commit encoding or extended
-commit commentary), a blame viewer won't ever care.
+commit commentary), a blame viewer will not care.
+
+
+MAPPING AUTHORS
+---------------
+
+include::mailmap.txt[]
 
 
 SEE ALSO
index 6103d62fe3dca23c78b16dbdbb5ba231a6b39bf7..cbd427587188d81726445183f772f02982736e6a 100644 (file)
@@ -18,19 +18,19 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-With no arguments, existing branches are listed, the current branch will
+With no arguments, existing branches are listed and the current branch will
 be highlighted with an asterisk.  Option `-r` causes the remote-tracking
 branches to be listed, and option `-a` shows both.
 
-With `--contains`, shows only the branches that contains the named commit
-(in other words, the branches whose tip commits are descendant of the
+With `--contains`, shows only the branches that contain the named commit
+(in other words, the branches whose tip commits are descendants of the
 named commit).  With `--merged`, only branches merged into the named
 commit (i.e. the branches whose tip commits are reachable from the named
 commit) will be listed.  With `--no-merged` only branches not merged into
-the named commit will be listed.  Missing <commit> argument defaults to
-'HEAD' (i.e. the tip of the current branch).
+the named commit will be listed.  If the <commit> argument is missing it
+defaults to 'HEAD' (i.e. the tip of the current branch).
 
-In its second form, a new branch named <branchname> will be created.
+In the command's second form, a new branch named <branchname> will be created.
 It will start out with a head equal to the one given as <start-point>.
 If no <start-point> is given, the branch will be created with a head
 equal to that of the currently checked out branch.
@@ -57,9 +57,9 @@ has a reflog then the reflog will also be deleted.
 
 Use -r together with -d to delete remote-tracking branches. Note, that it
 only makes sense to delete remote-tracking branches if they no longer exist
-in remote repository or if 'git-fetch' was configured not to fetch
-them again. See also 'prune' subcommand of linkgit:git-remote[1] for way to
-clean up all obsolete remote-tracking branches.
+in the remote repository or if 'git-fetch' was configured not to fetch
+them again. See also the 'prune' subcommand of linkgit:git-remote[1] for a
+way to clean up all obsolete remote-tracking branches.
 
 
 OPTIONS
@@ -76,14 +76,14 @@ OPTIONS
        based sha1 expressions such as "<branchname>@\{yesterday}".
 
 -f::
-       Force the creation of a new branch even if it means deleting
-       a branch that already exists with the same name.
+       Reset <branchname> to <startpoint> if <branchname> exists
+       already. Without `-f` 'git-branch' refuses to change an existing branch.
 
 -m::
        Move/rename a branch and the corresponding reflog.
 
 -M::
-       Move/rename a branch even if the new branchname already exists.
+       Move/rename a branch even if the new branch name already exists.
 
 --color::
        Color branches to highlight current, local, and remote branches.
@@ -100,29 +100,34 @@ OPTIONS
 
 -v::
 --verbose::
-       Show sha1 and commit subject line for each head.
+       Show sha1 and commit subject line for each head, along with
+       relationship to upstream branch (if any). If given twice, print
+       the name of the upstream branch, as well.
 
 --abbrev=<length>::
-       Alter minimum display length for sha1 in output listing,
-       default value is 7.
+       Alter the sha1's minimum display length in the output listing.
+       The default value is 7.
 
 --no-abbrev::
-       Display the full sha1s in output listing rather than abbreviating them.
+       Display the full sha1s in the output listing rather than abbreviating them.
 
 --track::
-       When creating a new branch, set up configuration so that 'git-pull'
-       will automatically retrieve data from the start point, which must be
-       a branch. Use this if you always pull from the same upstream branch
-       into the new branch, and if you don't want to use "git pull
-       <repository> <refspec>" explicitly. This behavior is the default
-       when the start point is a remote branch. Set the
-       branch.autosetupmerge configuration variable to `false` if you want
-       'git-checkout' and 'git-branch' to always behave as if '--no-track' were
-       given. Set it to `always` if you want this behavior when the
-       start-point is either a local or remote branch.
+       When creating a new branch, set up configuration to mark the
+       start-point branch as "upstream" from the new branch. This
+       configuration will tell git to show the relationship between the
+       two branches in `git status` and `git branch -v`. Furthermore,
+       it directs `git pull` without arguments to pull from the
+       upstream when the new branch is checked out.
++
+This behavior is the default when the start point is a remote branch.
+Set the branch.autosetupmerge configuration variable to `false` if you
+want `git checkout` and `git branch` to always behave as if '--no-track'
+were given. Set it to `always` if you want this behavior when the
+start-point is either a local or remote branch.
 
 --no-track::
-       Ignore the branch.autosetupmerge configuration variable.
+       Do not set up "upstream" configuration, even if the
+       branch.autosetupmerge configuration variable is true.
 
 --contains <commit>::
        Only list branches which contain the specified commit.
@@ -149,13 +154,13 @@ OPTIONS
 
 <newbranch>::
        The new name for an existing branch. The same restrictions as for
-       <branchname> applies.
+       <branchname> apply.
 
 
 Examples
 --------
 
-Start development off of a known tag::
+Start development from a known tag::
 +
 ------------
 $ git clone git://git.kernel.org/pub/scm/.../linux-2.6 my2.6
@@ -167,7 +172,7 @@ $ git checkout my2.6.14
 <1> This step and the next one could be combined into a single step with
 "checkout -b my2.6.14 v2.6.14".
 
-Delete unneeded branch::
+Delete an unneeded branch::
 +
 ------------
 $ git clone git://git.kernel.org/.../git.git my.git
@@ -176,21 +181,21 @@ $ git branch -d -r origin/todo origin/html origin/man   <1>
 $ git branch -D test                                    <2>
 ------------
 +
-<1> Delete remote-tracking branches "todo", "html", "man". Next 'fetch' or
-'pull' will create them again unless you configure them not to. See
-linkgit:git-fetch[1].
-<2> Delete "test" branch even if the "master" branch (or whichever branch is
-currently checked out) does not have all commits from test branch.
+<1> Delete the remote-tracking branches "todo", "html" and "man". The next
+'fetch' or 'pull' will create them again unless you configure them not to.
+See linkgit:git-fetch[1].
+<2> Delete the "test" branch even if the "master" branch (or whichever branch
+is currently checked out) does not have all commits from the test branch.
 
 
 Notes
 -----
 
-If you are creating a branch that you want to immediately checkout, it's
+If you are creating a branch that you want to checkout immediately, it is
 easier to use the git checkout command with its `-b` option to create
 a branch and check it out with a single command.
 
-The options `--contains`, `--merged` and `--no-merged` serves three related
+The options `--contains`, `--merged` and `--no-merged` serve three related
 but different purposes:
 
 - `--contains <commit>` is used to find all branches which will need
index 1b66ab743c64d980a43a028d57ca2f6505d97845..aee7e4a8c9396225e6d82d6a85618128c4170ce0 100644 (file)
@@ -19,13 +19,13 @@ DESCRIPTION
 
 Some workflows require that one or more branches of development on one
 machine be replicated on another machine, but the two machines cannot
-be directly connected so the interactive git protocols (git, ssh,
-rsync, http) cannot be used.  This command provides support for
+be directly connected, and therefore the interactive git protocols (git,
+ssh, rsync, http) cannot be used.  This command provides support for
 'git-fetch' and 'git-pull' to operate by packaging objects and references
 in an archive at the originating machine, then importing those into
 another repository using 'git-fetch' and 'git-pull'
 after moving the archive by some means (i.e., by sneakernet).  As no
-direct connection between repositories exists, the user must specify a
+direct connection between the repositories exists, the user must specify a
 basis for the bundle that is held by the destination repository: the
 bundle assumes that all objects in the basis are already in the
 destination repository.
@@ -43,7 +43,7 @@ verify <file>::
        bundle format itself as well as checking that the prerequisite
        commits exist and are fully linked in the current repository.
        'git-bundle' prints a list of missing commits, if any, and exits
-       with non-zero status.
+       with non-zero status.
 
 list-heads <file>::
        Lists the references defined in the bundle.  If followed by a
@@ -53,14 +53,14 @@ list-heads <file>::
 unbundle <file>::
        Passes the objects in the bundle to 'git-index-pack'
        for storage in the repository, then prints the names of all
-       defined references. If a reflist is given, only references
-       matching those in the given list are printed. This command is
+       defined references. If a list of references is given, only
+       references matching those in the list are printed. This command is
        really plumbing, intended to be called only by 'git-fetch'.
 
 [git-rev-list-args...]::
        A list of arguments, acceptable to 'git-rev-parse' and
-       'git-rev-list', that specify the specific objects and references
-       to transport.  For example, "master~10..master" causes the
+       'git-rev-list', that specifies the specific objects and references
+       to transport.  For example, `master\~10..master` causes the
        current master reference to be packaged along with all objects
        added since its 10th ancestor commit.  There is no explicit
        limit to the number of references and objects that may be
@@ -71,98 +71,134 @@ unbundle <file>::
        A list of references used to limit the references reported as
        available. This is principally of use to 'git-fetch', which
        expects to receive only those references asked for and not
-       necessarily everything in the pack (in this case, 'git-bundle' is
-       acting like 'git-fetch-pack').
+       necessarily everything in the pack (in this case, 'git-bundle' acts
+       like 'git-fetch-pack').
 
 SPECIFYING REFERENCES
 ---------------------
 
 'git-bundle' will only package references that are shown by
 'git-show-ref': this includes heads, tags, and remote heads.  References
-such as master~1 cannot be packaged, but are perfectly suitable for
+such as `master\~1` cannot be packaged, but are perfectly suitable for
 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).
+specified explicitly (e.g. `^master\~10`), or implicitly (e.g.
+`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
-to contain objects already in the destination as these are ignored
+It is okay to err on the side of caution, causing the bundle file
+to contain objects already in the destination, as these are ignored
 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.
+but we can move data from A to B via some mechanism (CD, email, etc.).
+We want to update R2 with development made on the 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 does not have
+any basis. You can use a tag to remember up to what commit you last
+processed, in order to make it easy to later update the other repository
+with an 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 transfer file.bundle to the target machine B. If you are creating
+the repository on machine B, then you can clone from the bundle as if it
+were a remote repository instead of creating an empty repository and then
+pulling or fetching objects from the bundle:
 
-- 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. The $GIT_DIR/config file in R2 will
+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/*
+------------------------
+
+To update the resulting mine.git repository, you can fetch or pull after
+replacing the bundle stored at /home/me/tmp/file.bundle with incremental
+updates.
+
+After working some more in the original repository, you can create an
+incremental bundle to update the other repository:
+
+----------------
+machineA$ cd R1
+machineA$ git bundle create file.bundle lastR2bundle..master
+machineA$ git tag -f lastR2bundle master
+----------------
+
+You then transfer the bundle 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, 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 any other options that 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:
+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 do not 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 which it fetches or pulls from. You can, for example, map
+references 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
-------------
+You can also see what references it offers.
 
-would treat it as if it is talking with a remote side over the
-network.
+----------------
+$ git ls-remote mybundle
+----------------
 
 Author
 ------
index 668f697c2a03c29f589e249de832ddf01b9b1e6d..58c8d65772af4ef20ad573af9dd28691f5357437 100644 (file)
@@ -3,30 +3,30 @@ git-cat-file(1)
 
 NAME
 ----
-git-cat-file - Provide content or type/size information for repository objects
+git-cat-file - Provide content or type and size information for repository objects
 
 
 SYNOPSIS
 --------
 [verse]
-'git cat-file' [-t | -s | -e | -p | <type>] <object>
-'git cat-file' [--batch | --batch-check] < <list-of-objects>
+'git cat-file' (-t | -s | -e | -p | <type>) <object>
+'git cat-file' (--batch | --batch-check) < <list-of-objects>
 
 DESCRIPTION
 -----------
-In the first form, provides content or type of objects in the repository. The
-type is required unless '-t' or '-p' is used to find the object type, or '-s'
-is used to find the object size.
+In its first form, the command provides the content or the type of an object in
+the repository. The type is required unless '-t' or '-p' is used to find the
+object type, or '-s' is used to find the object size.
 
-In the second form, a list of object (separated by LFs) is provided on stdin,
-and the SHA1, type, and size of each object is printed on stdout.
+In the second form, a list of objects (separated by linefeeds) is provided on
+stdin, and the SHA1, type, and size of each object is printed on stdout.
 
 OPTIONS
 -------
 <object>::
        The name of the object to show.
        For a more complete list of ways to spell object names, see
-       "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
+       the "SPECIFYING REVISIONS" section in linkgit:git-rev-parse[1].
 
 -t::
        Instead of the content, show the object type identified by
@@ -56,8 +56,8 @@ OPTIONS
        stdin. May not be combined with any other options or arguments.
 
 --batch-check::
-       Print the SHA1, type, and size of each object provided on stdin. May not be
-       combined with any other options or arguments.
+       Print the SHA1, type, and size of each object provided on stdin. May not
+       be combined with any other options or arguments.
 
 OUTPUT
 ------
index 8c2ac12f5d316a5db8c5a9be2c4d9639ca30653b..50824e3a2d7d00370d7311088349da84ee23b728 100644 (file)
@@ -14,7 +14,7 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-For every pathname, this command will list if each attr is 'unspecified',
+For every pathname, this command will list if each attribute is 'unspecified',
 'set', or 'unset' as a gitattribute on that pathname.
 
 OPTIONS
@@ -23,11 +23,11 @@ OPTIONS
        Read file names from stdin instead of from the command-line.
 
 -z::
-       Only meaningful with `--stdin`; paths are separated with
-       NUL character instead of LF.
+       Only meaningful with `--stdin`; paths are separated with a
+       NUL character instead of a linefeed character.
 
 \--::
-       Interpret all preceding arguments as attributes, and all following
+       Interpret all preceding arguments as attributes and all following
        arguments as path names. If not supplied, only the first argument will
        be treated as an attribute.
 
@@ -37,12 +37,12 @@ OUTPUT
 The output is of the form:
 <path> COLON SP <attribute> COLON SP <info> LF
 
-Where <path> is the path of a file being queried, <attribute> is an attribute
+<path> is the path of a file being queried, <attribute> is an attribute
 being queried and <info> can be either:
 
 'unspecified';; when the attribute is not defined for the path.
-'unset';;      when the attribute is defined to false.
-'set';;                when the attribute is defined to true.
+'unset';;      when the attribute is defined as false.
+'set';;                when the attribute is defined as true.
 <value>;;      when a value has been assigned to the attribute.
 
 EXAMPLES
@@ -69,7 +69,7 @@ org/example/MyClass.java: diff: java
 org/example/MyClass.java: myAttr: set
 ---------------
 
-* Listing attribute for multiple files:
+* Listing an attribute for multiple files:
 ---------------
 $ git check-attr myAttr -- org/example/MyClass.java org/example/NoMyAttr.java
 org/example/MyClass.java: myAttr: set
index 034223cc5ace81dd0b63da44d79db5e83a1d492a..c1ce26884e73f1599602472ba5032988486cc465 100644 (file)
@@ -3,52 +3,70 @@ git-check-ref-format(1)
 
 NAME
 ----
-git-check-ref-format - Make sure ref name is well formed
+git-check-ref-format - Ensures that a reference name is well formed
 
 SYNOPSIS
 --------
+[verse]
 'git check-ref-format' <refname>
+'git check-ref-format' [--branch] <branchname-shorthand>
 
 DESCRIPTION
 -----------
-Checks if a given 'refname' is acceptable, and exits non-zero if
-it is not.
+Checks if a given 'refname' is acceptable, and exits with a non-zero
+status if it is not.
 
 A reference is used in git to specify branches and tags.  A
-branch head is stored under `$GIT_DIR/refs/heads` directory, and
-a tag is stored under `$GIT_DIR/refs/tags` directory.  git
-imposes the following rules on how refs are named:
+branch head is stored under the `$GIT_DIR/refs/heads` directory, and
+a tag is stored under the `$GIT_DIR/refs/tags` directory.  git
+imposes the following rules on how references are named:
 
-. It can include slash `/` for hierarchical (directory)
+. They can include slash `/` for hierarchical (directory)
   grouping, but no slash-separated component can begin with a
-  dot `.`;
+  dot `.`.
 
-. It cannot have two consecutive dots `..` anywhere;
+. They cannot have two consecutive dots `..` anywhere.
 
-. It cannot have ASCII control character (i.e. bytes whose
+. They cannot have ASCII control characters (i.e. bytes whose
   values are lower than \040, or \177 `DEL`), space, tilde `~`,
   caret `{caret}`, colon `:`, question-mark `?`, asterisk `*`,
-  or open bracket `[` anywhere;
+  or open bracket `[` anywhere.
 
-. It cannot end with a slash `/`.
+. They cannot end with a slash `/` nor a dot `.`.
 
-These rules makes it easy for shell script based tools to parse
-refnames, pathname expansion by the shell when a refname is used
+. They cannot end with the sequence `.lock`.
+
+. They cannot contain a sequence `@{`.
+
+These rules make it easy for shell script based tools to parse
+reference names, pathname expansion by the shell when a reference name is used
 unquoted (by mistake), and also avoids ambiguities in certain
-refname expressions (see linkgit:git-rev-parse[1]).  Namely:
+reference name expressions (see linkgit:git-rev-parse[1]):
 
-. double-dot `..` are often used as in `ref1..ref2`, and in some
-  context this notation means `{caret}ref1 ref2` (i.e. not in
-  ref1 and in ref2).
+. A double-dot `..` is often used as in `ref1..ref2`, and in some
+  contexts this notation means `{caret}ref1 ref2` (i.e. not in
+  `ref1` and in `ref2`).
 
-. tilde `~` and caret `{caret}` are used to introduce postfix
+. A tilde `~` and caret `{caret}` are used to introduce the postfix
   'nth parent' and 'peel onion' operation.
 
-. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
+. colon `:` is used as in `srcref:dstref` to mean "use srcref\'s
   value and store it in dstref" in fetch and push operations.
   It may also be used to select a specific object such as with
   'git-cat-file': "git cat-file blob v1.3.3:refs.c".
 
+. at-open-brace `@{` is used as a notation to access a reflog entry.
+
+With the `--branch` option, it expands a branch name shorthand and
+prints the name of the branch the shorthand refers to.
+
+EXAMPLE
+-------
+
+git check-ref-format --branch @{-1}::
+
+Print the name of the previous branch.
+
 
 GIT
 ---
index 5ece6cc8053d883b3f47a41c4b87ab2d46248267..ad4b31e89218e857001fce69f32acbe85fc7539c 100644 (file)
@@ -8,28 +8,28 @@ git-checkout - Checkout a branch or paths to the working tree
 SYNOPSIS
 --------
 [verse]
-'git checkout' [-q] [-f] [--track | --no-track] [-b <new_branch> [-l]] [-m] [<branch>]
+'git checkout' [-q] [-f] [-m] [<branch>]
+'git checkout' [-q] [-f] [-m] [-b <new_branch>] [<start_point>]
 'git checkout' [-f|--ours|--theirs|-m|--conflict=<style>] [<tree-ish>] [--] <paths>...
 
 DESCRIPTION
 -----------
 
 When <paths> are not given, this command switches branches by
-updating the index and working tree to reflect the specified
-branch, <branch>, and updating HEAD to be <branch> or, if
-specified, <new_branch>.  Using -b will cause <new_branch> to
-be created; in this case you can use the --track or --no-track
-options, which will be passed to `git branch`.
+updating the index, working tree, and HEAD to reflect the specified
+branch.
 
-As a convenience, --track will default to create a branch whose
-name is constructed from the specified branch name by stripping
-the first namespace level.
+If `-b` is given, a new branch is created and checked out, as if
+linkgit:git-branch[1] were called; in this case you can
+use the --track or --no-track options, which will be passed to `git
+branch`.  As a convenience, --track without `-b` implies branch
+creation; see the description of --track below.
 
 When <paths> are given, this command does *not* switch
 branches.  It updates the named paths in the working tree from
 the index file, or from a named <tree-ish> (most often a commit).  In
-this case, the `-b` options is meaningless and giving
-either of them results in an error.  <tree-ish> argument can be
+this case, the `-b` and `--track` options are meaningless and giving
+either of them results in an error. The <tree-ish> argument can be
 used to specify a specific tree-ish (i.e. commit, tag or tree)
 to update the index for the given paths before updating the
 working tree.
@@ -62,27 +62,16 @@ entries; instead, unmerged entries are ignored.
 
 -b::
        Create a new branch named <new_branch> and start it at
-       <branch>.  The new branch name must pass all checks defined
-       by linkgit:git-check-ref-format[1].  Some of these checks
-       may restrict the characters allowed in a branch name.
+       <start_point>; see linkgit:git-branch[1] for details.
 
 -t::
 --track::
-       When creating a new branch, set up configuration so that 'git-pull'
-       will automatically retrieve data from the start point, which must be
-       a branch. Use this if you always pull from the same upstream branch
-       into the new branch, and if you don't want to use "git pull
-       <repository> <refspec>" explicitly. This behavior is the default
-       when the start point is a remote branch. Set the
-       branch.autosetupmerge configuration variable to `false` if you want
-       'git-checkout' and 'git-branch' to always behave as if '--no-track' were
-       given. Set it to `always` if you want this behavior when the
-       start-point is either a local or remote branch.
+       When creating a new branch, set up "upstream" configuration. See
+       "--track" in linkgit:git-branch[1] for details.
 +
-If no '-b' option was given, the name of the new branch will be
-derived from the remote branch, by attempting to guess the name
-of the branch on remote system.  If "remotes/" or "refs/remotes/"
-are prefixed, it is stripped away, and then the part up to the
+If no '-b' option is given, the name of the new branch will be
+derived from the remote branch.  If "remotes/" or "refs/remotes/"
+is prefixed it is stripped away, and then the part up to the
 next slash (which would be the nickname of the remote) is removed.
 This would tell us to use "hack" as the local branch when branching
 off of "origin/hack" (or "remotes/origin/hack", or even
@@ -91,12 +80,12 @@ guessing results in an empty name, the guessing is aborted.  You can
 explicitly give a name with '-b' in such a case.
 
 --no-track::
-       Ignore the branch.autosetupmerge configuration variable.
+       Do not set up "upstream" configuration, even if the
+       branch.autosetupmerge configuration variable is true.
 
 -l::
-       Create the new branch's reflog.  This activates recording of
-       all changes made to the branch ref, enabling use of date
-       based sha1 expressions such as "<branchname>@\{yesterday}".
+       Create the new branch's reflog; see linkgit:git-branch[1] for
+       details.
 
 -m::
 --merge::
@@ -124,19 +113,28 @@ the conflicted merge in the specified paths.
        "merge" (default) and "diff3" (in addition to what is shown by
        "merge" style, shows the original contents).
 
+<branch>::
+       Branch to checkout; if it refers to a branch (i.e., a name that,
+       when prepended with "refs/heads/", is a valid ref), then that
+       branch is checked out. Otherwise, if it refers to a valid
+       commit, your HEAD becomes "detached" and you are no longer on
+       any branch (see below for details).
++
+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\}"`.
+
 <new_branch>::
        Name for the new branch.
 
+<start_point>::
+       The name of a commit at which to start the new branch; see
+       linkgit:git-branch[1] for details. Defaults to HEAD.
+
 <tree-ish>::
        Tree to checkout from (when paths are given). If not specified,
        the index will be used.
 
-<branch>::
-       Branch to checkout (when no paths are given); may be any object
-       ID that resolves to a commit.  Defaults to HEAD.
-+
-When this parameter names a non-branch (but still a valid commit object),
-your HEAD becomes 'detached'.
 
 
 Detached HEAD
@@ -152,12 +150,12 @@ $ git checkout v2.6.18
 ------------
 
 Earlier versions of git did not allow this and asked you to
-create a temporary branch using `-b` option, but starting from
+create a temporary branch using the `-b` option, but starting from
 version 1.5.0, the above command 'detaches' your HEAD from the
-current branch and directly point at the commit named by the tag
-(`v2.6.18` in the above example).
+current branch and directly points at the commit named by the tag
+(`v2.6.18` in the example above).
 
-You can use usual git commands while in this state.  You can use
+You can use all git commands while in this state.  You can use
 `git reset --hard $othercommit` to further move around, for
 example.  You can make changes and create a new commit on top of
 a detached HEAD.  You can even create a merge by using `git
@@ -202,7 +200,7 @@ You should instead write:
 $ git checkout -- hello.c
 ------------
 
-. After working in a wrong branch, switching to the correct
+. After working in the wrong branch, switching to the correct
 branch would be done using:
 +
 ------------
@@ -210,7 +208,7 @@ $ git checkout mytopic
 ------------
 +
 However, your "wrong" branch and correct "mytopic" branch may
-differ in files that you have locally modified, in which case,
+differ in files that you have modified locally, in which case
 the above checkout would fail like this:
 +
 ------------
index 74d14c4e7fc88e702e4781b22eb8ed5ce0480c6f..7deefdae8f995d843971f6beff24af8325b99f1f 100644 (file)
@@ -7,7 +7,7 @@ git-cherry - Find commits not merged upstream
 
 SYNOPSIS
 --------
-'git cherry' [-v] <upstream> [<head>] [<limit>]
+'git cherry' [-v] [<upstream> [<head> [<limit>]]]
 
 DESCRIPTION
 -----------
@@ -51,6 +51,7 @@ OPTIONS
 
 <upstream>::
        Upstream branch to compare against.
+       Defaults to the first tracked remote branch, if available.
 
 <head>::
        Working branch; defaults to HEAD.
index 8a114509f4a19b4fa6c6d8de8afdc94938d24b82..be894af39ff6559affbf0617e4c5d8fe68c33fe8 100644 (file)
@@ -12,14 +12,17 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-Removes files unknown to git.  This allows to clean the working tree
-from files that are not under version control.  If the '-x' option is
-specified, ignored files are also removed, allowing to remove all
-build products.
+
+Cleans the working tree by recursively removing files that are not
+under version control, starting from the current directory.
+
+Normally, only files unknown to git are removed, but if the '-x'
+option is specified, ignored files are also removed. This can, for
+example, be useful to remove all build products.
+
 If any optional `<path>...` arguments are given, only those paths
 are affected.
 
-
 OPTIONS
 -------
 -d::
index 4072f40d7ae5417c51d1cddcf587b674aa1dc0e5..b14de6c407b8bd0bc001c608ca4f26fc619abf3e 100644 (file)
@@ -149,7 +149,7 @@ then the cloned repository will become corrupt.
        part of the source repository is used if no directory is
        explicitly given ("repo" for "/path/to/repo.git" and "foo"
        for "host.xz:foo/.git").  Cloning into an existing directory
-       is not allowed.
+       is only allowed if the directory is empty.
 
 :git-clone: 1
 include::urls.txt[]
index 19a8917b83f4ff111e9eec0767c3261a2c39d995..f68b198205d3a4d808ce58e6b0ac03e70bb160a4 100644 (file)
@@ -11,7 +11,7 @@ SYNOPSIS
 [verse]
 'git config' [<file-option>] [type] [-z|--null] name [value [value_regex]]
 'git config' [<file-option>] [type] --add name value
-'git config' [<file-option>] [type] --replace-all name [value [value_regex]]
+'git config' [<file-option>] [type] --replace-all name value [value_regex]
 'git config' [<file-option>] [type] [-z|--null] --get name [value_regex]
 'git config' [<file-option>] [type] [-z|--null] --get-all name [value_regex]
 'git config' [<file-option>] [type] [-z|--null] --get-regexp name_regex [value_regex]
@@ -22,6 +22,7 @@ SYNOPSIS
 'git config' [<file-option>] [-z|--null] -l | --list
 'git config' [<file-option>] --get-color name [default]
 'git config' [<file-option>] --get-colorbool name [stdout-is-tty]
+'git config' [<file-option>] -e | --edit
 
 DESCRIPTION
 -----------
@@ -68,7 +69,8 @@ OPTIONS
 
 --add::
        Adds a new line to the option without altering any existing
-       values.  This is the same as providing '^$' as the value_regex.
+       values.  This is the same as providing '^$' as the value_regex
+       in `--replace-all`.
 
 --get::
        Get the value for a given key (optionally filtered by a regex
@@ -130,6 +132,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
@@ -150,13 +156,18 @@ See also <<FILES>>.
        When the color setting for `name` is undefined, the command uses
        `color.ui` as fallback.
 
---get-color name default::
+--get-color name [default]::
 
        Find the color configured for `name` (e.g. `color.diff.new`) and
        output it as the ANSI color escape sequence to the standard
        output.  The optional `default` parameter is used instead, if
        there is no color configured for `name`.
 
+-e::
+--edit::
+       Opens an editor to modify the specified config file; either
+       '--system', '--global', or repository (default).
+
 [[FILES]]
 FILES
 -----
index b7a8c10b8709108c1c8a0d14f661c179c2b4f22c..614e769f4e5351d2820ebb9bf0dd1ae519c965ba 100644 (file)
@@ -24,6 +24,9 @@ repository, or incrementally import into an existing one.
 Splitting the CVS log into patch sets is done by 'cvsps'.
 At least version 2.1 is required.
 
+*WARNING:* for certain situations the import leads to incorrect results.
+Please see the section <<issues,ISSUES>> for further reference.
+
 You should *never* do any work of your own on the branches that are
 created by 'git-cvsimport'.  By default initial import will create and populate a
 "master" branch from the CVS repository's main branch which you're free
@@ -62,7 +65,7 @@ OPTIONS
 -r <remote>::
        The git remote to import this CVS repository into.
        Moves all CVS branches into remotes/<remote>/<branch>
-       akin to the 'git-clone' "--use-separate-remote" option.
+       akin to the way 'git-clone' uses 'origin' by default.
 
 -o <branch-for-HEAD>::
        When no remote is specified (via -r) the 'HEAD' branch
@@ -164,6 +167,39 @@ If '-v' is specified, the script reports what it is doing.
 Otherwise, success is indicated the Unix way, i.e. by simply exiting with
 a zero exit status.
 
+[[issues]]
+ISSUES
+------
+Problems related to timestamps:
+
+ * If timestamps of commits in the cvs repository are not stable enough
+   to be used for ordering commits changes may show up in the wrong
+   order.
+ * If any files were ever "cvs import"ed more than once (e.g., import of
+   more than one vendor release) the HEAD contains the wrong content.
+ * If the timestamp order of different files cross the revision order
+   within the commit matching time window the order of commits may be
+   wrong.
+
+Problems related to branches:
+
+ * Branches on which no commits have been made are not imported.
+ * All files from the branching point are added to a branch even if
+   never added in cvs.
+ * This applies to files added to the source branch *after* a daughter
+   branch was created: if previously no commit was made on the daughter
+   branch they will erroneously be added to the daughter branch in git.
+
+Problems related to tags:
+
+* Multiple tags on the same revision are not imported.
+
+If you suspect that any of these issues may apply to the repository you
+want to import consider using these alternative tools which proved to be
+more stable in practice:
+
+* cvs2git (part of cvs2svn), `http://cvs2svn.tigris.org`
+* parsecvs, `http://cgit.freedesktop.org/~keithp/parsecvs`
 
 Author
 ------
index d5596672a5493062afbbd3b57f94a3673f9de659..a85121c689e394e86f7c97025b92ffa03fca0df9 100644 (file)
@@ -100,7 +100,7 @@ OPTIONS
        it takes for the server to process the sub-request and the time spent
        waiting for the next client's request.
 
---max-connections::
+--max-connections=n::
        Maximum number of concurrent clients, defaults to 32.  Set it to
        zero for no limit.
 
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.
diff --git a/Documentation/git-difftool.txt b/Documentation/git-difftool.txt
new file mode 100644 (file)
index 0000000..15b247b
--- /dev/null
@@ -0,0 +1,105 @@
+git-difftool(1)
+===============
+
+NAME
+----
+git-difftool - Show changes using common diff tools
+
+SYNOPSIS
+--------
+'git difftool' [--tool=<tool>] [-y|--no-prompt|--prompt] [<'git diff' options>]
+
+DESCRIPTION
+-----------
+'git-difftool' is a git command that allows you to compare and edit files
+between revisions using common diff tools.  'git difftool' is a frontend
+to 'git-diff' and accepts the same options and arguments.
+
+OPTIONS
+-------
+-y::
+--no-prompt::
+       Do not prompt before launching a diff tool.
+
+--prompt::
+       Prompt before each invocation of the diff tool.
+       This is the default behaviour; the option is provided to
+       override any configuration settings.
+
+-t <tool>::
+--tool=<tool>::
+       Use the diff tool specified by <tool>.
+       Valid merge tools are:
+       kdiff3, kompare, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff,
+       ecmerge, diffuse and opendiff
++
+If a diff tool is not specified, 'git-difftool'
+will use the configuration variable `diff.tool`.  If the
+configuration variable `diff.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 `difftool.<tool>.path`. For example, you
+can configure the absolute path to kdiff3 by setting
+`difftool.kdiff3.path`. Otherwise, 'git-difftool' assumes the
+tool is available in PATH.
++
+Instead of running one of the known diff tools,
+'git-difftool' can be customized to run an alternative program
+by specifying the command line to invoke in a configuration
+variable `difftool.<tool>.cmd`.
++
+When 'git-difftool' is invoked with this tool (either through the
+`-t` or `--tool` option or the `diff.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`.
+
+See linkgit:git-diff[1] for the full list of supported options.
+
+CONFIG VARIABLES
+----------------
+'git-difftool' falls back to 'git-mergetool' config variables when the
+difftool equivalents have not been defined.
+
+diff.tool::
+       The default diff tool to use.
+
+difftool.<tool>.path::
+       Override the path for the given tool.  This is useful in case
+       your tool is not in the PATH.
+
+difftool.<tool>.cmd::
+       Specify the command to invoke the specified diff tool.
++
+See the `--tool=<tool>` option above for more details.
+
+difftool.prompt::
+       Prompt before each invocation of the diff tool.
+
+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 68f97cd5aee2dc3d4bc1c3e507cf373bee8c1922..ab527b5b316a81eddd8a0eefb501448a3097182f 100644 (file)
@@ -94,7 +94,9 @@ OPTIONS
 --index-filter <command>::
        This is the filter for rewriting the index.  It is similar to the
        tree filter but does not check out the tree, which makes it much
-       faster.  For hairy cases, see linkgit:git-update-index[1].
+       faster.  Frequently used with `git rm \--cached
+       \--ignore-unmatch ...`, see EXAMPLES below.  For hairy
+       cases, see linkgit:git-update-index[1].
 
 --parent-filter <command>::
        This is the filter for rewriting the commit's parent list.
@@ -125,6 +127,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,
@@ -154,6 +160,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'.
@@ -193,10 +209,14 @@ However, if the file is absent from the tree of some commit,
 a simple `rm filename` will fail for that tree and commit.
 Thus you may instead want to use `rm -f filename` as the script.
 
-A significantly faster version:
+Using `\--index-filter` with 'git-rm' yields a significantly faster
+version.  Like with using `rm filename`, `git rm --cached filename`
+will fail if the file is absent from the tree of a commit.  If you
+want to "completely forget" a file, it does not matter when it entered
+history, so we also add `\--ignore-unmatch`:
 
 --------------------------------------------------------------------------
-git filter-branch --index-filter 'git rm --cached filename' HEAD
+git filter-branch --index-filter 'git rm --cached --ignore-unmatch filename' HEAD
 --------------------------------------------------------------------------
 
 Now, you will get the rewritten history saved in HEAD.
@@ -323,6 +343,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 5061d3e4e7b8a888093c5e8c7b4cb03509391756..8dc873fd4465879ec3224732c690f1173a6e2dc2 100644 (file)
@@ -75,6 +75,8 @@ For all objects, the following names can be used:
 refname::
        The name of the ref (the part after $GIT_DIR/).
        For a non-ambiguous short name of the ref append `:short`.
+       The option core.warnAmbiguousRefs is used to select the strict
+       abbreviation mode.
 
 objecttype::
        The type of the object (`blob`, `tree`, `commit`, `tag`).
@@ -85,6 +87,11 @@ objectsize::
 objectname::
        The object name (aka SHA-1).
 
+upstream::
+       The name of a local ref which can be considered ``upstream''
+       from the displayed ref. Respects `:short` in the same way as
+       `refname` above.
+
 In addition to the above, for commit and tag objects, the header
 field names (`tree`, `parent`, `object`, `type`, and `tag`) can
 be used to specify the value in the header field.
index 1f577b8016156871debcbb17fc32af9f7a6082f4..6f1fc80119600c419d2e3b98bac0c44f9cb09026 100644 (file)
@@ -9,9 +9,10 @@ git-format-patch - Prepare patches for e-mail submission
 SYNOPSIS
 --------
 [verse]
-'git format-patch' [-k] [-o <dir> | --stdout] [--thread]
-                  [--attach[=<boundary>] | --inline[=<boundary>]]
-                  [-s | --signoff] [<common diff options>]
+'git format-patch' [-k] [(-o|--output-directory) <dir> | --stdout]
+                  [--thread[=<style>]]
+                  [(--attach|--inline)[=<boundary>] | --no-attach]
+                  [-s | --signoff]
                   [-n | --numbered | -N | --no-numbered]
                   [--start-number <n>] [--numbered-files]
                   [--in-reply-to=Message-Id] [--suffix=.<sfx>]
@@ -19,6 +20,7 @@ SYNOPSIS
                   [--subject-prefix=Subject-Prefix]
                   [--cc=<email>]
                   [--cover-letter]
+                  [<common diff options>]
                   [ <since> | <revision range> ]
 
 DESCRIPTION
@@ -92,7 +94,6 @@ include::diff-options.txt[]
 --numbered-files::
        Output file names will be a simple number sequence
        without the default first line of the commit appended.
-       Mutually exclusive with the --stdout option.
 
 -k::
 --keep-subject::
@@ -113,15 +114,27 @@ include::diff-options.txt[]
        which is the commit message and the patch itself in the
        second part, with "Content-Disposition: attachment".
 
+--no-attach::
+       Disable the creation of an attachment, overriding the
+       configuration setting.
+
 --inline[=<boundary>]::
        Create multipart/mixed attachment, the first part of
        which is the commit message and the patch itself in the
        second part, with "Content-Disposition: inline".
 
---thread::
+--thread[=<style>]::
        Add In-Reply-To and References headers to make the second and
        subsequent mails appear as replies to the first.  Also generates
        the Message-Id header to reference.
++
+The optional <style> argument can be either `shallow` or `deep`.
+'shallow' threading makes every mail a reply to the head of the
+series, where the head is chosen from the cover letter, the
+`\--in-reply-to`, and the first patch mail, in this order.  'deep'
+threading makes every mail a reply to the previous one.  If not
+specified, defaults to the 'format.thread' configuration, or `shallow`
+if that is not set.
 
 --in-reply-to=Message-Id::
        Make the first mail (or all the mails with --no-thread) appear as a
@@ -145,6 +158,11 @@ include::diff-options.txt[]
        Add a "Cc:" header to the email headers. This is in addition
        to any configured headers, and may be used multiple times.
 
+--add-header=<header>::
+       Add an arbitrary header to the email headers.  This is in addition
+       to any configured headers, and may be used multiple times.
+       For example, --add-header="Organization: git-foo"
+
 --cover-letter::
        In addition to the patches, generate a cover letter file
        containing the shortlog and the overall diffstat.  You can
@@ -153,18 +171,17 @@ include::diff-options.txt[]
 --suffix=.<sfx>::
        Instead of using `.patch` as the suffix for generated
        filenames, use specified suffix.  A common alternative is
-       `--suffix=.txt`.
+       `--suffix=.txt`.  Leaving this empty will remove the `.patch`
+       suffix.
 +
-Note that you would need to include the leading dot `.` if you
-want a filename like `0001-description-of-my-change.patch`, and
-the first letter does not have to be a dot.  Leaving it empty would
-not add any suffix.
+Note that the leading character does not have to be a dot; for example,
+you can use `--suffix=-patch` to get `0001-description-of-my-change-patch`.
 
 --no-binary::
-       Don't output contents of changes in binary files, just take note
-       that they differ.  Note that this disable the patch to be properly
-       applied.  By default the contents of changes in those files are
-       encoded in the patch.
+       Do not output contents of changes in binary files, instead
+       display a notice that those files changed.  Patches generated
+       using this option cannot be applied properly, but they are
+       still useful for code review.
 
 --root::
        Treat the revision argument as a <revision range>, even if it
@@ -175,9 +192,10 @@ not add any suffix.
 
 CONFIGURATION
 -------------
-You can specify extra mail header lines to be added to each message
-in the repository configuration, new defaults for the subject prefix
-and file suffix, and number patches when outputting more than one.
+You can specify extra mail header lines to be added to each message,
+defaults for the subject prefix and file suffix, number patches when
+outputting more than one patch, add "Cc:" headers, configure attachments,
+and sign off patches with configuration variables.
 
 ------------
 [format]
@@ -186,6 +204,8 @@ and file suffix, and number patches when outputting more than one.
        suffix = .txt
        numbered = auto
        cc = <email>
+       attach [ = mime-boundary-string ]
+       signoff = true
 ------------
 
 
@@ -223,8 +243,8 @@ $ git format-patch -M -B origin
 +
 Additionally, it detects and handles renames and complete rewrites
 intelligently to produce a renaming patch.  A renaming patch reduces
-the amount of text output, and generally makes it easier to review it.
-Note that the "patch" program does not understand renaming patches, so
+the amount of text output, and generally makes it easier to review.
+Note that non-git "patch" programs won't understand renaming patches, so
 use it only when you know the recipient uses git to apply your patch.
 
 * Extract three topmost commits from the current branch and format them
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 553da6cbb1777a94cb3d6b48dc77c445e18ca2b0..fccb82deb48ec75d25f948e7caa69a44e09f5832 100644 (file)
@@ -17,6 +17,7 @@ SYNOPSIS
           [-l | --files-with-matches] [-L | --files-without-match]
           [-z | --null]
           [-c | --count] [--all-match]
+          [--color | --no-color]
           [-A <post-context>] [-B <pre-context>] [-C <context>]
           [-f <file>] [-e] <pattern>
           [--and|--or|--not|(|)|-e <pattern>...] [<tree>...]
@@ -105,6 +106,13 @@ OPTIONS
        Instead of showing every matched line, show the number of
        lines that match.
 
+--color::
+       Show colored matches.
+
+--no-color::
+       Turn off match highlighting, even when the configuration file
+       gives the default to color output.
+
 -[ABC] <context>::
        Show `context` trailing (`A` -- after), or leading (`B`
        -- before), or both (`C` -- context) lines, and place a
index bd49a0aee8881f983077d74bd8d0fbe5764dfea5..d016dafd491de5a4635e30b92607dadbaf7bf46f 100644 (file)
@@ -51,7 +51,7 @@ imap.host::
 imap.user::
        The username to use when logging in to the server.
 
-imap.password::
+imap.pass::
        The password to use when logging in to the server.
 
 imap.port::
@@ -64,6 +64,13 @@ imap.sslverify::
        used by the SSL/TLS connection. Default is `true`. Ignored when
        imap.tunnel is set.
 
+imap.preformattedHTML::
+       A boolean to enable/disable the use of html encoding when sending
+       a patch.  An html encoded patch will be bracketed with <pre>
+       and have a content type of text/html.  Ironically, enabling this
+       option causes Thunderbird to send the patch as a plain/text,
+       format=fixed email.  Default is `false`.
+
 Examples
 ~~~~~~~~
 
@@ -98,6 +105,20 @@ Using direct mode with SSL:
 ..........................
 
 
+CAUTION
+-------
+It is still your responsibility to make sure that the email message
+sent by your email program meets the standards of your project.
+Many projects do not like patches to be attached.  Some mail
+agents will transform patches (e.g. wrap lines, send them as
+format=flowed) in ways that make them fail.  You will get angry
+flames ridiculing you if you don't check this.
+
+Thunderbird in particular is known to be problematic.  Thunderbird
+users may wish to visit this web page for more information:
+  http://kb.mozillazine.org/Plain_text_e-mail_-_Thunderbird#Completely_plain_email
+
+
 BUGS
 ----
 Doesn't handle lines starting with "From " in the message body.
index 71749c09d309f4cae2da9788969359d2620224a9..7151d12f349b7c6e265d5a4631029d71028a2c7d 100644 (file)
@@ -54,15 +54,21 @@ is given:
 
  - 'group' (or 'true'): Make the repository group-writable, (and g+sx, since
    the git group may be not the primary group of all users).
+   This is used to loosen the permissions of an otherwise safe umask(2) value.
+   Note that the umask still applies to the other permission bits (e.g. if
+   umask is '0022', using 'group' will not remove read privileges from other
+   (non-group) users). See '0xxx' for how to exactly specify the repository
+   permissions.
 
  - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository
    readable by all users.
 
- - '0xxx': '0xxx' is an octal number and each file will have mode '0xxx'
-   Any option except 'umask' can be set using this option. '0xxx' will
-   override users umask(2) value, and thus, users with a safe umask (0077)
-   can use this option. '0640' will create a repository which is group-readable
-   but not writable. '0660' is equivalent to 'group'.
+ - '0xxx': '0xxx' is an octal number and each file will have mode '0xxx'.
+   '0xxx' will override users' umask(2) value (and not only loosen permissions
+   as 'group' and 'all' does). '0640' will create a repository which is
+   group-readable, but not group-writable or accessible to others. '0660' will
+   create a repo that is readable and writable to the current user and group,
+   but inaccessible to others.
 
 By default, the configuration flag receive.denyNonFastForwards is enabled
 in shared repositories, so that you cannot force a non fast-forwarding push
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..c3fdccb4c2f66b8b3a3c6b60b21feac881b06cb4 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::
@@ -82,8 +82,10 @@ Output Format
 -------------
         <mode> SP <type> SP <object> TAB <file>
 
-When the `-z` option is not used, TAB, LF, and backslash characters
+Unless the `-z` option is used, TAB, LF, and backslash characters
 in pathnames are represented as `\t`, `\n`, and `\\`, respectively.
+This output format is compatible with what '--index-info --stdin' of
+'git update-index' expects.
 
 When the `-l` option is used, format changes to
 
index f7be5846a67de1a3215bad3d7b9c263705cd1bba..c04ae739edd409307d286fe31e6f87e41f977eac 100644 (file)
@@ -40,8 +40,8 @@ include::merge-options.txt[]
 include::merge-strategies.txt[]
 
 
-If you tried a merge which resulted in complex conflicts and
-would want to start over, you can recover with 'git-reset'.
+If you tried a merge which resulted in complex conflicts and
+want to start over, you can recover with 'git-reset'.
 
 CONFIGURATION
 -------------
@@ -146,7 +146,7 @@ And here is another line that is cleanly resolved or unmodified.
 ------------
 
 The area where a pair of conflicting changes happened is marked with markers
-"`<<<<<<<`", "`=======`", and "`>>>>>>>`".  The part before the "`=======`"
+`<<<<<<<`, `=======`, and `>>>>>>>`.  The part before the `=======`
 is typically your side, and the part afterwards is typically their side.
 
 The default format does not show what the original said in the conflicting
@@ -173,8 +173,8 @@ Git makes conflict resolution easy.
 And here is another line that is cleanly resolved or unmodified.
 ------------
 
-In addition to the "`<<<<<<<`", "`=======`", and "`>>>>>>>`" markers, it uses
-another "`|||||||`" marker that is followed by the original text.  You can
+In addition to the `<<<<<<<`, `=======`, and `>>>>>>>` markers, it uses
+another `|||||||` marker that is followed by the original text.  You can
 tell that the original just stated a fact, and your side simply gave in to
 that statement and gave up, while the other side tried to have a more
 positive attitude.  You can sometimes come up with a better resolution by
diff --git a/Documentation/git-mergetool--lib.txt b/Documentation/git-mergetool--lib.txt
new file mode 100644 (file)
index 0000000..78eb03f
--- /dev/null
@@ -0,0 +1,54 @@
+git-mergetool--lib(1)
+=====================
+
+NAME
+----
+git-mergetool--lib - Common git merge tool shell scriptlets
+
+SYNOPSIS
+--------
+'TOOL_MODE=(diff|merge) . "$(git --exec-path)/git-mergetool--lib"'
+
+DESCRIPTION
+-----------
+
+This is not a command the end user would want to run.  Ever.
+This documentation is meant for people who are studying the
+Porcelain-ish scripts and/or are writing new ones.
+
+The 'git-mergetool--lib' scriptlet is designed to be sourced (using
+`.`) by other shell scripts to set up functions for working
+with git merge tools.
+
+Before sourcing 'git-mergetool--lib', your script must set `TOOL_MODE`
+to define the operation mode for the functions listed below.
+'diff' and 'merge' are valid values.
+
+FUNCTIONS
+---------
+get_merge_tool::
+       returns a merge tool.
+
+get_merge_tool_cmd::
+       returns the custom command for a merge tool.
+
+get_merge_tool_path::
+       returns the custom path for a merge tool.
+
+run_merge_tool::
+       launches a merge tool given the tool name and a true/false
+       flag to indicate whether a merge base is present.
+       '$MERGED', '$LOCAL', '$REMOTE', and '$BASE' must be defined
+       for use by the merge tool.
+
+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 602e7c6d3b497aa4f891915305d507ae88910a54..ff9700d17a333f50d6509bc14a0bd81fad876d3b 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,10 +22,12 @@ 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
+       kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge,
+       diffuse, tortoisemerge and opendiff
 +
 If a merge resolution program is not specified, 'git-mergetool'
 will use the configuration variable `merge.tool`.  If the
@@ -60,6 +62,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 a5244d35f49f10b7954d8fa52164663e3d167d4b..1ee99c208ce4893d2fa2367544b22ed0c8b18044 100644 (file)
@@ -26,7 +26,7 @@ problem by stashing the refs in a single file,
 traditional `$GIT_DIR/refs` hierarchy, it is looked up in this
 file and used if found.
 
-Subsequent updates to branches always creates new file under
+Subsequent updates to branches always create new files under
 `$GIT_DIR/refs` hierarchy.
 
 A recommended practice to deal with a repository with too many
@@ -35,7 +35,7 @@ occasionally run `git pack-refs \--prune`.  Tags are by
 definition stationary and are not expected to change.  Branch
 heads will be packed with the initial `pack-refs --all`, but
 only the currently active branch heads will become unpacked,
-and next `pack-refs` (without `--all`) will leave them
+and the next `pack-refs` (without `--all`) will leave them
 unpacked.
 
 
index 477785e13418e1971156f5210015da4ab9d77cab..253fc0fc255a0f82778d120647031eaa66eb647a 100644 (file)
@@ -20,7 +20,7 @@ IOW, you can use this thing to look for likely duplicate commits.
 
 When dealing with 'git-diff-tree' output, it takes advantage of
 the fact that the patch is prefixed with the object name of the
-commit, and outputs two 40-byte hexadecimal string.  The first
+commit, and outputs two 40-byte hexadecimal strings.  The first
 string is the patch ID, and the second string is the commit ID.
 This can be used to make a mapping from patch ID to commit ID.
 
index ac6421178c1a62ca62fb335d9939ec36368c7e82..fd53c49fb886b196ba79e40c4c9c4efe0dae5432 100644 (file)
@@ -24,8 +24,8 @@ every time you push into it, by setting up 'hooks' there.  See
 documentation for linkgit:git-receive-pack[1].
 
 
-OPTIONS
--------
+OPTIONS[[OPTIONS]]
+------------------
 <repository>::
        The "remote" repository that is destination of a push
        operation.  This parameter can be either a URL
@@ -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
@@ -185,6 +187,28 @@ reason::
 Examples
 --------
 
+git push::
+       Works like `git push <remote>`, where <remote> is the
+       current branch's remote (or `origin`, if no remote is
+       configured for the current branch).
+
+git push origin::
+       Without additional configuration, works like
+       `git push origin :`.
++
+The default behavior of this command when no <refspec> is given can be
+configured by setting the `push` option of the remote.
++
+For example, to default to pushing only the current branch to `origin`
+use `git config remote.origin.push HEAD`.  Any valid <refspec> (like
+the ones in the examples below) can be configured as the default for
+`git push origin`.
+
+git push origin :::
+       Push "matching" branches to `origin`. See
+       <refspec> in the <<OPTIONS,OPTIONS>> section above for a
+       description of "matching" branches.
+
 git push origin master::
        Find a ref that matches `master` in the source repository
        (most likely, it would find `refs/heads/master`), and update
@@ -218,6 +242,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..3d5a066c31675e502eb027dde824d1966c9c0f09 100644 (file)
@@ -8,10 +8,11 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
-       [-s <strategy> | --strategy=<strategy>] [--no-verify]
-       [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
-       [--onto <newbase>] <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] [--onto <newbase>]
+       <upstream> [<branch>]
+'git rebase' [-i | --interactive] [options] --onto <newbase>
+       --root [<branch>]
+
 'git rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -22,7 +23,8 @@ it remains on the current branch.
 
 All changes made by commits in the current branch but that are not
 in <upstream> are saved to a temporary area.  This is the same set
-of commits that would be shown by `git log <upstream>..HEAD`.
+of commits that would be shown by `git log <upstream>..HEAD` (or
+`git log HEAD`, if --root is specified).
 
 The current branch is reset to <upstream>, or <newbase> if the
 --onto option was supplied.  This has the exact same effect as
@@ -190,6 +192,13 @@ Alternatively, you can undo the 'git-rebase' with
 
     git rebase --abort
 
+CONFIGURATION
+-------------
+
+rebase.stat::
+       Whether to show a diffstat of what changed upstream since the last
+       rebase. False by default.
+
 OPTIONS
 -------
 <newbase>::
@@ -230,7 +239,15 @@ OPTIONS
 
 -v::
 --verbose::
-       Display a diffstat of what changed upstream since the last rebase.
+       Be verbose. Implies --stat.
+
+--stat::
+       Show a diffstat of what changed upstream since the last rebase. The
+       diffstat is also controlled by the configuration option rebase.stat.
+
+-n::
+--no-stat::
+       Do not show a diffstat as part of the rebase process.
 
 --no-verify::
        This option bypasses the pre-rebase hook.  See also linkgit:githooks[5].
@@ -241,9 +258,22 @@ OPTIONS
        context exist they all must match.  By default no context is
        ever ignored.
 
---whitespace=<nowarn|warn|error|error-all|strip>::
+-f::
+--force-rebase::
+       Force the rebase even if the current branch is a descendant
+       of the commit you are rebasing onto.  Normally the command will
+       exit with the message "Current branch is up to date" in such a
+       situation.
+
+--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.
+
+--committer-date-is-author-date::
+--ignore-date::
+       These flags are passed to 'git-am' to easily change the dates
+       of the rebased commits (see linkgit:git-am[1]).
 
 -i::
 --interactive::
@@ -255,6 +285,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 fad983e297514da7e847196b0375e3254fdf235d..9e2b4eaa385db66ffe0c547f0452d29e9e3dc484 100644 (file)
@@ -13,9 +13,10 @@ SYNOPSIS
 'git remote add' [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>
 'git remote rename' <old> <new>
 'git remote rm' <name>
+'git remote set-head' <name> [-a | -d | <branch>]
 'git remote show' [-n] <name>
 'git remote prune' [-n | --dry-run] <name>
-'git remote update' [group]
+'git remote update' [-p | --prune] [group | remote]...
 
 DESCRIPTION
 -----------
@@ -53,8 +54,7 @@ is created.  You can give more than one `-t <branch>` to track
 multiple branches without grabbing all branches.
 +
 With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set
-up to point at remote's `<master>` branch instead of whatever
-branch the `HEAD` at the remote repository actually points at.
+up to point at remote's `<master>` branch. See also the set-head command.
 +
 In mirror mode, enabled with `\--mirror`, the refs will not be stored
 in the 'refs/remotes/' namespace, but in 'refs/heads/'.  This option
@@ -76,6 +76,30 @@ the configuration file format.
 Remove the remote named <name>. All remote tracking branches and
 configuration settings for the remote are removed.
 
+'set-head'::
+
+Sets or deletes the default branch (`$GIT_DIR/remotes/<name>/HEAD`) for
+the named remote. Having a default branch for a remote is not required,
+but allows the name of the remote to be specified in lieu of a specific
+branch. For example, if the default branch for `origin` is set to
+`master`, then `origin` may be specified wherever you would normally
+specify `origin/master`.
++
+With `-d`, `$GIT_DIR/remotes/<name>/HEAD` is deleted.
++
+With `-a`, the remote is queried to determine its `HEAD`, then
+`$GIT_DIR/remotes/<name>/HEAD` is set to the same branch. e.g., if the remote
+`HEAD` is pointed at `next`, "`git remote set-head origin -a`" will set
+`$GIT_DIR/refs/remotes/origin/HEAD` to `refs/remotes/origin/next`. This will
+only work if `refs/remotes/origin/next` already exists; if not it must be
+fetched first.
++
+Use `<branch>` to set `$GIT_DIR/remotes/<name>/HEAD` explicitly. e.g., "git
+remote set-head origin master" will set `$GIT_DIR/refs/remotes/origin/HEAD` to
+`refs/remotes/origin/master`. This will only work if
+`refs/remotes/origin/master` already exists; if not it must be fetched first.
++
+
 'show'::
 
 Gives some information about the remote <name>.
@@ -101,6 +125,8 @@ the configuration parameter remotes.default will get used; if
 remotes.default is not defined, all remotes which do not have the
 configuration parameter remote.<name>.skipDefaultUpdate set to true will
 be updated.  (See linkgit:git-config[1]).
++
+With `--prune` option, prune all the remotes that are updated.
 
 
 DISCUSSION
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..52c353e674761bf4897484a261c702e5cc02f18a 100644 (file)
@@ -26,7 +26,7 @@ OPTIONS
 --parseopt::
        Use 'git-rev-parse' in option parsing mode (see PARSEOPT section below).
 
---keep-dash-dash::
+--keep-dashdash::
        Only meaningful in `--parseopt` mode. Tells the option parser to echo
        out the first `--` met instead of skipping it.
 
@@ -84,6 +84,11 @@ OPTIONS
        unfortunately named tag "master"), and show them as full
        refnames (e.g. "refs/heads/master").
 
+--abbrev-ref[={strict|loose}]::
+       A non-ambiguous short name of the objects name.
+       The option core.warnAmbiguousRefs is used to select the strict
+       abbreviation mode.
+
 --all::
        Show all refs found in `$GIT_DIR/refs`.
 
@@ -212,6 +217,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}'
@@ -296,18 +304,18 @@ previous section means the set of commits reachable from that
 commit, following the commit ancestry chain.
 
 To exclude commits reachable from a commit, a prefix `{caret}`
-notation is used.  E.g. "`{caret}r1 r2`" means commits reachable
+notation is used.  E.g. `{caret}r1 r2` means commits reachable
 from `r2` but exclude the ones reachable from `r1`.
 
 This set operation appears so often that there is a shorthand
 for it.  When you have two commits `r1` and `r2` (named according
 to the syntax explained in SPECIFYING REVISIONS above), you can ask
 for commits that are reachable from r2 excluding those that are reachable
-from r1 by "`{caret}r1 r2`" and it can be written as "`r1..r2`".
+from r1 by `{caret}r1 r2` and it can be written as `r1..r2`.
 
-A similar notation "`r1\...r2`" is called symmetric difference
+A similar notation `r1\...r2` is called symmetric difference
 of `r1` and `r2` and is defined as
-"`r1 r2 --not $(git merge-base --all r1 r2)`".
+`r1 r2 --not $(git merge-base --all r1 r2)`.
 It is the set of commits that are reachable from either one of
 `r1` or `r2` but not from both.
 
index 66bf3b2fcdccda9cb29d66756b3c20e5a1545d46..794224b1b3431655aa9e9683c9d938e35f7a3ea4 100644 (file)
@@ -39,13 +39,13 @@ OPTIONS
 Composing
 ~~~~~~~~~
 
---bcc::
+--bcc=<address>::
        Specify a "Bcc:" value for each email. Default is the value of
        'sendemail.bcc'.
 +
 The --bcc option must be repeated for each user you want on the bcc list.
 
---cc::
+--cc=<address>::
        Specify a starting "Cc:" value for each email.
        Default is the value of 'sendemail.cc'.
 +
@@ -60,33 +60,32 @@ The --cc option must be repeated for each user you want on the cc list.
        Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
        introductory message for the patch series.
 +
-When '--compose' is used, git send-email gets less interactive will use the
-values of the headers you set there. If the body of the email (what you type
-after the headers and a blank line) only contains blank (or GIT: prefixed)
-lines, the summary won't be sent, but git-send-email will still use the
-Headers values if you don't removed them.
+When '--compose' is used, git send-email will use the From, Subject, and
+In-Reply-To headers specified in the message. If the body of the message
+(what you type after the headers and a blank line) only contains blank
+(or GIT: prefixed) lines the summary won't be sent, but From, Subject,
+and In-Reply-To headers will be used unless they are removed.
 +
-If it wasn't able to see a header in the summary it will ask you about it
-interactively after quitting your editor.
+Missing From or In-Reply-To headers will be prompted for.
 
---from::
+--from=<address>::
        Specify the sender of the emails.  This will default to
        the value GIT_COMMITTER_IDENT, as returned by "git var -l".
        The user will still be prompted to confirm this entry.
 
---in-reply-to::
+--in-reply-to=<identifier>::
        Specify the contents of the first In-Reply-To header.
        Subsequent emails will refer to the previous email
        instead of this if --chain-reply-to is set (the default)
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
---subject::
+--subject=<string>::
        Specify the initial subject of the email thread.
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
---to::
+--to=<address>::
        Specify the primary recipient of the emails generated. Generally, this
        will be the upstream maintainer of the project involved. Default is the
        value of the 'sendemail.to' configuration value; if that is unspecified,
@@ -98,7 +97,7 @@ The --to option must be repeated for each user you want on the to list.
 Sending
 ~~~~~~~
 
---envelope-sender::
+--envelope-sender=<address>::
        Specify the envelope sender used to send the emails.
        This is useful if your default address is not the address that is
        subscribed to a list. If you use the sendmail binary, you must have
@@ -106,12 +105,12 @@ Sending
        the 'sendemail.envelopesender' configuration variable; if that is
        unspecified, choosing the envelope sender is left to your MTA.
 
---smtp-encryption::
+--smtp-encryption=<encryption>::
        Specify the encryption to use, either 'ssl' or 'tls'.  Any other
        value reverts to plain SMTP.  Default is the value of
        'sendemail.smtpencryption'.
 
---smtp-pass::
+--smtp-pass[=<password>]::
        Password for SMTP-AUTH. The argument is optional: If no
        argument is specified, then the empty string is used as
        the password. Default is the value of 'sendemail.smtppass',
@@ -123,7 +122,7 @@ or on the command line. If a username has been specified (with
 specified (with '--smtp-pass' or 'sendemail.smtppass'), then the
 user is prompted for a password while the input is masked for privacy.
 
---smtp-server::
+--smtp-server=<host>::
        If set, specifies the outgoing SMTP server to use (e.g.
        `smtp.example.com` or a raw IP address).  Alternatively it can
        specify a full pathname of a sendmail-like program instead;
@@ -133,7 +132,7 @@ user is prompted for a password while the input is masked for privacy.
        `/usr/lib/sendmail` if such program is available, or
        `localhost` otherwise.
 
---smtp-server-port::
+--smtp-server-port=<port>::
        Specifies a port different from the default port (SMTP
        servers typically listen to smtp port 25 and ssmtp port
        465). This can be set with 'sendemail.smtpserverport'.
@@ -141,7 +140,7 @@ user is prompted for a password while the input is masked for privacy.
 --smtp-ssl::
        Legacy alias for '--smtp-encryption ssl'.
 
---smtp-user::
+--smtp-user=<user>::
        Username for SMTP-AUTH. Default is the value of 'sendemail.smtpuser';
        if a username is not specified (with '--smtp-user' or 'sendemail.smtpuser'),
        then authentication is not attempted.
@@ -150,13 +149,13 @@ user is prompted for a password while the input is masked for privacy.
 Automating
 ~~~~~~~~~~
 
---cc-cmd::
+--cc-cmd=<command>::
        Specify a command to execute once per patch file which
        should generate patch file specific "Cc:" entries.
        Output of this command must be single email address per line.
        Default is the value of 'sendemail.cccmd' configuration value.
 
---[no-]chain-reply-to::
+--[no-]chain-reply-to=<identifier>::
        If this is set, each email will be sent as a reply to the previous
        email sent.  If disabled with "--no-chain-reply-to", all emails after
        the first will be sent as replies to the first email sent.  When using
@@ -164,7 +163,7 @@ Automating
        entire patch series. Default is the value of the 'sendemail.chainreplyto'
        configuration value; if that is unspecified, default to --chain-reply-to.
 
---identity::
+--identity=<identity>::
        A configuration identity. When given, causes values in the
        'sendemail.<identity>' subsection to take precedence over
        values in the 'sendemail' section. The default identity is
@@ -175,16 +174,27 @@ Automating
        cc list. Default is the value of 'sendemail.signedoffbycc' configuration
        value; if that is unspecified, default to --signed-off-by-cc.
 
---suppress-cc::
+--suppress-cc=<category>::
        Specify an additional category of recipients to suppress the
-       auto-cc of.  'self' will avoid including the sender, 'author' will
-       avoid including the patch author, 'cc' will avoid including anyone
-       mentioned in Cc lines in the patch, 'sob' will avoid including
-       anyone mentioned in Signed-off-by lines, and 'cccmd' will avoid
-       running the --cc-cmd.  'all' will suppress all auto cc values.
-       Default is the value of 'sendemail.suppresscc' configuration value;
-       if that is unspecified, default to 'self' if --suppress-from is
-       specified, as well as 'sob' if --no-signed-off-cc is specified.
+       auto-cc of:
++
+--
+- 'author' will avoid including the patch author
+- 'self' will avoid including the sender
+- 'cc' will avoid including anyone mentioned in Cc lines in the patch header
+  except for self (use 'self' for that).
+- 'ccbody' will avoid including anyone mentioned in Cc lines in the
+  patch body (commit message) except for self (use 'self' for that).
+- 'sob' will avoid including anyone mentioned in Signed-off-by lines except
+   for self (use 'self' for that).
+- 'cccmd' will avoid running the --cc-cmd.
+- 'body' is equivalent to 'sob' + 'ccbody'
+- 'all' will suppress all auto cc values.
+--
++
+Default is the value of 'sendemail.suppresscc' configuration value; if
+that is unspecified, default to 'self' if --suppress-from is
+specified, as well as 'body' if --no-signed-off-cc is specified.
 
 --[no-]suppress-from::
        If this is set, do not add the From: address to the cc: list.
@@ -201,6 +211,22 @@ Automating
 Administering
 ~~~~~~~~~~~~~
 
+--confirm=<mode>::
+       Confirm just before sending:
++
+--
+- 'always' will always confirm before sending
+- 'never' will never confirm before sending
+- 'cc' will confirm before sending when send-email has automatically
+  added addresses from the patch to the Cc list
+- 'compose' will confirm before sending the first message when using --compose.
+- 'auto' is equivalent to 'cc' + 'compose'
+--
++
+Default is the value of 'sendemail.confirm' configuration value; if that
+is unspecified, default to 'auto' unless any of the suppress options
+have been specified, in which case default to 'compose'.
+
 --dry-run::
        Do everything except actually send the emails.
 
@@ -236,7 +262,7 @@ sendemail.aliasesfile::
 
 sendemail.aliasfiletype::
        Format of the file(s) specified in sendemail.aliasesfile. Must be
-       one of 'mutt', 'mailrc', 'pine', or 'gnus'.
+       one of 'mutt', 'mailrc', 'pine', 'elm', or 'gnus'.
 
 sendemail.multiedit::
        If true (default), a single editor instance will be spawned to edit
@@ -244,6 +270,11 @@ sendemail.multiedit::
        summary when '--compose' is used). If false, files will be edited one
        after the other, spawning a new editor each time.
 
+sendemail.confirm::
+       Sets the default for whether to confirm before sending. Must be
+       one of 'always', 'never', 'cc', 'compose', or 'auto'. See '--confirm'
+       in the previous section for the meaning of these values.
+
 
 Author
 ------
index 3f8d973af1c9b8aead3f831eb94a42b2e461ec8b..0f3ad811cfa41e65a3d807a5eb766ce2a66a7831 100644 (file)
@@ -18,9 +18,9 @@ of server-side GIT commands implementing the pull/push functionality.
 The commands can be executed only by the '-c' option; the shell is not
 interactive.
 
-Currently, only three commands are permitted to be called, 'git-receive-pack'
-'git-upload-pack' with a single required argument or 'cvs server' (to invoke
-'git-cvsserver').
+Currently, only four commands are permitted to be called, 'git-receive-pack'
+'git-upload-pack' and 'git-upload-archive' with a single required argument, or
+'cvs server' (to invoke 'git-cvsserver').
 
 Author
 ------
index 8f7c0e226df8f58712e6a0d78ecec58c96aa2453..42463a955dd3f5dad25f3a45f0eec62d8712f731 100644 (file)
@@ -45,45 +45,16 @@ OPTIONS
        and subsequent lines are indented by `indent2` spaces. `width`,
        `indent1`, and `indent2` default to 76, 6 and 9 respectively.
 
-FILES
------
-
-If a file `.mailmap` exists at the toplevel of the repository,
-it is used to map an author email address to a canonical real name. This
-can be used to coalesce together commits by the same person where their
-name was spelled differently (whether with the same email address or
-not).
-
-Each line in the file consists, in this order, of the canonical real name
-of an author, whitespace, and an email address (enclosed by '<' and '>')
-to map to the name. Use hash '#' for comments, either on their own line,
-or after the email address.
-
-A canonical name may appear in more than one line, associated with
-different email addresses, but it doesn't make sense for a given address
-to appear more than once (if that happens, a later line overrides the
-earlier ones).
-
-So, for example, if your history contains commits by two authors, Jane
-and Joe, whose names appear in the repository under several forms:
-
-------------
-Joe Developer <joe@example.com>
-Joe R. Developer <joe@example.com>
-Jane Doe <jane@example.com>
-Jane Doe <jane@laptop.(none)>
-Jane D. <jane@desktop.(none)>
-------------
-
-Then, supposing Joe wants his middle name initial used, and Jane prefers
-her family name fully spelled out, a proper `.mailmap` file would look like:
-
-------------
-# Note how we don't need an entry for <jane@laptop.(none)>, because the
-# real name of that author is correct already, and coalesced directly.
-Jane Doe <jane@desktop.(none)>
-Joe R. Developer <joe@random.com>
-------------
+
+MAPPING AUTHORS
+---------------
+
+The `.mailmap` feature is used to coalesce together commits by the same
+person in the shortlog, where their name and/or email address was
+spelled differently.
+
+include::mailmap.txt[]
+
 
 Author
 ------
index 8277577a6f896f019793382735a8c0f4a6d5e1c3..51a4e9d6d767f34205f418ec86a6281dbf4c7b2c 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
@@ -148,9 +148,10 @@ $ git show-branch master fixes mhf
 ------------------------------------------------
 
 These three branches all forked from a common commit, [master],
-whose commit message is "Add 'git show-branch'.  "fixes" branch
-adds one commit 'Introduce "reset type"'.  "mhf" branch has many
-other commits.  The current branch is "master".
+whose commit message is "Add \'git show-branch\'". The "fixes"
+branch adds one commit "Introduce "reset type" flag to "git reset"".
+The "mhf" branch adds many other commits. The current branch
+is "master".
 
 
 EXAMPLE
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..1c40894669d6f86e7dbb97d86ef9ee6f2a76190d 100644 (file)
@@ -85,6 +85,10 @@ COMMANDS
        specified, the prefix must include a trailing slash.
        Setting a prefix is useful if you wish to track multiple
        projects that share a common repository.
+--ignore-paths=<regex>;;
+       When passed to 'init' or 'clone' this regular expression will
+       be preserved as a config key.  See 'fetch' for a description
+       of '--ignore-paths'.
 
 'fetch'::
        Fetch unfetched revisions from the Subversion remote we are
@@ -92,6 +96,41 @@ 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.
+
+--parent;;
+       Fetch only from the SVN parent of the current HEAD.
+
+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 a Perl regular expression that will
+       cause skipping of all matching paths from checkout from SVN.
+       The '--ignore-paths' option should match for every 'fetch'
+       (including automatic fetches due to 'clone', 'dcommit',
+       'rebase', etc) on a given repository.
+
+config key: svn-remote.<name>.ignore-paths
+
+       If the ignore-paths config key is set and the command
+       line option is also given, both regular expressions
+       will be used.
+
+Examples:
+
+       --ignore-paths="^doc" - skip "doc*" directory for every
+           fetch.
+
+       --ignore-paths="^[^/]+/(?:branches|tags)" - skip
+           "branches" and "tags" of first level directories.
+
 'clone'::
        Runs 'init' and 'fetch'.  It will automatically create a
        directory based on the basename of the URL passed to it;
@@ -145,6 +184,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.
 --
@@ -357,7 +400,8 @@ config key: svn.authorsfile
 
 -q::
 --quiet::
-       Make 'git-svn' less verbose.
+       Make 'git-svn' less verbose. Specify a second time to make it
+       even less verbose.
 
 --repack[=<n>]::
 --repack-flags=<flags>::
@@ -475,6 +519,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
@@ -636,14 +688,14 @@ listed below are allowed:
 ------------------------------------------------------------------------
 [svn-remote "project-a"]
        url = http://server.org/svn
+       fetch = trunk/project-a:refs/remotes/project-a/trunk
        branches = branches/*/project-a:refs/remotes/project-a/branches/*
        tags = tags/*/project-a:refs/remotes/project-a/tags/*
-       trunk = trunk/project-a:refs/remotes/project-a/trunk
 ------------------------------------------------------------------------
 
-Keep in mind that the '*' (asterisk) wildcard of the local ref
+Keep in mind that the '\*' (asterisk) wildcard of the local ref
 (right of the ':') *must* be the farthest right path component;
-however the remote wildcard may be anywhere as long as it's own
+however the remote wildcard may be anywhere as long as it's an
 independent path component (surrounded by '/' or EOL).   This
 type of configuration is not automatically created by 'init' and
 should be manually entered with a text-editor or using 'git-config'.
index 3546acffb5197ac2ed1a542607fc244bc31b3ae3..fa733214ab0259dec1c866e9cb629dd0e7b69f3a 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
@@ -69,6 +69,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 35d27b0c7f0e4b7a1d0851140958e71fabb0e6bc..035cc3018f22a9e2669c94f10475624d02f4098a 100644 (file)
@@ -39,12 +39,6 @@ what they are for:
 * info/refs
 
 
-BUGS
-----
-When you remove an existing ref, the command fails to update
-info/refs file unless `--force` flag is given.
-
-
 Author
 ------
 Written by Junio C Hamano <gitster@pobox.com>
index 17dc8b20192f2558775250436152d0e8acfde41e..9d8f236fe84cd02550b8cf08e835b2926e6c45f0 100644 (file)
@@ -9,7 +9,7 @@ git - the stupid content tracker
 SYNOPSIS
 --------
 [verse]
-'git' [--version] [--exec-path[=GIT_EXEC_PATH]]
+'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]
     [-p|--paginate|--no-pager]
     [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
     [--help] COMMAND [ARGS]
@@ -43,9 +43,22 @@ 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.3/git.html[documentation for release 1.6.3]
 
 * release notes for
+  link:RelNotes-1.6.2.5.txt[1.6.2.5],
+  link:RelNotes-1.6.2.4.txt[1.6.2.4],
+  link:RelNotes-1.6.2.3.txt[1.6.2.3],
+  link:RelNotes-1.6.2.2.txt[1.6.2.2],
+  link:RelNotes-1.6.2.1.txt[1.6.2.1],
+  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]
@@ -169,6 +182,10 @@ help ...`.
        environment variable. If no path is given, 'git' will print
        the current setting and then exit.
 
+--html-path::
+       Print the path to wherever your git HTML documentation is installed
+       and exit.
+
 -p::
 --paginate::
        Pipe all output into 'less' (or if set, $PAGER).
index 82c10fa0fda58a07ffd05685de4b110f38e4edad..aaa073efc80522a649f17d60127aae8cc85b0b3b 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
@@ -296,7 +297,8 @@ for paths.
 
 Then, you would define a "diff.tex.xfuncname" configuration to
 specify a regular expression that matches a line that you would
-want to appear as the hunk header "TEXT", like this:
+want to appear as the hunk header "TEXT". Add a section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
 
 ------------------------
 [diff "tex"]
@@ -317,6 +319,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 +338,26 @@ 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 in your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
+
+------------------------
+[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
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
@@ -351,7 +375,8 @@ resulting text on stdout.
 
 For example, to show the diff of the exif information of a
 file instead of the binary information (assuming you have the
-exif tool installed):
+exif tool installed), add the following section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file):
 
 ------------------------
 [diff "jpg"]
index 29e5929db22257346a2bed16cbd5ca6531698676..be39ed7c1509158867998ce8fb77341242ecdf79 100644 (file)
@@ -46,20 +46,20 @@ Here are the rules regarding the "flags" that you should follow when you are
 scripting git:
 
  * it's preferred to use the non dashed form of git commands, which means that
-   you should prefer `"git foo"` to `"git-foo"`.
+   you should prefer `git foo` to `git-foo`.
 
- * splitting short options to separate words (prefer `"git foo -a -b"`
-   to `"git foo -ab"`, the latter may not even work).
+ * splitting short options to separate words (prefer `git foo -a -b`
+   to `git foo -ab`, the latter may not even work).
 
  * when a command line option takes an argument, use the 'sticked' form.  In
-   other words, write `"git foo -oArg"` instead of `"git foo -o Arg"` for short
-   options, and `"git foo --long-opt=Arg"` instead of `"git foo --long-opt Arg"`
+   other words, write `git foo -oArg` instead of `git foo -o Arg` for short
+   options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg`
    for long options.  An option that takes optional option-argument must be
    written in the 'sticked' form.
 
  * when you give a revision parameter to a command, make sure the parameter is
    not ambiguous with a name of a file in the work tree.  E.g. do not write
-   `"git log -1 HEAD"` but write `"git log -1 HEAD --"`; the former will not work
+   `git log -1 HEAD` but write `git log -1 HEAD --`; the former will not work
    if you happen to have a file called `HEAD` in the work tree.
 
 
@@ -99,17 +99,17 @@ usage: git-describe [options] <committish>*
 
 Negating options
 ~~~~~~~~~~~~~~~~
-Options with long option names can be negated by prefixing `"--no-"`. For
-example, `"git branch"` has the option `"--track"` which is 'on' by default. You
-can use `"--no-track"` to override that behaviour. The same goes for `"--color"`
-and `"--no-color"`.
+Options with long option names can be negated by prefixing `--no-`. For
+example, `git branch` has the option `--track` which is 'on' by default. You
+can use `--no-track` to override that behaviour. The same goes for `--color`
+and `--no-color`.
 
 
 Aggregating short options
 ~~~~~~~~~~~~~~~~~~~~~~~~~
 Commands that support the enhanced option parser allow you to aggregate short
-options. This means that you can for example use `"git rm -rf"` or
-`"git clean -fdx"`.
+options. This means that you can for example use `git rm -rf` or
+`git clean -fdx`.
 
 
 Separating argument from the option
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 aaa7ef737a4c190c60e37e2849ce42f3bdb5dda7..0e49c1c03776af30f0509679f720273061aaa7b3 100644 (file)
@@ -118,7 +118,7 @@ Importing a CVS archive
 First, install version 2.1 or higher of cvsps from
 link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
 sure it is in your path.  Then cd to a checked out CVS working directory
-of the project you are interested in and run 'git-cvsimport':
+of the project you are interested in and run linkgit:git-cvsimport[1]:
 
 -------------------------------------------
 $ git cvsimport -C <destination> <module>
index 28a8abcf52b0c4180a132d4093b6b8a59c63c6fd..1c736738ccc91b6929226a27e02716221ec3ef05 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.
@@ -151,6 +151,10 @@ indicating whether the checkout was a branch checkout (changing branches,
 flag=1) or a file checkout (retrieving a file from the index, flag=0).
 This hook cannot affect the outcome of 'git-checkout'.
 
+It is also run after 'git-clone', unless the --no-checkout (-n) option is
+used. The first parameter given to the hook is the null-ref, the second the
+ref of the new HEAD and the flag is always 1.
+
 This hook can be used to perform repository validity checks, auto-display
 differences from the previous HEAD if different, or set working dir metadata
 properties.
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 9afca755ed309b5bdf3cd293d6120f676f1053cd..572374f7a6ff17dd40ad687a2fda24d53f7d730b 100644 (file)
@@ -262,7 +262,7 @@ This commit is referred to as a "merge commit", or sometimes just a
        'origin' is used for that purpose. New upstream updates
        will be fetched into remote <<def_tracking_branch,tracking branches>> named
        origin/name-of-upstream-branch, which you can see using
-       "`git branch -r`".
+       `git branch -r`.
 
 [[def_pack]]pack::
        A set of objects which have been compressed into one file (to save space
@@ -449,6 +449,12 @@ This commit is referred to as a "merge commit", or sometimes just a
        An <<def_object,object>> which is not <<def_reachable,reachable>> from a
        <<def_branch,branch>>, <<def_tag,tag>>, or any other reference.
 
+[[def_upstream_branch]]upstream branch::
+       The default <<def_branch,branch>> that is merged into the branch in
+       question (or the branch in question is rebased onto). It is configured
+       via branch.<name>.remote and branch.<name>.merge. If the upstream branch
+       of 'A' is 'origin/B' sometimes we say "'A' is tracking 'origin/B'".
+
 [[def_working_tree]]working tree::
        The tree of actual checked out files.  The working tree is
        normally equal to the <<def_HEAD,HEAD>> plus any local changes
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..288f04e
--- /dev/null
@@ -0,0 +1,74 @@
+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. For example:
+--
+       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 there is no need for an entry for <jane@laptop.(none)>, because the
+real name of that author is already correct.
+
+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 that looks 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.
index 4065a3a27a38be73132b9f509e1d63546b3fddef..b4d315cb8c4792c25cfad7892e056356543e85e1 100644 (file)
@@ -1,21 +1,14 @@
-<!-- Based on callouts.xsl. Fixes man page callouts for DocBook 1.72 XSL -->
-<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+<!-- manpage-1.72.xsl:
+     special settings for manpages rendered from asciidoc+docbook
+     handles peculiarities in docbook-xsl 1.72.0 -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
 
-<xsl:param name="man.output.quietly" select="1"/>
-<xsl:param name="refentry.meta.get.quietly" select="1"/>
+<xsl:import href="manpage-base.xsl"/>
 
-<xsl:template match="co">
-       <xsl:value-of select="concat('&#x2593;fB(',substring-after(@id,'-'),')&#x2593;fR')"/>
-</xsl:template>
-<xsl:template match="calloutlist">
-       <xsl:text>&#x2302;sp&#10;</xsl:text>
-       <xsl:apply-templates/>
-       <xsl:text>&#10;</xsl:text>
-</xsl:template>
-<xsl:template match="callout">
-       <xsl:value-of select="concat('&#x2593;fB',substring-after(@arearefs,'-'),'. &#x2593;fR')"/>
-       <xsl:apply-templates/>
-       <xsl:text>&#x2302;br&#10;</xsl:text>
-</xsl:template>
+<!-- these are the special values for the roff control characters
+     needed for docbook-xsl 1.72.0 -->
+<xsl:param name="git.docbook.backslash">&#x2593;</xsl:param>
+<xsl:param name="git.docbook.dot"      >&#x2302;</xsl:param>
 
 </xsl:stylesheet>
diff --git a/Documentation/manpage-base.xsl b/Documentation/manpage-base.xsl
new file mode 100644 (file)
index 0000000..a264fa6
--- /dev/null
@@ -0,0 +1,35 @@
+<!-- manpage-base.xsl:
+     special formatting for manpages rendered from asciidoc+docbook -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<!-- these params silence some output from xmlto -->
+<xsl:param name="man.output.quietly" select="1"/>
+<xsl:param name="refentry.meta.get.quietly" select="1"/>
+
+<!-- convert asciidoc callouts to man page format;
+     git.docbook.backslash and git.docbook.dot params
+     must be supplied by another XSL file or other means -->
+<xsl:template match="co">
+       <xsl:value-of select="concat(
+                             $git.docbook.backslash,'fB(',
+                             substring-after(@id,'-'),')',
+                             $git.docbook.backslash,'fR')"/>
+</xsl:template>
+<xsl:template match="calloutlist">
+       <xsl:value-of select="$git.docbook.dot"/>
+       <xsl:text>sp&#10;</xsl:text>
+       <xsl:apply-templates/>
+       <xsl:text>&#10;</xsl:text>
+</xsl:template>
+<xsl:template match="callout">
+       <xsl:value-of select="concat(
+                             $git.docbook.backslash,'fB',
+                             substring-after(@arearefs,'-'),
+                             '. ',$git.docbook.backslash,'fR')"/>
+       <xsl:apply-templates/>
+       <xsl:value-of select="$git.docbook.dot"/>
+       <xsl:text>br&#10;</xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/Documentation/manpage-bold-literal.xsl b/Documentation/manpage-bold-literal.xsl
new file mode 100644 (file)
index 0000000..608eb5d
--- /dev/null
@@ -0,0 +1,17 @@
+<!-- manpage-bold-literal.xsl:
+     special formatting for manpages rendered from asciidoc+docbook -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<!-- render literal text as bold (instead of plain or monospace);
+     this makes literal text easier to distinguish in manpages
+     viewed on a tty -->
+<xsl:template match="literal">
+       <xsl:value-of select="$git.docbook.backslash"/>
+       <xsl:text>fB</xsl:text>
+       <xsl:apply-templates/>
+       <xsl:value-of select="$git.docbook.backslash"/>
+       <xsl:text>fR</xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/Documentation/manpage-normal.xsl b/Documentation/manpage-normal.xsl
new file mode 100644 (file)
index 0000000..a48f5b1
--- /dev/null
@@ -0,0 +1,13 @@
+<!-- manpage-normal.xsl:
+     special settings for manpages rendered from asciidoc+docbook
+     handles anything we want to keep away from docbook-xsl 1.72.0 -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<xsl:import href="manpage-base.xsl"/>
+
+<!-- these are the normal values for the roff control characters -->
+<xsl:param name="git.docbook.backslash">\</xsl:param>
+<xsl:param name="git.docbook.dot"      >.</xsl:param>
+
+</xsl:stylesheet>
diff --git a/Documentation/manpage-suppress-sp.xsl b/Documentation/manpage-suppress-sp.xsl
new file mode 100644 (file)
index 0000000..a63c763
--- /dev/null
@@ -0,0 +1,21 @@
+<!-- manpage-suppress-sp.xsl:
+     special settings for manpages rendered from asciidoc+docbook
+     handles erroneous, inline .sp in manpage output of some
+     versions of docbook-xsl -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version="1.0">
+
+<!-- attempt to work around spurious .sp at the tail of the line
+     that some versions of docbook stylesheets seem to add -->
+<xsl:template match="simpara">
+  <xsl:variable name="content">
+    <xsl:apply-templates/>
+  </xsl:variable>
+  <xsl:value-of select="normalize-space($content)"/>
+  <xsl:if test="not(ancestor::authorblurb) and
+                not(ancestor::personblurb)">
+    <xsl:text>&#10;&#10;</xsl:text>
+  </xsl:if>
+</xsl:template>
+
+</xsl:stylesheet>
index 1ff08ff2ccc6e56542f49713867698b66dab673f..4832bc75e2604c997e004018023e3b93cb394b87 100644 (file)
@@ -22,7 +22,8 @@ merge.stat::
 merge.tool::
        Controls which merge resolution program is used by
        linkgit:git-mergetool[1].  Valid built-in values are: "kdiff3",
-       "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and
+       "tkdiff", "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff",
+       "diffuse", "ecmerge", "tortoisemerge", and
        "opendiff".  Any other value is treated is custom merge tool
        and there must be a corresponding mergetool.<tool>.cmd option.
 
index 637b53f898829af98153d60953434fa30e3df179..adadf8e4bf309a2fd2ec8efbeff09b410ca7b041 100644 (file)
@@ -39,7 +39,8 @@
 
 --squash::
        Produce the working tree and index state as if a real
-       merge happened, but do not actually make a commit or
+       merge happened (except for the merge information),
+       but do not actually make a commit or
        move the `HEAD`, nor record `$GIT_DIR/MERGE_HEAD` to
        cause the next `git commit` command to create a merge
        commit.  This allows you to create a single commit on
index 1276f858ade29bec40716d19cf56fe6e3882fc25..4365b7e8420fa96d6cbfa14a5aa49d956ba2de16 100644 (file)
@@ -3,15 +3,15 @@ MERGE STRATEGIES
 
 resolve::
        This can only resolve two heads (i.e. the current branch
-       and another branch you pulled from) using 3-way merge
+       and another branch you pulled from) using 3-way merge
        algorithm.  It tries to carefully detect criss-cross
        merge ambiguities and is considered generally safe and
        fast.
 
 recursive::
-       This can only resolve two heads using 3-way merge
-       algorithm.  When there are more than one common
-       ancestors that can be used for 3-way merge, it creates a
+       This can only resolve two heads using 3-way merge
+       algorithm.  When there is more than one common
+       ancestor that can be used for 3-way merge, it creates a
        merged tree of the common ancestors and uses that as
        the reference tree for the 3-way merge.  This has been
        reported to result in fewer merge conflicts without
@@ -22,11 +22,11 @@ recursive::
        pulling or merging one branch.
 
 octopus::
-       This resolves more than two-head case, but refuses to do
-       complex merge that needs manual resolution.  It is
+       This resolves cases with more than two heads, but refuses to do
+       complex merge that needs manual resolution.  It is
        primarily meant to be used for bundling topic branch
        heads together.  This is the default merge strategy when
-       pulling or merging more than one branches.
+       pulling or merging more than one branch.
 
 ours::
        This resolves any number of heads, but the result of the
index 0a8a948e6ffba9d15b5db03399f74ed0139394ea..2a845b1e57590a6f18f05fd3efa858b736c1071a 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
@@ -119,11 +121,13 @@ The placeholders are:
 - '%d': ref names, like the --decorate option of linkgit:git-log[1]
 - '%e': encoding
 - '%s': subject
+- '%f': sanitized subject line, suitable for a filename
 - '%b': body
 - '%Cred': switch color to red
 - '%Cgreen': switch color to green
 - '%Cblue': switch color to blue
 - '%Creset': reset color
+- '%C(...)': color specification, as described in color.branch.* config option
 - '%m': left, right or boundary mark
 - '%n': newline
 - '%x00': print a byte from a hex code
@@ -149,3 +153,12 @@ $ git log -2 --pretty=tformat:%h 4da45bef \
 4da45be
 7134973
 ---------------------
++
+In addition, any unrecognized string that has a `%` in it is interpreted
+as if it has `tformat:` in front of it.  For example, these two are
+equivalent:
++
+---------------------
+$ git log -2 --pretty=tformat:%h 4da45bef
+$ git log -2 --pretty=%h 4da45bef
+---------------------
index 6d66c74cc11e6622892061f8328d04dfe38f87bf..bff94991b68aaca5a81eae4e6681f3431aa6b9ac 100644 (file)
@@ -1,4 +1,5 @@
 --pretty[='<format>']::
+--format[='<format>']::
 
        Pretty-print the contents of the commit logs in a given format,
        where '<format>' can be one of 'oneline', 'short', 'medium',
@@ -10,13 +11,17 @@ configuration (see linkgit:git-config[1]).
 
 --abbrev-commit::
        Instead of showing the full 40-byte hexadecimal commit object
-       name, show only handful hexdigits prefix.  Non default number of
+       name, show only a partial prefix.  Non default number of
        digits can be specified with "--abbrev=<n>" (which also modifies
        diff output, if it is displayed).
 +
 This should make "--pretty=oneline" a whole lot more readable for
 people using 80-column terminals.
 
+--oneline::
+       This is a shorthand for "--pretty=oneline --abbrev-commit"
+       used together.
+
 --encoding[=<encoding>]::
        The commit objects record the encoding used for the log message
        in their encoding header; this option can be used to tell the
index b9f6e4d1b7564480fac9c4e355fdc4936f6fa3db..11eec941dff7302ffcd68eca3d6437d31c022ca9 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).
 
@@ -138,38 +140,38 @@ limiting may be applied.
 --
 
 -n 'number'::
---max-count='number'::
+--max-count=<number>::
 
        Limit the number of commits output.
 
---skip='number'::
+--skip=<number>::
 
        Skip 'number' commits before starting to show the commit output.
 
---since='date'::
---after='date'::
+--since=<date>::
+--after=<date>::
 
        Show commits more recent than a specific date.
 
---until='date'::
---before='date'::
+--until=<date>::
+--before=<date>::
 
        Show commits older than a specific date.
 
 ifdef::git-rev-list[]
---max-age='timestamp'::
---min-age='timestamp'::
+--max-age=<timestamp>::
+--min-age=<timestamp>::
 
        Limit the commits output to specified time range.
 endif::git-rev-list[]
 
---author='pattern'::
---committer='pattern'::
+--author=<pattern>::
+--committer=<pattern>::
 
        Limit the commits output to ones with author/committer
        header lines that match the specified pattern (regular expression).
 
---grep='pattern'::
+--grep=<pattern>::
 
        Limit the commits output to ones with log message that
        matches the specified pattern (regular expression).
@@ -566,11 +568,11 @@ This outputs all the commit objects between the included and excluded
 commits, ordered by their distance to the included and excluded
 commits. The farthest from them is displayed first. (This is the only
 one displayed by `--bisect`.)
-
++
 This is useful because it makes it easy to choose a good commit to
 test when you want to avoid to test some of them for some reason (they
 may not compile for example).
-
++
 This option can be used along with `--bisect-vars`, in this case,
 after all the sorted commit objects, there will be the same text as if
 `--bisect-vars` had been used alone.
index 539863b1f920f8f34ad9272907cbacbd35a7fcbd..e30c602f476da2da632d2cd04396d168c80ab233 100644 (file)
@@ -66,6 +66,12 @@ Steps to parse options
 non-option arguments in `argv[]`.
 `argc` is updated appropriately because of the assignment.
 +
+You can also pass NULL instead of a usage array as fourth parameter of
+parse_options(), to avoid displaying a help screen with usage info and
+option list.  This should only be done if necessary, e.g. to implement
+a limited parser for only a subset of the options that needs to be run
+before the full parser, which in turn shows the full help message.
++
 Flags are the bitwise-or of:
 
 `PARSE_OPT_KEEP_DASHDASH`::
@@ -77,6 +83,28 @@ Flags are the bitwise-or of:
        Using this flag, processing is stopped at the first non-option
        argument.
 
+`PARSE_OPT_KEEP_ARGV0`::
+       Keep the first argument, which contains the program name.  It's
+       removed from argv[] by default.
+
+`PARSE_OPT_KEEP_UNKNOWN`::
+       Keep unknown arguments instead of erroring out.  This doesn't
+       work for all combinations of arguments as users might expect
+       it to do.  E.g. if the first argument in `--unknown --known`
+       takes a value (which we can't know), the second one is
+       mistakenly interpreted as a known option.  Similarly, if
+       `PARSE_OPT_STOP_AT_NON_OPTION` is set, the second argument in
+       `--unknown value` will be mistakenly interpreted as a
+       non-option, not as a value belonging to the unknown option,
+       the parser early.  That's why parse_options() errors out if
+       both options are set.
+
+`PARSE_OPT_NO_INTERNAL_HELP`::
+       By default, parse_options() handles `-h`, `--help` and
+       `--help-all` internally, by showing a help screen.  This option
+       turns it off and allows one to add custom handlers for these
+       options, or to just leave them unknown.
+
 Data Structure
 --------------
 
@@ -170,7 +198,7 @@ The function must be defined in this form:
 
 The callback mechanism is as follows:
 
-* Inside `funct`, the only interesting member of the structure
+* Inside `func`, the only interesting member of the structure
   given by `opt` is the void pointer `opt->value`.
   `\*opt->value` will be the value that is saved into `var`, if you
   use `OPT_CALLBACK()`.
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 96af8977f6cae5382728f13116ea24ba2d130bef..dbbeb7e7c7856776450bc64321ff5f1de26202bb 100644 (file)
@@ -188,7 +188,7 @@ As you can see, a commit shows who made the latest change, what they
 did, and why.
 
 Every commit has a 40-hexdigit id, sometimes called the "object name" or the
-"SHA1 id", shown on the first line of the "git-show" output.  You can usually
+"SHA-1 id", shown on the first line of the "git show" output.  You can usually
 refer to a commit by a shorter name, such as a tag or a branch name, but this
 longer name can also be useful.  Most importantly, it is a globally unique
 name for this commit: so if you tell somebody else the object name (for
@@ -307,7 +307,7 @@ ref: refs/heads/master
 Examining an old version without creating a new branch
 ------------------------------------------------------
 
-The git-checkout command normally expects a branch head, but will also
+The `git checkout` command normally expects a branch head, but will also
 accept an arbitrary commit; for example, you can check out the commit
 referenced by a tag:
 
@@ -320,7 +320,7 @@ If you want to create a new branch from this checkout, you may do so
 HEAD is now at 427abfa... Linux v2.6.17
 ------------------------------------------------
 
-The HEAD then refers to the SHA1 of the commit instead of to a branch,
+The HEAD then refers to the SHA-1 of the commit instead of to a branch,
 and git branch shows that you are no longer on a branch:
 
 ------------------------------------------------
@@ -400,7 +400,7 @@ references with the same shorthand name, see the "SPECIFYING
 REVISIONS" section of linkgit:git-rev-parse[1].
 
 [[Updating-a-repository-With-git-fetch]]
-Updating a repository with git-fetch
+Updating a repository with git fetch
 ------------------------------------
 
 Eventually the developer cloned from will do additional work in her
@@ -427,7 +427,7 @@ $ git fetch linux-nfs
 -------------------------------------------------
 
 New remote-tracking branches will be stored under the shorthand name
-that you gave "git-remote add", in this case linux-nfs:
+that you gave "git remote add", in this case linux-nfs:
 
 -------------------------------------------------
 $ git branch -r
@@ -516,7 +516,7 @@ $ git bisect reset
 
 to return you to the branch you were on before.
 
-Note that the version which git-bisect checks out for you at each
+Note that the version which `git bisect` checks out for you at each
 point is just a suggestion, and you're free to try a different
 version if you think it would be a good idea.  For example,
 occasionally you may land on a commit that broke something unrelated;
@@ -592,11 +592,11 @@ In addition to HEAD, there are several other special names for
 commits:
 
 Merges (to be discussed later), as well as operations such as
-git-reset, which change the currently checked-out commit, generally
+`git reset`, which change the currently checked-out commit, generally
 set ORIG_HEAD to the value HEAD had before the current operation.
 
-The git-fetch operation always stores the head of the last fetched
-branch in FETCH_HEAD.  For example, if you run git fetch without
+The `git fetch` operation always stores the head of the last fetched
+branch in FETCH_HEAD.  For example, if you run `git fetch` without
 specifying a local branch as the target of the operation
 
 -------------------------------------------------
@@ -739,7 +739,7 @@ $ git log --pretty=oneline origin..mybranch | wc -l
 -------------------------------------------------
 
 Alternatively, you may often see this sort of thing done with the
-lower-level command linkgit:git-rev-list[1], which just lists the SHA1's
+lower-level command linkgit:git-rev-list[1], which just lists the SHA-1's
 of all the given commits:
 
 -------------------------------------------------
@@ -1073,9 +1073,9 @@ $ git diff
 
 shows the difference between the working tree and the index file.
 
-Note that "git-add" always adds just the current contents of a file
+Note that "git add" always adds just the current contents of a file
 to the index; further changes to the same file will be ignored unless
-you run git-add on the file again.
+you run `git add` on the file again.
 
 When you're ready, just run
 
@@ -1136,10 +1136,10 @@ Ignoring files
 A project will often generate files that you do 'not' want to track with git.
 This typically includes files generated by a build process or temporary
 backup files made by your editor. Of course, 'not' tracking files with git
-is just a matter of 'not' calling "`git-add`" on them. But it quickly becomes
+is just a matter of 'not' calling `git add` on them. But it quickly becomes
 annoying to have these untracked files lying around; e.g. they make
-"`git add .`" practically useless, and they keep showing up in the output of
-"`git status`".
+`git add .` practically useless, and they keep showing up in the output of
+`git status`.
 
 You can tell git to ignore certain files by creating a file called .gitignore
 in the top level of your working directory, with contents such as:
@@ -1349,7 +1349,7 @@ $ git add file.txt
 -------------------------------------------------
 
 the different stages of that file will be "collapsed", after which
-git-diff will (by default) no longer show diffs for that file.
+`git diff` will (by default) no longer show diffs for that file.
 
 [[undoing-a-merge]]
 Undoing a merge
@@ -1446,7 +1446,7 @@ Fixing a mistake by rewriting history
 
 If the problematic commit is the most recent commit, and you have not
 yet made that commit public, then you may just
-<<undoing-a-merge,destroy it using git-reset>>.
+<<undoing-a-merge,destroy it using `git reset`>>.
 
 Alternatively, you
 can edit the working directory and update the index to fix your
@@ -1474,7 +1474,7 @@ Checking out an old version of a file
 
 In the process of undoing a previous bad change, you may find it
 useful to check out an older version of a particular file using
-linkgit:git-checkout[1].  We've used git-checkout before to switch
+linkgit:git-checkout[1].  We've used `git checkout` before to switch
 branches, but it has quite different behavior if it is given a path
 name: the command
 
@@ -1542,7 +1542,7 @@ $ git gc
 -------------------------------------------------
 
 to recompress the archive.  This can be very time-consuming, so
-you may prefer to run git-gc when you are not doing other work.
+you may prefer to run `git gc` when you are not doing other work.
 
 
 [[ensuring-reliability]]
@@ -1634,7 +1634,7 @@ In some situations the reflog may not be able to save you.  For example,
 suppose you delete a branch, then realize you need the history it
 contained.  The reflog is also deleted; however, if you have not yet
 pruned the repository, then you may still be able to find the lost
-commits in the dangling objects that git-fsck reports.  See
+commits in the dangling objects that `git fsck` reports.  See
 <<dangling-objects>> for the details.
 
 -------------------------------------------------
@@ -1676,7 +1676,7 @@ Sharing development with others
 ===============================
 
 [[getting-updates-With-git-pull]]
-Getting updates with git-pull
+Getting updates with git pull
 -----------------------------
 
 After you clone a repository and make a few changes of your own, you
@@ -1722,7 +1722,7 @@ repository that you pulled from.
 <<fast-forwards,fast forward>>; instead, your branch will just be
 updated to point to the latest commit from the upstream branch.)
 
-The git-pull command can also be given "." as the "remote" repository,
+The `git pull` command can also be given "." as the "remote" repository,
 in which case it just merges in a branch from the current repository; so
 the commands
 
@@ -1795,7 +1795,7 @@ Public git repositories
 Another way to submit changes to a project is to tell the maintainer
 of that project to pull the changes from your repository using
 linkgit:git-pull[1].  In the section "<<getting-updates-With-git-pull,
-Getting updates with git-pull>>" we described this as a way to get
+Getting updates with `git pull`>>" we described this as a way to get
 updates from the "main" repository, but it works just as well in the
 other direction.
 
@@ -1847,7 +1847,7 @@ Setting up a public repository
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Assume your personal repository is in the directory ~/proj.  We
-first create a new clone of the repository and tell git-daemon that it
+first create a new clone of the repository and tell `git daemon` that it
 is meant to be public:
 
 -------------------------------------------------
@@ -1878,10 +1878,10 @@ repository>>", below.
 Otherwise, all you need to do is start linkgit:git-daemon[1]; it will
 listen on port 9418.  By default, it will allow access to any directory
 that looks like a git directory and contains the magic file
-git-daemon-export-ok.  Passing some directory paths as git-daemon
+git-daemon-export-ok.  Passing some directory paths as `git daemon`
 arguments will further restrict the exports to those paths.
 
-You can also run git-daemon as an inetd service; see the
+You can also run `git daemon` as an inetd service; see the
 linkgit:git-daemon[1] man page for details.  (See especially the
 examples section.)
 
@@ -1942,7 +1942,7 @@ or just
 $ git push ssh://yourserver.com/~you/proj.git master
 -------------------------------------------------
 
-As with git-fetch, git-push will complain if this does not result in a
+As with `git fetch`, `git push` will complain if this does not result in a
 <<fast-forwards,fast forward>>; see the following section for details on
 handling this case.
 
@@ -1952,7 +1952,7 @@ repository that has a checked-out working tree, but the working tree
 will not be updated by the push.  This may lead to unexpected results if
 the branch you push to is the currently checked-out branch!
 
-As with git-fetch, you may also set up configuration options to
+As with `git fetch`, you may also set up configuration options to
 save typing; so, for example, after
 
 -------------------------------------------------
@@ -1988,13 +1988,13 @@ error: failed to push to 'ssh://yourserver.com/~you/proj.git'
 
 This can happen, for example, if you:
 
-       - use `git-reset --hard` to remove already-published commits, or
-       - use `git-commit --amend` to replace already-published commits
+       - use `git reset --hard` to remove already-published commits, or
+       - use `git commit --amend` to replace already-published commits
          (as in <<fixing-a-mistake-by-rewriting-history>>), or
-       - use `git-rebase` to rebase any already-published commits (as
+       - use `git rebase` to rebase any already-published commits (as
          in <<using-git-rebase>>).
 
-You may force git-push to perform the update anyway by preceding the
+You may force `git push` to perform the update anyway by preceding the
 branch name with a plus sign:
 
 -------------------------------------------------
@@ -2036,7 +2036,7 @@ advantages over the central shared repository:
 
        - Git's ability to quickly import and merge patches allows a
          single maintainer to process incoming changes even at very
-         high rates.  And when that becomes too much, git-pull provides
+         high rates.  And when that becomes too much, `git pull` provides
          an easy way for that maintainer to delegate this job to other
          maintainers while still allowing optional review of incoming
          changes.
@@ -2404,7 +2404,7 @@ use them, and then explain some of the problems that can arise because
 you are rewriting history.
 
 [[using-git-rebase]]
-Keeping a patch series up to date using git-rebase
+Keeping a patch series up to date using git rebase
 --------------------------------------------------
 
 Suppose that you create a branch "mywork" on a remote-tracking branch
@@ -2468,9 +2468,9 @@ patches to the new mywork.  The result will look like:
 ................................................
 
 In the process, it may discover conflicts.  In that case it will stop
-and allow you to fix the conflicts; after fixing conflicts, use "git-add"
+and allow you to fix the conflicts; after fixing conflicts, use `git add`
 to update the index with those contents, and then, instead of
-running git-commit, just run
+running `git commit`, just run
 
 -------------------------------------------------
 $ git rebase --continue
@@ -2508,7 +2508,7 @@ with
 $ git tag bad mywork~5
 -------------------------------------------------
 
-(Either gitk or git-log may be useful for finding the commit.)
+(Either gitk or `git log` may be useful for finding the commit.)
 
 Then check out that commit, edit it, and rebase the rest of the series
 on top of it (note that we could check out the commit on a temporary
@@ -2549,12 +2549,12 @@ $ gitk origin..mywork &
 
 and browse through the list of patches in the mywork branch using gitk,
 applying them (possibly in a different order) to mywork-new using
-cherry-pick, and possibly modifying them as you go using `commit --amend`.
+cherry-pick, and possibly modifying them as you go using `git commit --amend`.
 The linkgit:git-gui[1] command may also help as it allows you to
 individually select diff hunks for inclusion in the index (by
 right-clicking on the diff hunk and choosing "Stage Hunk for Commit").
 
-Another technique is to use git-format-patch to create a series of
+Another technique is to use `git format-patch` to create a series of
 patches, then reset the state to before the patches:
 
 -------------------------------------------------
@@ -2662,7 +2662,7 @@ you know is that D is bad, that Z is good, and that
 linkgit:git-bisect[1] identifies C as the culprit, how will you
 figure out that the problem is due to this change in semantics?
 
-When the result of a git-bisect is a non-merge commit, you should
+When the result of a `git bisect` is a non-merge commit, you should
 normally be able to discover the problem by examining just that commit.
 Developers can make this easy by breaking their changes into small
 self-contained commits.  That won't help in the case above, however,
@@ -2725,7 +2725,7 @@ master branch.  In more detail:
 git fetch and fast-forwards
 ---------------------------
 
-In the previous example, when updating an existing branch, "git-fetch"
+In the previous example, when updating an existing branch, "git fetch"
 checks to make sure that the most recent commit on the remote
 branch is a descendant of the most recent commit on your copy of the
 branch before updating your copy of the branch to point at the new
@@ -2751,7 +2751,7 @@ resulting in a situation like:
             o--o--o <-- new head of the branch
 ................................................
 
-In this case, "git-fetch" will fail, and print out a warning.
+In this case, "git fetch" will fail, and print out a warning.
 
 In that case, you can still force git to update to the new head, as
 described in the following section.  However, note that in the
@@ -2760,7 +2760,7 @@ unless you've already created a reference of your own pointing to
 them.
 
 [[forcing-fetch]]
-Forcing git-fetch to do non-fast-forward updates
+Forcing git fetch to do non-fast-forward updates
 ------------------------------------------------
 
 If git fetch fails because the new head of a branch is not a
@@ -2865,8 +2865,8 @@ The Object Database
 We already saw in <<understanding-commits>> that all commits are stored
 under a 40-digit "object name".  In fact, all the information needed to
 represent the history of a project is stored in objects with such names.
-In each case the name is calculated by taking the SHA1 hash of the
-contents of the object.  The SHA1 hash is a cryptographic hash function.
+In each case the name is calculated by taking the SHA-1 hash of the
+contents of the object.  The SHA-1 hash is a cryptographic hash function.
 What that means to us is that it is impossible to find two different
 objects with the same name.  This has a number of advantages; among
 others:
@@ -2877,10 +2877,10 @@ others:
   same content stored in two repositories will always be stored under
   the same name.
 - Git can detect errors when it reads an object, by checking that the
-  object's name is still the SHA1 hash of its contents.
+  object's name is still the SHA-1 hash of its contents.
 
 (See <<object-details>> for the details of the object formatting and
-SHA1 calculation.)
+SHA-1 calculation.)
 
 There are four different types of objects: "blob", "tree", "commit", and
 "tag".
@@ -2926,9 +2926,9 @@ committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700
 
 As you can see, a commit is defined by:
 
-- a tree: The SHA1 name of a tree object (as defined below), representing
+- a tree: The SHA-1 name of a tree object (as defined below), representing
   the contents of a directory at a certain point in time.
-- parent(s): The SHA1 name of some number of commits which represent the
+- parent(s): The SHA-1 name of some number of commits which represent the
   immediately previous step(s) in the history of the project.  The
   example above has one parent; merge commits may have more than
   one.  A commit with no parents is called a "root" commit, and
@@ -2977,13 +2977,13 @@ $ git ls-tree fb3a8bdd0ce
 ------------------------------------------------
 
 As you can see, a tree object contains a list of entries, each with a
-mode, object type, SHA1 name, and name, sorted by name.  It represents
+mode, object type, SHA-1 name, and name, sorted by name.  It represents
 the contents of a single directory tree.
 
 The object type may be a blob, representing the contents of a file, or
 another tree, representing the contents of a subdirectory.  Since trees
-and blobs, like all other objects, are named by the SHA1 hash of their
-contents, two trees have the same SHA1 name if and only if their
+and blobs, like all other objects, are named by the SHA-1 hash of their
+contents, two trees have the same SHA-1 name if and only if their
 contents (including, recursively, the contents of all subdirectories)
 are identical.  This allows git to quickly determine the differences
 between two related tree objects, since it can ignore any entries with
@@ -3029,15 +3029,15 @@ currently checked out.
 Trust
 ~~~~~
 
-If you receive the SHA1 name of a blob from one source, and its contents
+If you receive the SHA-1 name of a blob from one source, and its contents
 from another (possibly untrusted) source, you can still trust that those
-contents are correct as long as the SHA1 name agrees.  This is because
-the SHA1 is designed so that it is infeasible to find different contents
+contents are correct as long as the SHA-1 name agrees.  This is because
+the SHA-1 is designed so that it is infeasible to find different contents
 that produce the same hash.
 
-Similarly, you need only trust the SHA1 name of a top-level tree object
+Similarly, you need only trust the SHA-1 name of a top-level tree object
 to trust the contents of the entire directory that it refers to, and if
-you receive the SHA1 name of a commit from a trusted source, then you
+you receive the SHA-1 name of a commit from a trusted source, then you
 can easily verify the entire history of commits reachable through
 parents of that commit, and all of those contents of the trees referred
 to by those commits.
@@ -3049,7 +3049,7 @@ that you trust that commit, and the immutability of the history of
 commits tells others that they can trust the whole history.
 
 In other words, you can easily validate a whole archive by just
-sending out a single email that tells the people the name (SHA1 hash)
+sending out a single email that tells the people the name (SHA-1 hash)
 of the top commit, and digitally sign that email using something
 like GPG/PGP.
 
@@ -3090,7 +3090,7 @@ How git stores objects efficiently: pack files
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Newly created objects are initially created in a file named after the
-object's SHA1 hash (stored in .git/objects).
+object's SHA-1 hash (stored in .git/objects).
 
 Unfortunately this system becomes inefficient once a project has a
 lot of objects.  Try this on an old project:
@@ -3131,7 +3131,7 @@ $ git prune
 
 to remove any of the "loose" objects that are now contained in the
 pack.  This will also remove any unreferenced objects (which may be
-created when, for example, you use "git-reset" to remove a commit).
+created when, for example, you use "git reset" to remove a commit).
 You can verify that the loose objects are gone by looking at the
 .git/objects directory or by running
 
@@ -3160,7 +3160,7 @@ branch still exists, as does everything it pointed to. The branch
 pointer itself just doesn't, since you replaced it with another one.
 
 There are also other situations that cause dangling objects. For
-example, a "dangling blob" may arise because you did a "git-add" of a
+example, a "dangling blob" may arise because you did a "git add" of a
 file, but then, before you actually committed it and made it part of the
 bigger picture, you changed something else in that file and committed
 that *updated* thing--the old state that you added originally ends up
@@ -3210,7 +3210,7 @@ Usually, dangling blobs and trees aren't very interesting. They're
 almost always the result of either being a half-way mergebase (the blob
 will often even have the conflict markers from a merge in it, if you
 have had conflicting merges that you fixed up by hand), or simply
-because you interrupted a "git-fetch" with ^C or something like that,
+because you interrupted a "git fetch" with ^C or something like that,
 leaving _some_ of the new objects in the object database, but just
 dangling and useless.
 
@@ -3225,9 +3225,9 @@ and they'll be gone. But you should only run "git prune" on a quiescent
 repository--it's kind of like doing a filesystem fsck recovery: you
 don't want to do that while the filesystem is mounted.
 
-(The same is true of "git-fsck" itself, btw, but since
-git-fsck never actually *changes* the repository, it just reports
-on what it found, git-fsck itself is never "dangerous" to run.
+(The same is true of "git fsck" itself, btw, but since
+`git fsck` never actually *changes* the repository, it just reports
+on what it found, `git fsck` itself is never 'dangerous' to run.
 Running it while somebody is actually changing the repository can cause
 confusing and scary messages, but it won't actually do anything bad. In
 contrast, running "git prune" while somebody is actively changing the
@@ -3297,7 +3297,7 @@ $ git hash-object -w somedirectory/myfile
 ------------------------------------------------
 
 which will create and store a blob object with the contents of
-somedirectory/myfile, and output the sha1 of that object.  if you're
+somedirectory/myfile, and output the SHA-1 of that object.  if you're
 extremely lucky it might be 4b9458b3786228369c63936db65827de3cc06200, in
 which case you've guessed right, and the corruption is fixed!
 
@@ -3359,7 +3359,7 @@ The index
 -----------
 
 The index is a binary file (generally kept in .git/index) containing a
-sorted list of path names, each with permissions and the SHA1 of a blob
+sorted list of path names, each with permissions and the SHA-1 of a blob
 object; linkgit:git-ls-files[1] can show you the contents of the index:
 
 -------------------------------------------------
@@ -3489,14 +3489,14 @@ done
 
 NOTE: Do not use local URLs here if you plan to publish your superproject!
 
-See what files `git-submodule` created:
+See what files `git submodule` created:
 
 -------------------------------------------------
 $ ls -a
 .  ..  .git  .gitmodules  a  b  c  d
 -------------------------------------------------
 
-The `git-submodule add <repo> <path>` command does a couple of things:
+The `git submodule add <repo> <path>` command does a couple of things:
 
 - It clones the submodule from <repo> to the given <path> under the
   current directory and by default checks out the master branch.
@@ -3542,7 +3542,7 @@ init` to add the submodule repository URLs to `.git/config`:
 $ git submodule init
 -------------------------------------------------
 
-Now use `git-submodule update` to clone the repositories and check out the
+Now use `git submodule update` to clone the repositories and check out the
 commits specified in the superproject:
 
 -------------------------------------------------
@@ -3552,8 +3552,8 @@ $ ls -a
 .  ..  .git  a.txt
 -------------------------------------------------
 
-One major difference between `git-submodule update` and `git-submodule add` is
-that `git-submodule update` checks out a specific commit, rather than the tip
+One major difference between `git submodule update` and `git submodule add` is
+that `git submodule update` checks out a specific commit, rather than the tip
 of a branch. It's like checking out a tag: the head is detached, so you're not
 working on a branch.
 
@@ -3754,7 +3754,7 @@ unsaved state that you might want to restore later!) your current
 index.  Normal operation is just
 
 -------------------------------------------------
-$ git read-tree <sha1 of tree>
+$ git read-tree <SHA-1 of tree>
 -------------------------------------------------
 
 and your index file will now be equivalent to the tree that you saved
@@ -3769,7 +3769,7 @@ You update your working directory from the index by "checking out"
 files. This is not a very common operation, since normally you'd just
 keep your files updated, and rather than write to your working
 directory, you'd tell the index files about the changes in your
-working directory (i.e. `git-update-index`).
+working directory (i.e. `git update-index`).
 
 However, if you decide to jump to a new version, or check out somebody
 else's version, or just restore a previous tree, you'd populate your
@@ -3782,7 +3782,7 @@ $ git checkout-index filename
 
 or, if you want to check out all of the index, use `-a`.
 
-NOTE! git-checkout-index normally refuses to overwrite old files, so
+NOTE! `git checkout-index` normally refuses to overwrite old files, so
 if you have an old version of the tree already checked out, you will
 need to use the "-f" flag ('before' the "-a" flag or the filename) to
 'force' the checkout.
@@ -3820,7 +3820,7 @@ $ git commit-tree <tree> -p <parent> [-p <parent2> ..]
 and then giving the reason for the commit on stdin (either through
 redirection from a pipe or file, or by just typing it at the tty).
 
-git-commit-tree will return the name of the object that represents
+`git commit-tree` will return the name of the object that represents
 that commit, and you should save it away for later use. Normally,
 you'd commit a new `HEAD` state, and while git doesn't care where you
 save the note about that state, in practice we tend to just write the
@@ -3889,7 +3889,7 @@ $ git cat-file blob|tree|commit|tag <objectname>
 
 to show its contents. NOTE! Trees have binary content, and as a result
 there is a special helper for showing that content, called
-`git-ls-tree`, which turns the binary content into a more easily
+`git ls-tree`, which turns the binary content into a more easily
 readable form.
 
 It's especially instructive to look at "commit" objects, since those
@@ -3978,13 +3978,13 @@ $ git ls-files --unmerged
 ------------------------------------------------
 
 Each line of the `git ls-files --unmerged` output begins with
-the blob mode bits, blob SHA1, 'stage number', and the
+the blob mode bits, blob SHA-1, 'stage number', and the
 filename.  The 'stage number' is git's way to say which tree it
 came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD`
 tree, and stage3 `$target` tree.
 
 Earlier we said that trivial merges are done inside
-`git-read-tree -m`.  For example, if the file did not change
+`git read-tree -m`.  For example, if the file did not change
 from `$orig` to `HEAD` nor `$target`, or if the file changed
 from `$orig` to `HEAD` and `$orig` to `$target` the same way,
 obviously the final outcome is what is in `HEAD`.  What the
@@ -4011,20 +4011,20 @@ $ mv -f hello.c~2 hello.c
 $ git update-index hello.c
 -------------------------------------------------
 
-When a path is in the "unmerged" state, running `git-update-index` for
+When a path is in the "unmerged" state, running `git update-index` for
 that path tells git to mark the path resolved.
 
 The above is the description of a git merge at the lowest level,
 to help you understand what conceptually happens under the hood.
-In practice, nobody, not even git itself, runs `git-cat-file` three times
-for this.  There is a `git-merge-index` program that extracts the
+In practice, nobody, not even git itself, runs `git cat-file` three times
+for this.  There is a `git merge-index` program that extracts the
 stages to temporary files and calls a "merge" script on it:
 
 -------------------------------------------------
 $ git merge-index git-merge-one-file hello.c
 -------------------------------------------------
 
-and that is what higher level `git-merge -s resolve` is implemented with.
+and that is what higher level `git merge -s resolve` is implemented with.
 
 [[hacking-git]]
 Hacking git
@@ -4045,12 +4045,12 @@ objects).  There are currently four different object types: "blob",
 Regardless of object type, all objects share the following
 characteristics: they are all deflated with zlib, and have a header
 that not only specifies their type, but also provides size information
-about the data in the object.  It's worth noting that the SHA1 hash
+about the data in the object.  It's worth noting that the SHA-1 hash
 that is used to name the object is the hash of the original data
 plus this header, so `sha1sum` 'file' does not match the object name
 for 'file'.
 (Historical note: in the dawn of the age of git the hash
-was the sha1 of the 'compressed' object.)
+was the SHA-1 of the 'compressed' object.)
 
 As a result, the general consistency of an object can always be tested
 independently of the contents or the type of the object: all objects can
@@ -4061,7 +4061,7 @@ size> {plus} <byte\0> {plus} <binary object data>.
 
 The structured objects can further have their structure and
 connectivity to other objects verified. This is generally done with
-the `git-fsck` program, which generates a full dependency graph
+the `git fsck` program, which generates a full dependency graph
 of all objects, and verifies their internal consistency (in addition
 to just verifying their superficial consistency through the hash).
 
@@ -4120,7 +4120,7 @@ functions like `get_sha1_basic()` or the likes.
 This is just to get you into the groove for the most libified part of Git:
 the revision walker.
 
-Basically, the initial version of `git-log` was a shell script:
+Basically, the initial version of `git log` was a shell script:
 
 ----------------------------------------------------------------
 $ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \
@@ -4129,20 +4129,20 @@ $ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \
 
 What does this mean?
 
-`git-rev-list` is the original version of the revision walker, which
+`git rev-list` is the original version of the revision walker, which
 _always_ printed a list of revisions to stdout.  It is still functional,
 and needs to, since most new Git programs start out as scripts using
-`git-rev-list`.
+`git rev-list`.
 
-`git-rev-parse` is not as important any more; it was only used to filter out
+`git rev-parse` is not as important any more; it was only used to filter out
 options that were relevant for the different plumbing commands that were
 called by the script.
 
-Most of what `git-rev-list` did is contained in `revision.c` and
+Most of what `git rev-list` did is contained in `revision.c` and
 `revision.h`.  It wraps the options in a struct named `rev_info`, which
 controls how and what revisions are walked, and more.
 
-The original job of `git-rev-parse` is now taken by the function
+The original job of `git rev-parse` is now taken by the function
 `setup_revisions()`, which parses the revisions and the common command line
 options for the revision walker. This information is stored in the struct
 `rev_info` for later consumption. You can do your own command line option
@@ -4155,7 +4155,7 @@ just have a look at the first implementation of `cmd_log()`; call
 `git show v1.3.0{tilde}155^2{tilde}4` and scroll down to that function (note that you
 no longer need to call `setup_pager()` directly).
 
-Nowadays, `git-log` is a builtin, which means that it is _contained_ in the
+Nowadays, `git log` is a builtin, which means that it is _contained_ in the
 command `git`.  The source side of a builtin is
 
 - a function called `cmd_<bla>`, typically defined in `builtin-<bla>.c`,
@@ -4171,7 +4171,7 @@ since they share quite a bit of code.  In that case, the commands which are
 _not_ named like the `.c` file in which they live have to be listed in
 `BUILT_INS` in the `Makefile`.
 
-`git-log` looks more complicated in C than it does in the original script,
+`git log` looks more complicated in C than it does in the original script,
 but that allows for a much greater flexibility and performance.
 
 Here again it is a good point to take a pause.
@@ -4182,9 +4182,9 @@ the organization of Git (after you know the basic concepts).
 So, think about something which you are interested in, say, "how can I
 access a blob just knowing the object name of it?".  The first step is to
 find a Git command with which you can do it.  In this example, it is either
-`git-show` or `git-cat-file`.
+`git show` or `git cat-file`.
 
-For the sake of clarity, let's stay with `git-cat-file`, because it
+For the sake of clarity, let's stay with `git cat-file`, because it
 
 - is plumbing, and
 
@@ -4198,7 +4198,7 @@ it does.
 ------------------------------------------------------------------
         git_config(git_default_config);
         if (argc != 3)
-                usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
+               usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>");
         if (get_sha1(argv[2], sha1))
                 die("Not a valid object name %s", argv[2]);
 ------------------------------------------------------------------
@@ -4243,10 +4243,10 @@ To find out how the result can be used, just read on in `cmd_cat_file()`:
 -----------------------------------
 
 Sometimes, you do not know where to look for a feature.  In many such cases,
-it helps to search through the output of `git log`, and then `git-show` the
+it helps to search through the output of `git log`, and then `git show` the
 corresponding commit.
 
-Example: If you know that there was some test case for `git-bundle`, but
+Example: If you know that there was some test case for `git bundle`, but
 do not remember where it was (yes, you _could_ `git grep bundle t/`, but that
 does not illustrate the point!):
 
@@ -4530,7 +4530,7 @@ The basic requirements:
 - Whenever possible, section headings should clearly describe the task
   they explain how to do, in language that requires no more knowledge
   than necessary: for example, "importing patches into a project" rather
-  than "the git-am command"
+  than "the `git am` command"
 
 Think about how to create a clear chapter dependency graph that will
 allow people to get to important topics without necessarily reading
index e5e62ef0de781d7b96ff21017a912cb3d9aa1da5..d292e3a2d38a935a0f06b708d9d0b4a36d58ba66 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.6.1.4
+DEF_VER=v1.6.3.1
 
 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..26d180cc5422c968f5bde4ae4a300347b3f33e02 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
@@ -123,6 +126,12 @@ all::
 # randomly break unless your underlying filesystem supports those sub-second
 # times (my ext3 doesn't).
 #
+# Define USE_ST_TIMESPEC if your "struct stat" uses "st_ctimespec" instead of
+# "st_ctim"
+#
+# Define NO_NSEC if your "struct stat" does not have "st_ctim.tv_nsec"
+# available.  This automatically turns USE_NSEC off.
+#
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-index perspective.
 #
@@ -136,6 +145,8 @@ all::
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
+# Define NO_PERL if you do not want Perl scripts or libraries at all.
+#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -156,6 +167,14 @@ all::
 # Define NO_EXTERNAL_GREP if you don't want "git grep" to ever call
 # your external grep (e.g., if your system lacks grep, if its grep is
 # broken, or spawning external process is slower than built-in grep git has).
+#
+# Define UNRELIABLE_FSTAT if your system's fstat does not return the same
+# information on a not yet closed file that lstat would return for the same
+# file after it was closed.
+#
+# Define OBJECT_CREATION_USES_RENAMES if your operating systems has problems
+# when hardlinking a file to another name and unlinking the original file right
+# away (some NTFS drivers seem to zero the contents in that scenario).
 
 GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -179,28 +198,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 +244,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
@@ -250,14 +273,28 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 BASIC_CFLAGS =
 BASIC_LDFLAGS =
 
+# Guard against environment variables
+BUILTIN_OBJS =
+BUILT_INS =
+COMPAT_CFLAGS =
+COMPAT_OBJS =
+LIB_H =
+LIB_OBJS =
+PROGRAMS =
+SCRIPT_PERL =
+SCRIPT_SH =
+TEST_PROGRAMS =
+
 SCRIPT_SH += git-am.sh
 SCRIPT_SH += git-bisect.sh
+SCRIPT_SH += git-difftool--helper.sh
 SCRIPT_SH += git-filter-branch.sh
 SCRIPT_SH += git-lost-found.sh
 SCRIPT_SH += git-merge-octopus.sh
 SCRIPT_SH += git-merge-one-file.sh
 SCRIPT_SH += git-merge-resolve.sh
 SCRIPT_SH += git-mergetool.sh
+SCRIPT_SH += git-mergetool--lib.sh
 SCRIPT_SH += git-parse-remote.sh
 SCRIPT_SH += git-pull.sh
 SCRIPT_SH += git-quiltimport.sh
@@ -271,6 +308,7 @@ SCRIPT_SH += git-submodule.sh
 SCRIPT_SH += git-web--browse.sh
 
 SCRIPT_PERL += git-add--interactive.perl
+SCRIPT_PERL += git-difftool.perl
 SCRIPT_PERL += git-archimport.perl
 SCRIPT_PERL += git-cvsexportcommit.perl
 SCRIPT_PERL += git-cvsimport.perl
@@ -289,7 +327,6 @@ EXTRA_PROGRAMS =
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS += $(EXTRA_PROGRAMS)
 PROGRAMS += git-fast-import$X
-PROGRAMS += git-fetch-pack$X
 PROGRAMS += git-hash-object$X
 PROGRAMS += git-index-pack$X
 PROGRAMS += git-merge-index$X
@@ -298,7 +335,6 @@ PROGRAMS += git-mktag$X
 PROGRAMS += git-mktree$X
 PROGRAMS += git-pack-redundant$X
 PROGRAMS += git-patch-id$X
-PROGRAMS += git-send-pack$X
 PROGRAMS += git-shell$X
 PROGRAMS += git-show-index$X
 PROGRAMS += git-unpack-file$X
@@ -310,8 +346,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
@@ -328,7 +364,7 @@ BUILT_INS += git-whatchanged$X
 ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
 
 # what 'all' will build but not install in gitexecdir
-OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
+OTHER_PROGRAMS = git$X
 
 # Set paths to tools early so that they can be used for version tests.
 ifndef SHELL_PATH
@@ -350,8 +386,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 +412,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 +423,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
@@ -406,6 +443,7 @@ LIB_OBJS += archive-tar.o
 LIB_OBJS += archive-zip.o
 LIB_OBJS += attr.o
 LIB_OBJS += base85.o
+LIB_OBJS += bisect.o
 LIB_OBJS += blob.o
 LIB_OBJS += branch.o
 LIB_OBJS += bundle.o
@@ -427,8 +465,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 +498,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 +514,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 +530,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,12 +539,12 @@ 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
 BUILTIN_OBJS += builtin-apply.o
 BUILTIN_OBJS += builtin-archive.o
+BUILTIN_OBJS += builtin-bisect--helper.o
 BUILTIN_OBJS += builtin-blame.o
 BUILTIN_OBJS += builtin-branch.o
 BUILTIN_OBJS += builtin-bundle.o
@@ -640,12 +680,15 @@ 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
+       USE_ST_TIMESPEC = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -695,6 +738,7 @@ ifeq ($(uname_S),FreeBSD)
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
        DIR_HAS_BSD_GROUP_SEMANTICS = YesPlease
+       USE_ST_TIMESPEC = YesPlease
        THREADED_DELTA_SEARCH = YesPlease
        ifeq ($(shell expr "$(uname_R)" : '4\.'),2)
                PTHREAD_LIBS = -pthread
@@ -705,6 +749,7 @@ endif
 ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
        NO_MEMMEM = YesPlease
+       USE_ST_TIMESPEC = YesPlease
        NEEDS_LIBICONV = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
@@ -717,12 +762,14 @@ ifeq ($(uname_S),NetBSD)
        BASIC_CFLAGS += -I/usr/pkg/include
        BASIC_LDFLAGS += -L/usr/pkg/lib $(CC_LD_DYNPATH)/usr/pkg/lib
        THREADED_DELTA_SEARCH = YesPlease
+       USE_ST_TIMESPEC = YesPlease
 endif
 ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
        NO_MEMMEM = YesPlease
        NO_MKDTEMP = YesPlease
        NO_STRLCPY = YesPlease
+       NO_NSEC = YesPlease
        FREAD_READS_DIRECTORIES = UnfortunatelyYes
        INTERNAL_QSORT = UnfortunatelyYes
        NEEDS_LIBICONV=YesPlease
@@ -763,9 +810,9 @@ ifeq ($(uname_S),HP-UX)
 endif
 ifneq (,$(findstring CYGWIN,$(uname_S)))
        COMPAT_OBJS += compat/cygwin.o
+       UNRELIABLE_FSTAT = UnfortunatelyYes
 endif
 ifneq (,$(findstring MINGW,$(uname_S)))
-       NO_MMAP = YesPlease
        NO_PREAD = YesPlease
        NO_OPENSSL = YesPlease
        NO_CURL = YesPlease
@@ -785,17 +832,19 @@ 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
+       NO_NSEC = YesPlease
+       USE_WIN32_MMAP = YesPlease
+       UNRELIABLE_FSTAT = UnfortunatelyYes
+       OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/regex -Icompat/fnmatch
        COMPAT_CFLAGS += -DSNPRINTF_SIZE_CORR=1
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        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 +866,7 @@ ifeq ($(uname_S),Darwin)
                        BASIC_LDFLAGS += -L/opt/local/lib
                endif
        endif
+       PTHREAD_LIBS =
 endif
 
 ifndef CC_LD_DYNPATH
@@ -849,7 +899,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
 
@@ -905,6 +960,15 @@ endif
 ifdef NO_ST_BLOCKS_IN_STRUCT_STAT
        BASIC_CFLAGS += -DNO_ST_BLOCKS_IN_STRUCT_STAT
 endif
+ifdef USE_NSEC
+       BASIC_CFLAGS += -DUSE_NSEC
+endif
+ifdef USE_ST_TIMESPEC
+       BASIC_CFLAGS += -DUSE_ST_TIMESPEC
+endif
+ifdef NO_NSEC
+       BASIC_CFLAGS += -DNO_NSEC
+endif
 ifdef NO_C99_FORMAT
        BASIC_CFLAGS += -DNO_C99_FORMAT
 endif
@@ -952,6 +1016,14 @@ endif
 ifdef NO_MMAP
        COMPAT_CFLAGS += -DNO_MMAP
        COMPAT_OBJS += compat/mmap.o
+else
+       ifdef USE_WIN32_MMAP
+               COMPAT_CFLAGS += -DUSE_WIN32_MMAP
+               COMPAT_OBJS += compat/win32mmap.o
+       endif
+endif
+ifdef OBJECT_CREATION_USES_RENAMES
+       COMPAT_CFLAGS += -DOBJECT_CREATION_MODE=1
 endif
 ifdef NO_PREAD
        COMPAT_CFLAGS += -DNO_PREAD
@@ -1027,6 +1099,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 =
@@ -1045,11 +1120,18 @@ endif
 ifdef NO_EXTERNAL_GREP
        BASIC_CFLAGS += -DNO_EXTERNAL_GREP
 endif
+ifdef UNRELIABLE_FSTAT
+       BASIC_CFLAGS += -DUNRELIABLE_FSTAT
+endif
 
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
 endif
 
+ifeq ($(PERL_PATH),)
+NO_PERL=NoThanks
+endif
+
 QUIET_SUBDIR0  = +$(MAKE) -C # space to separate -C and subdir
 QUIET_SUBDIR1  =
 
@@ -1086,6 +1168,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))
@@ -1123,7 +1206,9 @@ ifndef NO_TCLTK
        $(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) gitexecdir='$(gitexec_instdir_SQ)' all
        $(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
 endif
+ifndef NO_PERL
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+endif
        $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
 
 please_set_SHELL_PATH_to_a_more_modern_shell:
@@ -1136,6 +1221,7 @@ strip: $(PROGRAMS) git$X
 
 git.o: git.c common-cmds.h GIT-CFLAGS
        $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
+               '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
                $(ALL_CFLAGS) -c $(filter %.c,$^)
 
 git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
@@ -1170,6 +1256,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
        chmod +x $@+ && \
        mv $@+ $@
 
+ifndef NO_PERL
 $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 
 perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
@@ -1191,6 +1278,7 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        chmod +x $@+ && \
        mv $@+ $@
 
+OTHER_PROGRAMS += gitweb/gitweb.cgi
 gitweb/gitweb.cgi: gitweb/gitweb.perl
        $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
@@ -1229,6 +1317,15 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
            $@.sh > $@+ && \
        chmod +x $@+ && \
        mv $@+ $@
+else # NO_PERL
+$(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
+       $(QUIET_GEN)$(RM) $@ $@+ && \
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+           -e 's|@@REASON@@|NO_PERL=$(NO_PERL)|g' \
+           unimplemented.sh >$@+ && \
+       chmod +x $@+ && \
+       mv $@+ $@
+endif # NO_PERL
 
 configure: configure.ac
        $(QUIET_GEN)$(RM) $@ $<+ && \
@@ -1251,7 +1348,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 +1389,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 +1409,9 @@ html:
 info:
        $(MAKE) -C Documentation info
 
+pdf:
+       $(MAKE) -C Documentation pdf
+
 TAGS:
        $(RM) TAGS
        $(FIND) . -name '*.[hcS]' -print | xargs etags -a
@@ -1336,6 +1441,8 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
        @echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
        @echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
+       @echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
+       @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
 
 ### Detect Tck/Tk interpreter path changes
 ifndef NO_TCLTK
@@ -1353,7 +1460,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 +1483,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 +1516,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
@@ -1418,23 +1537,27 @@ install: all
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)'
        $(INSTALL) git$X git-upload-pack$X git-receive-pack$X git-upload-archive$X git-shell$X git-cvsserver '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+ifndef NO_PERL
        $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install
+endif
 ifndef NO_TCLTK
        $(MAKE) -C gitk-git install
        $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install
 endif
 ifneq (,$X)
-       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
+       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), test '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p' -ef '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p$X' || $(RM) '$(DESTDIR_SQ)$(gitexec_instdir_SQ)/$p';)
 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 +1572,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
 
@@ -1521,9 +1647,11 @@ clean:
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
-       $(RM) gitweb/gitweb.cgi
        $(MAKE) -C Documentation/ clean
+ifndef NO_PERL
+       $(RM) gitweb/gitweb.cgi
        $(MAKE) -C perl clean
+endif
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
 ifndef NO_TCLTK
@@ -1596,3 +1724,27 @@ check-docs::
 check-builtins::
        ./check-builtins.sh
 
+### Test suite coverage testing
+#
+.PHONY: coverage coverage-clean coverage-build coverage-report
+
+coverage:
+       $(MAKE) coverage-build
+       $(MAKE) coverage-report
+
+coverage-clean:
+       rm -f *.gcda *.gcno
+
+COVERAGE_CFLAGS = $(CFLAGS) -O0 -ftest-coverage -fprofile-arcs
+COVERAGE_LDFLAGS = $(CFLAGS)  -O0 -lgcov
+
+coverage-build: coverage-clean
+       $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" all
+       $(MAKE) CFLAGS="$(COVERAGE_CFLAGS)" LDFLAGS="$(COVERAGE_LDFLAGS)" \
+               -j1 test
+
+coverage-report:
+       gcov -b *.c
+       grep '^function.*called 0 ' *.c.gcov \
+               | sed -e 's/\([^:]*\)\.gcov: *function \([^ ]*\) called.*/\1: \2/' \
+               | tee coverage-untested-functions
diff --git a/README b/README
index 5fa41b7a18942a68bacb7b488984bdf98f6dfd1a..c932ab310510c84c3e69107b458a228db37cb1f6 100644 (file)
--- a/README
+++ b/README
@@ -24,10 +24,18 @@ It was originally written by Linus Torvalds with help of a group of
 hackers around the net. It is currently maintained by Junio C Hamano.
 
 Please read the file INSTALL for installation instructions.
+
 See Documentation/gittutorial.txt to get started, then see
-Documentation/everyday.txt for a useful minimum set of commands,
-and "man git-commandname" for documentation of each command.
-CVS users may also want to read Documentation/cvs-migration.txt.
+Documentation/everyday.txt for a useful minimum set of commands, and
+Documentation/git-commandname.txt for documentation of each command.
+If git has been correctly installed, then the tutorial can also be
+read with "man gittutorial" or "git help tutorial", and the
+documentation of each command with "man git-commandname" or "git help
+commandname".
+
+CVS users may also want to read Documentation/gitcvs-migration.txt
+("man gitcvs-migration" or "git help cvs-migration" if git is
+installed).
 
 Many Git online resources are accessible from http://git.or.cz/
 including full documentation and Git related tools.
index 56936add354167e7dac8eb4f8b053572c92c47f8..0f6a588f1da39cb249a6e2ae841022245f949a40 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.6.1.4.txt
\ No newline at end of file
+Documentation/RelNotes-1.6.3.1.txt
\ No newline at end of file
diff --git a/alias.c b/alias.c
index ccb1108c94436035d0da8b1d6f08f859b68294a3..372b7d809301f9e3e936459e405cd6f2627bd4a9 100644 (file)
--- a/alias.c
+++ b/alias.c
@@ -27,7 +27,7 @@ int split_cmdline(char *cmdline, const char ***argv)
        int src, dst, count = 0, size = 16;
        char quoted = 0;
 
-       *argv = xmalloc(sizeof(char*) * size);
+       *argv = xmalloc(sizeof(char *) * size);
 
        /* split alias_string */
        (*argv)[count++] = cmdline;
@@ -38,10 +38,7 @@ int split_cmdline(char *cmdline, const char ***argv)
                        while (cmdline[++src]
                                        && isspace(cmdline[src]))
                                ; /* skip */
-                       if (count >= size) {
-                               size += 16;
-                               *argv = xrealloc(*argv, sizeof(char*) * size);
-                       }
+                       ALLOC_GROW(*argv, count+1, size);
                        (*argv)[count++] = cmdline + dst;
                } else if (!quoted && (c == '\'' || c == '"')) {
                        quoted = c;
@@ -72,6 +69,9 @@ int split_cmdline(char *cmdline, const char ***argv)
                return error("unclosed quote");
        }
 
+       ALLOC_GROW(*argv, count+1, size);
+       (*argv)[count] = NULL;
+
        return count;
 }
 
diff --git a/alloc.c b/alloc.c
index 216c23a6f854c614d38c743cd7687a37f304161b..6ef6753d180afad29bc335854150c824b9de8a18 100644 (file)
--- a/alloc.c
+++ b/alloc.c
@@ -57,7 +57,7 @@ DEFINE_ALLOCATOR(object, union any_object)
 #define SZ_FMT "%zu"
 #endif
 
-static void report(const charname, unsigned int count, size_t size)
+static void report(const char *name, unsigned int count, size_t size)
 {
     fprintf(stderr, "%10s: %8u (" SZ_FMT " kB)\n", name, count, size);
 }
index ba890ebdecd8672aeb32757605c8a2976fa21161..cee06ce3cbc1819008337633ed0753638c423ac5 100644 (file)
@@ -180,7 +180,7 @@ static int write_tar_entry(struct archiver_args *args,
 
        sprintf(header.mode, "%07o", mode & 07777);
        sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
-       sprintf(header.mtime, "%011lo", args->time);
+       sprintf(header.mtime, "%011lo", (unsigned long) args->time);
 
        sprintf(header.uid, "%07o", 0);
        sprintf(header.gid, "%07o", 0);
index 9ac455d889b72deba8c949da1d9efe2be3a50244..b2b90d31700d3aa50a3ac0626c1a8561c37c49b7 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -4,6 +4,7 @@
 #include "attr.h"
 #include "archive.h"
 #include "parse-options.h"
+#include "unpack-trees.h"
 
 static char const * const archive_usage[] = {
        "git archive [options] <tree-ish> [path...]",
@@ -132,7 +133,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,
@@ -150,6 +151,8 @@ int write_archive_entries(struct archiver_args *args,
                write_archive_entry_fn_t write_entry)
 {
        struct archiver_context context;
+       struct unpack_trees_options opts;
+       struct tree_desc t;
        int err;
 
        if (args->baselen > 0 && args->base[args->baselen - 1] == '/') {
@@ -168,6 +171,22 @@ int write_archive_entries(struct archiver_args *args,
        context.args = args;
        context.write_entry = write_entry;
 
+       /*
+        * Setup index and instruct attr to read index only
+        */
+       if (!args->worktree_attributes) {
+               memset(&opts, 0, sizeof(opts));
+               opts.index_only = 1;
+               opts.head_idx = -1;
+               opts.src_index = &the_index;
+               opts.dst_index = &the_index;
+               opts.fn = oneway_merge;
+               init_tree_desc(&t, args->tree->buffer, args->tree->size);
+               if (unpack_trees(1, &t, &opts))
+                       return -1;
+               git_attr_set_direction(GIT_ATTR_INDEX, &the_index);
+       }
+
        err =  read_tree_recursive(args->tree, args->base, args->baselen, 0,
                        args->pathspec, write_archive_entry, &context);
        if (err == READ_TREE_RECURSIVE)
@@ -253,15 +272,21 @@ static int parse_archive_args(int argc, const char **argv,
        const char *base = NULL;
        const char *remote = NULL;
        const char *exec = NULL;
+       const char *output = NULL;
        int compression_level = -1;
        int verbose = 0;
        int i;
        int list = 0;
+       int worktree_attributes = 0;
        struct option opts[] = {
                OPT_GROUP(""),
                OPT_STRING(0, "format", &format, "fmt", "archive format"),
                OPT_STRING(0, "prefix", &base, "prefix",
                        "prepend prefix to each pathname in the archive"),
+               OPT_STRING(0, "output", &output, "file",
+                       "write the archive to this file"),
+               OPT_BOOLEAN(0, "worktree-attributes", &worktree_attributes,
+                       "read .gitattributes in working directory"),
                OPT__VERBOSE(&verbose),
                OPT__COMPR('0', &compression_level, "store only", 0),
                OPT__COMPR('1', &compression_level, "compress faster", 1),
@@ -290,6 +315,8 @@ static int parse_archive_args(int argc, const char **argv,
                die("Unexpected option --remote");
        if (exec)
                die("Option --exec can only be used together with --remote");
+       if (output)
+               die("Unexpected option --output");
 
        if (!base)
                base = "";
@@ -319,6 +346,7 @@ static int parse_archive_args(int argc, const char **argv,
        args->verbose = verbose;
        args->base = base;
        args->baselen = strlen(base);
+       args->worktree_attributes = worktree_attributes;
 
        return argc;
 }
index 0b15b35143fffcc13764e4e668ee452b191cc609..038ac353d40567029403e3e7a2933af73a130720 100644 (file)
--- a/archive.h
+++ b/archive.h
@@ -10,6 +10,7 @@ struct archiver_args {
        time_t time;
        const char **pathspec;
        unsigned int verbose : 1;
+       unsigned int worktree_attributes : 1;
        int compression_level;
 };
 
diff --git a/attr.c b/attr.c
index 17f6a4dca521d9690377f2e93a0192d8a874d2ad..98eb636f13d314c20e18e646c074e2511b3c891c 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -1,3 +1,4 @@
+#define NO_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
 #include "attr.h"
 
@@ -223,7 +224,7 @@ static struct match_attr *parse_attr_line(const char *line, const char *src,
                if (is_macro)
                        res->u.attr = git_attr(name, namelen);
                else {
-                       res->u.pattern = (char*)&(res->state[num_attr]);
+                       res->u.pattern = (char *)&(res->state[num_attr]);
                        memcpy(res->u.pattern, name, namelen);
                        res->u.pattern[namelen] = 0;
                }
@@ -274,7 +275,7 @@ static void free_attr_elem(struct attr_stack *e)
                            setto == ATTR__UNKNOWN)
                                ;
                        else
-                               free((char*) setto);
+                               free((char *) setto);
                }
                free(a);
        }
@@ -318,6 +319,9 @@ static struct attr_stack *read_attr_from_array(const char **list)
        return res;
 }
 
+static enum git_attr_direction direction;
+static struct index_state *use_index;
+
 static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
 {
        FILE *fp = fopen(path, "r");
@@ -340,9 +344,10 @@ static void *read_index_data(const char *path)
        unsigned long sz;
        enum object_type type;
        void *data;
+       struct index_state *istate = use_index ? use_index : &the_index;
 
        len = strlen(path);
-       pos = cache_name_pos(path, len);
+       pos = index_name_pos(istate, path, len);
        if (pos < 0) {
                /*
                 * We might be in the middle of a merge, in which
@@ -350,15 +355,15 @@ static void *read_index_data(const char *path)
                 */
                int i;
                for (i = -pos - 1;
-                    (pos < 0 && i < active_nr &&
-                     !strcmp(active_cache[i]->name, path));
+                    (pos < 0 && i < istate->cache_nr &&
+                     !strcmp(istate->cache[i]->name, path));
                     i++)
-                       if (ce_stage(active_cache[i]) == 2)
+                       if (ce_stage(istate->cache[i]) == 2)
                                pos = i;
        }
        if (pos < 0)
                return NULL;
-       data = read_sha1_file(active_cache[pos]->sha1, &type, &sz);
+       data = read_sha1_file(istate->cache[pos]->sha1, &type, &sz);
        if (!data || type != OBJ_BLOB) {
                free(data);
                return NULL;
@@ -366,27 +371,17 @@ static void *read_index_data(const char *path)
        return data;
 }
 
-static struct attr_stack *read_attr(const char *path, int macro_ok)
+static struct attr_stack *read_attr_from_index(const char *path, int macro_ok)
 {
        struct attr_stack *res;
        char *buf, *sp;
        int lineno = 0;
 
-       res = read_attr_from_file(path, macro_ok);
-       if (res)
-               return res;
-
-       res = xcalloc(1, sizeof(*res));
-
-       /*
-        * There is no checked out .gitattributes file there, but
-        * we might have it in the index.  We allow operation in a
-        * sparsely checked out work tree, so read from it.
-        */
        buf = read_index_data(path);
        if (!buf)
-               return res;
+               return NULL;
 
+       res = xcalloc(1, sizeof(*res));
        for (sp = buf; *sp; ) {
                char *ep;
                int more;
@@ -401,6 +396,32 @@ static struct attr_stack *read_attr(const char *path, int macro_ok)
        return res;
 }
 
+static struct attr_stack *read_attr(const char *path, int macro_ok)
+{
+       struct attr_stack *res;
+
+       if (direction == GIT_ATTR_CHECKOUT) {
+               res = read_attr_from_index(path, macro_ok);
+               if (!res)
+                       res = read_attr_from_file(path, macro_ok);
+       }
+       else if (direction == GIT_ATTR_CHECKIN) {
+               res = read_attr_from_file(path, macro_ok);
+               if (!res)
+                       /*
+                        * There is no checked out .gitattributes file there, but
+                        * we might have it in the index.  We allow operation in a
+                        * sparsely checked out work tree, so read from it.
+                        */
+                       res = read_attr_from_index(path, macro_ok);
+       }
+       else
+               res = read_attr_from_index(path, macro_ok);
+       if (!res)
+               res = xcalloc(1, sizeof(*res));
+       return res;
+}
+
 #if DEBUG_ATTR
 static void debug_info(const char *what, struct attr_stack *elem)
 {
@@ -428,6 +449,15 @@ static void debug_set(const char *what, const char *match, struct git_attr *attr
 #define debug_set(a,b,c,d) do { ; } while (0)
 #endif
 
+static void drop_attr_stack(void)
+{
+       while (attr_stack) {
+               struct attr_stack *elem = attr_stack;
+               attr_stack = elem->prev;
+               free_attr_elem(elem);
+       }
+}
+
 static void bootstrap_attr_stack(void)
 {
        if (!attr_stack) {
@@ -438,7 +468,7 @@ static void bootstrap_attr_stack(void)
                elem->prev = attr_stack;
                attr_stack = elem;
 
-               if (!is_bare_repository()) {
+               if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
                        elem = read_attr(GITATTRIBUTES_FILE, 1);
                        elem->origin = strdup("");
                        elem->prev = attr_stack;
@@ -505,7 +535,7 @@ static void prepare_attr_stack(const char *path, int dirlen)
        /*
         * Read from parent directories and push them down
         */
-       if (!is_bare_repository()) {
+       if (!is_bare_repository() || direction == GIT_ATTR_INDEX) {
                while (1) {
                        char *cp;
 
@@ -642,3 +672,16 @@ int git_checkattr(const char *path, int num, struct git_attr_check *check)
 
        return 0;
 }
+
+void git_attr_set_direction(enum git_attr_direction new, struct index_state *istate)
+{
+       enum git_attr_direction old = direction;
+
+       if (is_bare_repository() && new != GIT_ATTR_INDEX)
+               die("BUG: non-INDEX attr direction in a bare repo");
+
+       direction = new;
+       if (new != old)
+               drop_attr_stack();
+       use_index = istate;
+}
diff --git a/attr.h b/attr.h
index f1c2038b0923d3130937eef965667204a8634e6d..69b5767ebc2189a8bf9d98ff88c1885ec8fcdb7d 100644 (file)
--- a/attr.h
+++ b/attr.h
@@ -31,4 +31,11 @@ struct git_attr_check {
 
 int git_checkattr(const char *path, int, struct git_attr_check *);
 
+enum git_attr_direction {
+       GIT_ATTR_CHECKIN,
+       GIT_ATTR_CHECKOUT,
+       GIT_ATTR_INDEX,
+};
+void git_attr_set_direction(enum git_attr_direction, struct index_state *);
+
 #endif /* ATTR_H */
diff --git a/bisect.c b/bisect.c
new file mode 100644 (file)
index 0000000..58f7e6f
--- /dev/null
+++ b/bisect.c
@@ -0,0 +1,556 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "refs.h"
+#include "list-objects.h"
+#include "quote.h"
+#include "sha1-lookup.h"
+#include "bisect.h"
+
+static unsigned char (*skipped_sha1)[20];
+static int skipped_sha1_nr;
+static int skipped_sha1_alloc;
+
+static const char **rev_argv;
+static int rev_argv_nr;
+static int rev_argv_alloc;
+
+/* bits #0-15 in revision.h */
+
+#define COUNTED                (1u<<16)
+
+/*
+ * This is a truly stupid algorithm, but it's only
+ * used for bisection, and we just don't care enough.
+ *
+ * We care just barely enough to avoid recursing for
+ * non-merge entries.
+ */
+static int count_distance(struct commit_list *entry)
+{
+       int nr = 0;
+
+       while (entry) {
+               struct commit *commit = entry->item;
+               struct commit_list *p;
+
+               if (commit->object.flags & (UNINTERESTING | COUNTED))
+                       break;
+               if (!(commit->object.flags & TREESAME))
+                       nr++;
+               commit->object.flags |= COUNTED;
+               p = commit->parents;
+               entry = p;
+               if (p) {
+                       p = p->next;
+                       while (p) {
+                               nr += count_distance(p);
+                               p = p->next;
+                       }
+               }
+       }
+
+       return nr;
+}
+
+static void clear_distance(struct commit_list *list)
+{
+       while (list) {
+               struct commit *commit = list->item;
+               commit->object.flags &= ~COUNTED;
+               list = list->next;
+       }
+}
+
+#define DEBUG_BISECT 0
+
+static inline int weight(struct commit_list *elem)
+{
+       return *((int*)(elem->item->util));
+}
+
+static inline void weight_set(struct commit_list *elem, int weight)
+{
+       *((int*)(elem->item->util)) = weight;
+}
+
+static int count_interesting_parents(struct commit *commit)
+{
+       struct commit_list *p;
+       int count;
+
+       for (count = 0, p = commit->parents; p; p = p->next) {
+               if (p->item->object.flags & UNINTERESTING)
+                       continue;
+               count++;
+       }
+       return count;
+}
+
+static inline int halfway(struct commit_list *p, int nr)
+{
+       /*
+        * Don't short-cut something we are not going to return!
+        */
+       if (p->item->object.flags & TREESAME)
+               return 0;
+       if (DEBUG_BISECT)
+               return 0;
+       /*
+        * 2 and 3 are halfway of 5.
+        * 3 is halfway of 6 but 2 and 4 are not.
+        */
+       switch (2 * weight(p) - nr) {
+       case -1: case 0: case 1:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+#if !DEBUG_BISECT
+#define show_list(a,b,c,d) do { ; } while (0)
+#else
+static void show_list(const char *debug, int counted, int nr,
+                     struct commit_list *list)
+{
+       struct commit_list *p;
+
+       fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
+
+       for (p = list; p; p = p->next) {
+               struct commit_list *pp;
+               struct commit *commit = p->item;
+               unsigned flags = commit->object.flags;
+               enum object_type type;
+               unsigned long size;
+               char *buf = read_sha1_file(commit->object.sha1, &type, &size);
+               char *ep, *sp;
+
+               fprintf(stderr, "%c%c%c ",
+                       (flags & TREESAME) ? ' ' : 'T',
+                       (flags & UNINTERESTING) ? 'U' : ' ',
+                       (flags & COUNTED) ? 'C' : ' ');
+               if (commit->util)
+                       fprintf(stderr, "%3d", weight(p));
+               else
+                       fprintf(stderr, "---");
+               fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
+               for (pp = commit->parents; pp; pp = pp->next)
+                       fprintf(stderr, " %.*s", 8,
+                               sha1_to_hex(pp->item->object.sha1));
+
+               sp = strstr(buf, "\n\n");
+               if (sp) {
+                       sp += 2;
+                       for (ep = sp; *ep && *ep != '\n'; ep++)
+                               ;
+                       fprintf(stderr, " %.*s", (int)(ep - sp), sp);
+               }
+               fprintf(stderr, "\n");
+       }
+}
+#endif /* DEBUG_BISECT */
+
+static struct commit_list *best_bisection(struct commit_list *list, int nr)
+{
+       struct commit_list *p, *best;
+       int best_distance = -1;
+
+       best = list;
+       for (p = list; p; p = p->next) {
+               int distance;
+               unsigned flags = p->item->object.flags;
+
+               if (flags & TREESAME)
+                       continue;
+               distance = weight(p);
+               if (nr - distance < distance)
+                       distance = nr - distance;
+               if (distance > best_distance) {
+                       best = p;
+                       best_distance = distance;
+               }
+       }
+
+       return best;
+}
+
+struct commit_dist {
+       struct commit *commit;
+       int distance;
+};
+
+static int compare_commit_dist(const void *a_, const void *b_)
+{
+       struct commit_dist *a, *b;
+
+       a = (struct commit_dist *)a_;
+       b = (struct commit_dist *)b_;
+       if (a->distance != b->distance)
+               return b->distance - a->distance; /* desc sort */
+       return hashcmp(a->commit->object.sha1, b->commit->object.sha1);
+}
+
+static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
+{
+       struct commit_list *p;
+       struct commit_dist *array = xcalloc(nr, sizeof(*array));
+       int cnt, i;
+
+       for (p = list, cnt = 0; p; p = p->next) {
+               int distance;
+               unsigned flags = p->item->object.flags;
+
+               if (flags & TREESAME)
+                       continue;
+               distance = weight(p);
+               if (nr - distance < distance)
+                       distance = nr - distance;
+               array[cnt].commit = p->item;
+               array[cnt].distance = distance;
+               cnt++;
+       }
+       qsort(array, cnt, sizeof(*array), compare_commit_dist);
+       for (p = list, i = 0; i < cnt; i++) {
+               struct name_decoration *r = xmalloc(sizeof(*r) + 100);
+               struct object *obj = &(array[i].commit->object);
+
+               sprintf(r->name, "dist=%d", array[i].distance);
+               r->next = add_decoration(&name_decoration, obj, r);
+               p->item = array[i].commit;
+               p = p->next;
+       }
+       if (p)
+               p->next = NULL;
+       free(array);
+       return list;
+}
+
+/*
+ * zero or positive weight is the number of interesting commits it can
+ * reach, including itself.  Especially, weight = 0 means it does not
+ * reach any tree-changing commits (e.g. just above uninteresting one
+ * but traversal is with pathspec).
+ *
+ * weight = -1 means it has one parent and its distance is yet to
+ * be computed.
+ *
+ * weight = -2 means it has more than one parent and its distance is
+ * unknown.  After running count_distance() first, they will get zero
+ * or positive distance.
+ */
+static struct commit_list *do_find_bisection(struct commit_list *list,
+                                            int nr, int *weights,
+                                            int find_all)
+{
+       int n, counted;
+       struct commit_list *p;
+
+       counted = 0;
+
+       for (n = 0, p = list; p; p = p->next) {
+               struct commit *commit = p->item;
+               unsigned flags = commit->object.flags;
+
+               p->item->util = &weights[n++];
+               switch (count_interesting_parents(commit)) {
+               case 0:
+                       if (!(flags & TREESAME)) {
+                               weight_set(p, 1);
+                               counted++;
+                               show_list("bisection 2 count one",
+                                         counted, nr, list);
+                       }
+                       /*
+                        * otherwise, it is known not to reach any
+                        * tree-changing commit and gets weight 0.
+                        */
+                       break;
+               case 1:
+                       weight_set(p, -1);
+                       break;
+               default:
+                       weight_set(p, -2);
+                       break;
+               }
+       }
+
+       show_list("bisection 2 initialize", counted, nr, list);
+
+       /*
+        * If you have only one parent in the resulting set
+        * then you can reach one commit more than that parent
+        * can reach.  So we do not have to run the expensive
+        * count_distance() for single strand of pearls.
+        *
+        * However, if you have more than one parents, you cannot
+        * just add their distance and one for yourself, since
+        * they usually reach the same ancestor and you would
+        * end up counting them twice that way.
+        *
+        * So we will first count distance of merges the usual
+        * way, and then fill the blanks using cheaper algorithm.
+        */
+       for (p = list; p; p = p->next) {
+               if (p->item->object.flags & UNINTERESTING)
+                       continue;
+               if (weight(p) != -2)
+                       continue;
+               weight_set(p, count_distance(p));
+               clear_distance(list);
+
+               /* Does it happen to be at exactly half-way? */
+               if (!find_all && halfway(p, nr))
+                       return p;
+               counted++;
+       }
+
+       show_list("bisection 2 count_distance", counted, nr, list);
+
+       while (counted < nr) {
+               for (p = list; p; p = p->next) {
+                       struct commit_list *q;
+                       unsigned flags = p->item->object.flags;
+
+                       if (0 <= weight(p))
+                               continue;
+                       for (q = p->item->parents; q; q = q->next) {
+                               if (q->item->object.flags & UNINTERESTING)
+                                       continue;
+                               if (0 <= weight(q))
+                                       break;
+                       }
+                       if (!q)
+                               continue;
+
+                       /*
+                        * weight for p is unknown but q is known.
+                        * add one for p itself if p is to be counted,
+                        * otherwise inherit it from q directly.
+                        */
+                       if (!(flags & TREESAME)) {
+                               weight_set(p, weight(q)+1);
+                               counted++;
+                               show_list("bisection 2 count one",
+                                         counted, nr, list);
+                       }
+                       else
+                               weight_set(p, weight(q));
+
+                       /* Does it happen to be at exactly half-way? */
+                       if (!find_all && halfway(p, nr))
+                               return p;
+               }
+       }
+
+       show_list("bisection 2 counted all", counted, nr, list);
+
+       if (!find_all)
+               return best_bisection(list, nr);
+       else
+               return best_bisection_sorted(list, nr);
+}
+
+struct commit_list *find_bisection(struct commit_list *list,
+                                         int *reaches, int *all,
+                                         int find_all)
+{
+       int nr, on_list;
+       struct commit_list *p, *best, *next, *last;
+       int *weights;
+
+       show_list("bisection 2 entry", 0, 0, list);
+
+       /*
+        * Count the number of total and tree-changing items on the
+        * list, while reversing the list.
+        */
+       for (nr = on_list = 0, last = NULL, p = list;
+            p;
+            p = next) {
+               unsigned flags = p->item->object.flags;
+
+               next = p->next;
+               if (flags & UNINTERESTING)
+                       continue;
+               p->next = last;
+               last = p;
+               if (!(flags & TREESAME))
+                       nr++;
+               on_list++;
+       }
+       list = last;
+       show_list("bisection 2 sorted", 0, nr, list);
+
+       *all = nr;
+       weights = xcalloc(on_list, sizeof(*weights));
+
+       /* Do the real work of finding bisection commit. */
+       best = do_find_bisection(list, nr, weights, find_all);
+       if (best) {
+               if (!find_all)
+                       best->next = NULL;
+               *reaches = weight(best);
+       }
+       free(weights);
+       return best;
+}
+
+static int register_ref(const char *refname, const unsigned char *sha1,
+                       int flags, void *cb_data)
+{
+       if (!strcmp(refname, "bad")) {
+               ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+               rev_argv[rev_argv_nr++] = xstrdup(sha1_to_hex(sha1));
+       } else if (!prefixcmp(refname, "good-")) {
+               const char *hex = sha1_to_hex(sha1);
+               char *good = xmalloc(strlen(hex) + 2);
+               *good = '^';
+               memcpy(good + 1, hex, strlen(hex) + 1);
+               ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+               rev_argv[rev_argv_nr++] = good;
+       } else if (!prefixcmp(refname, "skip-")) {
+               ALLOC_GROW(skipped_sha1, skipped_sha1_nr + 1,
+                          skipped_sha1_alloc);
+               hashcpy(skipped_sha1[skipped_sha1_nr++], sha1);
+       }
+
+       return 0;
+}
+
+static int read_bisect_refs(void)
+{
+       return for_each_ref_in("refs/bisect/", register_ref, NULL);
+}
+
+void read_bisect_paths(void)
+{
+       struct strbuf str = STRBUF_INIT;
+       const char *filename = git_path("BISECT_NAMES");
+       FILE *fp = fopen(filename, "r");
+
+       if (!fp)
+               die("Could not open file '%s': %s", filename, strerror(errno));
+
+       while (strbuf_getline(&str, fp, '\n') != EOF) {
+               char *quoted;
+               int res;
+
+               strbuf_trim(&str);
+               quoted = strbuf_detach(&str, NULL);
+               res = sq_dequote_to_argv(quoted, &rev_argv,
+                                        &rev_argv_nr, &rev_argv_alloc);
+               if (res)
+                       die("Badly quoted content in file '%s': %s",
+                           filename, quoted);
+       }
+
+       strbuf_release(&str);
+       fclose(fp);
+}
+
+static int skipcmp(const void *a, const void *b)
+{
+       return hashcmp(a, b);
+}
+
+static void prepare_skipped(void)
+{
+       qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp);
+}
+
+static const unsigned char *skipped_sha1_access(size_t index, void *table)
+{
+       unsigned char (*skipped)[20] = table;
+       return skipped[index];
+}
+
+static int lookup_skipped(unsigned char *sha1)
+{
+       return sha1_pos(sha1, skipped_sha1, skipped_sha1_nr,
+                       skipped_sha1_access);
+}
+
+struct commit_list *filter_skipped(struct commit_list *list,
+                                  struct commit_list **tried,
+                                  int show_all)
+{
+       struct commit_list *filtered = NULL, **f = &filtered;
+
+       *tried = NULL;
+
+       if (!skipped_sha1_nr)
+               return list;
+
+       prepare_skipped();
+
+       while (list) {
+               struct commit_list *next = list->next;
+               list->next = NULL;
+               if (0 <= lookup_skipped(list->item->object.sha1)) {
+                       /* Move current to tried list */
+                       *tried = list;
+                       tried = &list->next;
+               } else {
+                       if (!show_all)
+                               return list;
+                       /* Move current to filtered list */
+                       *f = list;
+                       f = &list->next;
+               }
+               list = next;
+       }
+
+       return filtered;
+}
+
+static void bisect_rev_setup(struct rev_info *revs, const char *prefix)
+{
+       init_revisions(revs, prefix);
+       revs->abbrev = 0;
+       revs->commit_format = CMIT_FMT_UNSPECIFIED;
+
+       /* argv[0] will be ignored by setup_revisions */
+       ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+       rev_argv[rev_argv_nr++] = xstrdup("bisect_rev_setup");
+
+       if (read_bisect_refs())
+               die("reading bisect refs failed");
+
+       ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+       rev_argv[rev_argv_nr++] = xstrdup("--");
+
+       read_bisect_paths();
+
+       ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+       rev_argv[rev_argv_nr++] = NULL;
+
+       setup_revisions(rev_argv_nr, rev_argv, revs, NULL);
+
+       revs->limited = 1;
+}
+
+int bisect_next_vars(const char *prefix)
+{
+       struct rev_info revs;
+       struct rev_list_info info;
+       int reaches = 0, all = 0;
+
+       memset(&info, 0, sizeof(info));
+       info.revs = &revs;
+       info.bisect_show_flags = BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED;
+
+       bisect_rev_setup(&revs, prefix);
+
+       if (prepare_revision_walk(&revs))
+               die("revision walk setup failed");
+       if (revs.tree_objects)
+               mark_edges_uninteresting(revs.commits, &revs, NULL);
+
+       revs.commits = find_bisection(revs.commits, &reaches, &all,
+                                     !!skipped_sha1_nr);
+
+       return show_bisect_vars(&info, reaches, all);
+}
diff --git a/bisect.h b/bisect.h
new file mode 100644 (file)
index 0000000..fdba913
--- /dev/null
+++ b/bisect.h
@@ -0,0 +1,29 @@
+#ifndef BISECT_H
+#define BISECT_H
+
+extern struct commit_list *find_bisection(struct commit_list *list,
+                                         int *reaches, int *all,
+                                         int find_all);
+
+extern struct commit_list *filter_skipped(struct commit_list *list,
+                                         struct commit_list **tried,
+                                         int show_all);
+
+/* bisect_show_flags flags in struct rev_list_info */
+#define BISECT_SHOW_ALL                (1<<0)
+#define BISECT_SHOW_TRIED      (1<<1)
+#define BISECT_SHOW_STRINGED   (1<<2)
+
+struct rev_list_info {
+       struct rev_info *revs;
+       int bisect_show_flags;
+       int show_timestamp;
+       int hdr_termination;
+       const char *header_prefix;
+};
+
+extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all);
+
+extern int bisect_next_vars(const char *prefix);
+
+#endif
index b1ac837f3d30c826ddbe29492b906bf2d0de0a1a..62030af4b51abbfb9c488dbf018770f3b3606789 100644 (file)
--- a/branch.c
+++ b/branch.c
@@ -32,21 +32,59 @@ static int find_tracked_branch(struct remote *remote, void *priv)
        return 0;
 }
 
-static int should_setup_rebase(const struct tracking *tracking)
+static int should_setup_rebase(const char *origin)
 {
        switch (autorebase) {
        case AUTOREBASE_NEVER:
                return 0;
        case AUTOREBASE_LOCAL:
-               return tracking->remote == NULL;
+               return origin == NULL;
        case AUTOREBASE_REMOTE:
-               return tracking->remote != NULL;
+               return origin != NULL;
        case AUTOREBASE_ALWAYS:
                return 1;
        }
        return 0;
 }
 
+void install_branch_config(int flag, const char *local, const char *origin, const char *remote)
+{
+       struct strbuf key = STRBUF_INIT;
+       int rebasing = should_setup_rebase(origin);
+
+       strbuf_addf(&key, "branch.%s.remote", local);
+       git_config_set(key.buf, origin ? origin : ".");
+
+       strbuf_reset(&key);
+       strbuf_addf(&key, "branch.%s.merge", local);
+       git_config_set(key.buf, remote);
+
+       if (rebasing) {
+               strbuf_reset(&key);
+               strbuf_addf(&key, "branch.%s.rebase", local);
+               git_config_set(key.buf, "true");
+       }
+
+       if (flag & BRANCH_CONFIG_VERBOSE) {
+               strbuf_reset(&key);
+
+               strbuf_addstr(&key, origin ? "remote" : "local");
+
+               /* Are we tracking a proper "branch"? */
+               if (!prefixcmp(remote, "refs/heads/")) {
+                       strbuf_addf(&key, " branch %s", remote + 11);
+                       if (origin)
+                               strbuf_addf(&key, " from %s", origin);
+               }
+               else
+                       strbuf_addf(&key, " ref %s", remote);
+               printf("Branch %s set up to track %s%s.\n",
+                      local, key.buf,
+                      rebasing ? " by rebasing" : "");
+       }
+       strbuf_release(&key);
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -55,7 +93,6 @@ static int should_setup_rebase(const struct tracking *tracking)
 static int setup_tracking(const char *new_ref, const char *orig_ref,
                           enum branch_track track)
 {
-       char key[1024];
        struct tracking tracking;
 
        if (strlen(new_ref) > 1024 - 7 - 7 - 1)
@@ -80,19 +117,10 @@ static int setup_tracking(const char *new_ref, const char *orig_ref,
                return error("Not tracking: ambiguous information for ref %s",
                                orig_ref);
 
-       sprintf(key, "branch.%s.remote", new_ref);
-       git_config_set(key, tracking.remote ?  tracking.remote : ".");
-       sprintf(key, "branch.%s.merge", new_ref);
-       git_config_set(key, tracking.src ? tracking.src : orig_ref);
-       printf("Branch %s set up to track %s branch %s.\n", new_ref,
-               tracking.remote ? "remote" : "local", orig_ref);
-       if (should_setup_rebase(&tracking)) {
-               sprintf(key, "branch.%s.rebase", new_ref);
-               git_config_set(key, "true");
-               printf("This branch will rebase on pull.\n");
-       }
-       free(tracking.src);
+       install_branch_config(BRANCH_CONFIG_VERBOSE, new_ref, tracking.remote,
+                             tracking.src ? tracking.src : orig_ref);
 
+       free(tracking.src);
        return 0;
 }
 
@@ -103,14 +131,14 @@ 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;
 
-       snprintf(ref, sizeof ref, "refs/heads/%s", name);
-       if (check_ref_format(ref))
+       if (strbuf_check_branch_ref(&ref, name))
                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 +170,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 +190,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 9f0c2a2c1fab9a312f436880956da0973c68ead8..eed817a64c7620bfe67f395e39b4eef2f85a4ab3 100644 (file)
--- a/branch.h
+++ b/branch.h
@@ -21,4 +21,11 @@ void create_branch(const char *head, const char *name, const char *start_name,
  */
 void remove_branch_state(void);
 
+/*
+ * Configure local branch "local" to merge remote branch "remote"
+ * taken from origin "origin".
+ */
+#define BRANCH_CONFIG_VERBOSE 01
+extern void install_branch_config(int flag, const char *local, const char *origin, const char *remote);
+
 #endif
index ac98c8354d84a7f556c22c53fbe9007832ac4346..ad889aac5bd174bf96a87b78eeb243aea89a1626 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)
@@ -61,7 +61,7 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
        fill_pathspec_matches(pathspec, seen, specs);
 
        for (i = 0; i < specs; i++) {
-               if (!seen[i] && !file_exists(pathspec[i]))
+               if (!seen[i] && pathspec[i][0] && !file_exists(pathspec[i]))
                        die("pathspec '%s' did not match any files",
                                        pathspec[i]);
        }
@@ -104,7 +104,7 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec,
        /* Set up the default git porcelain excludes */
        memset(dir, 0, sizeof(*dir));
        if (!ignored_too) {
-               dir->collect_ignored = 1;
+               dir->flags |= DIR_COLLECT_IGNORED;
                setup_standard_excludes(dir);
        }
 
@@ -148,7 +148,7 @@ static const char **validate_pathspec(int argc, const char **argv, const char *p
        if (pathspec) {
                const char **p;
                for (p = pathspec; *p; p++) {
-                       if (has_symlink_leading_path(strlen(*p), *p)) {
+                       if (has_symlink_leading_path(*p, strlen(*p))) {
                                int len = prefix ? strlen(prefix) : 0;
                                die("'%s' is beyond a symbolic link", *p + len);
                        }
index 58d998577e6d4148ddead9e57bdb0999ff026f73..8a3771e87e1ef2ac7a1ee70133b8206f0f18cbb5 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)
 {
@@ -2266,6 +2271,25 @@ static struct patch *in_fn_table(const char *name)
        return NULL;
 }
 
+/*
+ * item->util in the filename table records the status of the path.
+ * Usually it points at a patch (whose result records the contents
+ * of it after applying it), but it could be PATH_WAS_DELETED for a
+ * path that a previously applied patch has already removed.
+ */
+ #define PATH_TO_BE_DELETED ((struct patch *) -2)
+#define PATH_WAS_DELETED ((struct patch *) -1)
+
+static int to_be_deleted(struct patch *patch)
+{
+       return patch == PATH_TO_BE_DELETED;
+}
+
+static int was_deleted(struct patch *patch)
+{
+       return patch == PATH_WAS_DELETED;
+}
+
 static void add_to_fn_table(struct patch *patch)
 {
        struct string_list_item *item;
@@ -2286,7 +2310,22 @@ static void add_to_fn_table(struct patch *patch)
         */
        if ((patch->new_name == NULL) || (patch->is_rename)) {
                item = string_list_insert(patch->old_name, &fn_table);
-               item->util = (struct patch *) -1;
+               item->util = PATH_WAS_DELETED;
+       }
+}
+
+static void prepare_fn_table(struct patch *patch)
+{
+       /*
+        * store information about incoming file deletion
+        */
+       while (patch) {
+               if ((patch->new_name == NULL) || (patch->is_rename)) {
+                       struct string_list_item *item;
+                       item = string_list_insert(patch->old_name, &fn_table);
+                       item->util = PATH_TO_BE_DELETED;
+               }
+               patch = patch->next;
        }
 }
 
@@ -2299,8 +2338,8 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
        struct patch *tpatch;
 
        if (!(patch->is_copy || patch->is_rename) &&
-           ((tpatch = in_fn_table(patch->old_name)) != NULL)) {
-               if (tpatch == (struct patch *) -1) {
+           (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
+               if (was_deleted(tpatch)) {
                        return error("patch %s has been renamed/deleted",
                                patch->old_name);
                }
@@ -2355,7 +2394,7 @@ static int check_to_create_blob(const char *new_name, int ok_if_exists)
                 * In such a case, path "new_name" does not exist as
                 * far as git is concerned.
                 */
-               if (has_symlink_leading_path(strlen(new_name), new_name))
+               if (has_symlink_leading_path(new_name, strlen(new_name)))
                        return 0;
 
                return error("%s: already exists in working directory", new_name);
@@ -2394,10 +2433,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
        assert(patch->is_new <= 0);
 
        if (!(patch->is_copy || patch->is_rename) &&
-           (tpatch = in_fn_table(old_name)) != NULL) {
-               if (tpatch == (struct patch *) -1) {
+           (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
+               if (was_deleted(tpatch))
                        return error("%s: has been deleted/renamed", old_name);
-               }
                st_mode = tpatch->new_mode;
        } else if (!cached) {
                stat_ret = lstat(old_name, st);
@@ -2405,6 +2443,9 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
                        return error("%s: %s", old_name, strerror(errno));
        }
 
+       if (to_be_deleted(tpatch))
+               tpatch = NULL;
+
        if (check_index && !tpatch) {
                int pos = cache_name_pos(old_name, strlen(old_name));
                if (pos < 0) {
@@ -2446,7 +2487,7 @@ static int check_preimage(struct patch *patch, struct cache_entry **ce, struct s
        if ((st_mode ^ patch->old_mode) & S_IFMT)
                return error("%s: wrong type", old_name);
        if (st_mode != patch->old_mode)
-               fprintf(stderr, "warning: %s has type %o, expected %o\n",
+               warning("%s has type %o, expected %o",
                        old_name, st_mode, patch->old_mode);
        if (!patch->new_mode && !patch->is_delete)
                patch->new_mode = st_mode;
@@ -2466,6 +2507,7 @@ static int check_patch(struct patch *patch)
        const char *new_name = patch->new_name;
        const char *name = old_name ? old_name : new_name;
        struct cache_entry *ce = NULL;
+       struct patch *tpatch;
        int ok_if_exists;
        int status;
 
@@ -2476,7 +2518,8 @@ static int check_patch(struct patch *patch)
                return status;
        old_name = patch->old_name;
 
-       if (in_fn_table(new_name) == (struct patch *) -1)
+       if ((tpatch = in_fn_table(new_name)) &&
+                       (was_deleted(tpatch) || to_be_deleted(tpatch)))
                /*
                 * A type-change diff is always split into a patch to
                 * delete old, immediately followed by a patch to
@@ -2528,6 +2571,7 @@ static int check_patch_list(struct patch *patch)
 {
        int err = 0;
 
+       prepare_fn_table(patch);
        while (patch) {
                if (apply_verbosely)
                        say_patch_name(stderr,
@@ -2737,7 +2781,7 @@ static void remove_file(struct patch *patch, int rmdir_empty)
                        if (rmdir(patch->old_name))
                                warning("unable to remove submodule %s",
                                        patch->old_name);
-               } else if (!unlink(patch->old_name) && rmdir_empty) {
+               } else if (!unlink_or_warn(patch->old_name) && rmdir_empty) {
                        remove_path(patch->old_name);
                }
        }
@@ -2847,7 +2891,7 @@ static void create_one_file(char *path, unsigned mode, const char *buf, unsigned
                        if (!try_create_file(newpath, mode, buf, size)) {
                                if (!rename(newpath, path))
                                        return;
-                               unlink(newpath);
+                               unlink_or_warn(newpath);
                                break;
                        }
                        if (errno != EEXIST)
@@ -2927,8 +2971,7 @@ static int write_out_one_reject(struct patch *patch)
        cnt = strlen(patch->new_name);
        if (ARRAY_SIZE(namebuf) <= cnt + 5) {
                cnt = ARRAY_SIZE(namebuf) - 5;
-               fprintf(stderr,
-                       "warning: truncating .rej filename to %.*s.rej",
+               warning("truncating .rej filename to %.*s.rej",
                        cnt - 1, patch->new_name);
        }
        memcpy(namebuf, patch->new_name, cnt);
@@ -3138,151 +3181,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"),
+               { OPTION_BOOLEAN, 0, "allow-binary-replacement", &binary,
+                 NULL, "old option, now no-op", PARSE_OPT_HIDDEN },
+               { OPTION_BOOLEAN, 0, "binary", &binary,
+                 NULL, "old option, now no-op", PARSE_OPT_HIDDEN },
+               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);
@@ -3301,8 +3353,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
                    squelch_whitespace_errors < whitespace_error) {
                        int squelched =
                                whitespace_error - squelch_whitespace_errors;
-                       fprintf(stderr, "warning: squelched %d "
-                               "whitespace error%s\n",
+                       warning("squelched %d "
+                               "whitespace error%s",
                                squelched,
                                squelched == 1 ? "" : "s");
                }
@@ -3312,12 +3364,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
                            whitespace_error == 1 ? "" : "s",
                            whitespace_error == 1 ? "s" : "");
                if (applied_after_fixing_ws && apply)
-                       fprintf(stderr, "warning: %d line%s applied after"
-                               " fixing whitespace errors.\n",
+                       warning("%d line%s applied after"
+                               " fixing whitespace errors.",
                                applied_after_fixing_ws,
                                applied_after_fixing_ws == 1 ? "" : "s");
                else if (whitespace_error)
-                       fprintf(stderr, "warning: %d line%s add%s whitespace errors.\n",
+                       warning("%d line%s add%s whitespace errors.",
                                whitespace_error,
                                whitespace_error == 1 ? "" : "s",
                                whitespace_error == 1 ? "s" : "");
index 5ceec433fd590e8bf6a51700ea69c37f9af30fa7..ab50cebba0e6798996cc007ced9079c3f0b94a29 100644 (file)
@@ -5,44 +5,35 @@
 #include "cache.h"
 #include "builtin.h"
 #include "archive.h"
+#include "parse-options.h"
 #include "pkt-line.h"
 #include "sideband.h"
 
-static int run_remote_archiver(const char *remote, int argc,
-                              const char **argv)
+static void create_output_file(const char *output_file)
+{
+       int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666);
+       if (output_fd < 0)
+               die("could not create archive file: %s ", output_file);
+       if (output_fd != 1) {
+               if (dup2(output_fd, 1) < 0)
+                       die("could not redirect output");
+               else
+                       close(output_fd);
+       }
+}
+
+static int run_remote_archiver(int argc, const char **argv,
+                              const char *remote, const char *exec)
 {
        char *url, buf[LARGE_PACKET_MAX];
        int fd[2], i, len, rv;
        struct child_process *conn;
-       const char *exec = "git-upload-archive";
-       int exec_at = 0, exec_value_at = 0;
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-               if (!prefixcmp(arg, "--exec=")) {
-                       if (exec_at)
-                               die("multiple --exec specified");
-                       exec = arg + 7;
-                       exec_at = i;
-               } else if (!strcmp(arg, "--exec")) {
-                       if (exec_at)
-                               die("multiple --exec specified");
-                       if (i + 1 >= argc)
-                               die("option --exec requires a value");
-                       exec = argv[i + 1];
-                       exec_at = i;
-                       exec_value_at = ++i;
-               }
-       }
 
        url = xstrdup(remote);
        conn = git_connect(fd, url, exec, 0);
 
-       for (i = 1; i < argc; i++) {
-               if (i == exec_at || i == exec_value_at)
-                       continue;
+       for (i = 1; i < argc; i++)
                packet_write(fd[1], "argument %s\n", argv[i]);
-       }
        packet_flush(fd[1]);
 
        len = packet_read_line(fd[0], buf, sizeof(buf));
@@ -61,7 +52,7 @@ static int run_remote_archiver(const char *remote, int argc,
                die("git archive: expected a flush");
 
        /* Now, start reading from fd[0] and spit it out to stdout */
-       rv = recv_sideband("archive", fd[0], 1, 2);
+       rv = recv_sideband("archive", fd[0], 1);
        close(fd[0]);
        close(fd[1]);
        rv |= finish_connect(conn);
@@ -69,51 +60,33 @@ static int run_remote_archiver(const char *remote, int argc,
        return !!rv;
 }
 
-static const char *extract_remote_arg(int *ac, const char **av)
-{
-       int ix, iy, cnt = *ac;
-       int no_more_options = 0;
-       const char *remote = NULL;
-
-       for (ix = iy = 1; ix < cnt; ix++) {
-               const char *arg = av[ix];
-               if (!strcmp(arg, "--"))
-                       no_more_options = 1;
-               if (!no_more_options) {
-                       if (!prefixcmp(arg, "--remote=")) {
-                               if (remote)
-                                       die("Multiple --remote specified");
-                               remote = arg + 9;
-                               continue;
-                       } else if (!strcmp(arg, "--remote")) {
-                               if (remote)
-                                       die("Multiple --remote specified");
-                               if (++ix >= cnt)
-                                       die("option --remote requires a value");
-                               remote = av[ix];
-                               continue;
-                       }
-                       if (arg[0] != '-')
-                               no_more_options = 1;
-               }
-               if (ix != iy)
-                       av[iy] = arg;
-               iy++;
-       }
-       if (remote) {
-               av[--cnt] = NULL;
-               *ac = cnt;
-       }
-       return remote;
-}
+#define PARSE_OPT_KEEP_ALL ( PARSE_OPT_KEEP_DASHDASH |         \
+                            PARSE_OPT_KEEP_ARGV0 |     \
+                            PARSE_OPT_KEEP_UNKNOWN |   \
+                            PARSE_OPT_NO_INTERNAL_HELP )
 
 int cmd_archive(int argc, const char **argv, const char *prefix)
 {
+       const char *exec = "git-upload-archive";
+       const char *output = NULL;
        const char *remote = NULL;
+       struct option local_opts[] = {
+               OPT_STRING(0, "output", &output, "file",
+                       "write the archive to this file"),
+               OPT_STRING(0, "remote", &remote, "repo",
+                       "retrieve the archive from remote repository <repo>"),
+               OPT_STRING(0, "exec", &exec, "cmd",
+                       "path to the remote git-upload-archive command"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, local_opts, NULL, PARSE_OPT_KEEP_ALL);
+
+       if (output)
+               create_output_file(output);
 
-       remote = extract_remote_arg(&argc, argv);
        if (remote)
-               return run_remote_archiver(remote, argc, argv);
+               return run_remote_archiver(argc, argv, remote, exec);
 
        setvbuf(stderr, NULL, _IOLBF, BUFSIZ);
 
diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c
new file mode 100644 (file)
index 0000000..8fe7787
--- /dev/null
@@ -0,0 +1,27 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "bisect.h"
+
+static const char * const git_bisect_helper_usage[] = {
+       "git bisect--helper --next-vars",
+       NULL
+};
+
+int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
+{
+       int next_vars = 0;
+       struct option options[] = {
+               OPT_BOOLEAN(0, "next-vars", &next_vars,
+                           "output next bisect step variables"),
+               OPT_END()
+       };
+
+       argc = parse_options(argc, argv, options, git_bisect_helper_usage, 0);
+
+       if (!next_vars)
+               usage_with_options(git_bisect_helper_usage, options);
+
+       /* next-vars */
+       return bisect_next_vars(prefix);
+}
index e462a6ec5037a9b33894990bcaf2b425273ba647..cf74a926149668b7e67544aecd46a0e05d4f59d9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Pickaxe
+ * Blame
  *
  * Copyright (c) 2006, Junio C Hamano
  */
@@ -40,6 +40,10 @@ static int reverse;
 static int blank_boundary;
 static int incremental;
 static int xdl_opts = XDF_NEED_MINIMAL;
+
+static enum date_mode blame_date_mode = DATE_ISO8601;
+static size_t blame_date_width;
+
 static struct string_list mailmap;
 
 #ifndef DEBUG
@@ -74,6 +78,7 @@ static unsigned blame_copy_score;
  */
 struct origin {
        int refcnt;
+       struct origin *previous;
        struct commit *commit;
        mmfile_t file;
        unsigned char blob_sha1[20];
@@ -115,6 +120,8 @@ static inline struct origin *origin_incref(struct origin *o)
 static void origin_decref(struct origin *o)
 {
        if (o && --o->refcnt <= 0) {
+               if (o->previous)
+                       origin_decref(o->previous);
                free(o->file.ptr);
                free(o);
        }
@@ -866,7 +873,7 @@ static void find_copy_in_blob(struct scoreboard *sb,
         * Prepare mmfile that contains only the lines in ent.
         */
        cp = nth_line(sb, ent->lno);
-       file_o.ptr = (char*) cp;
+       file_o.ptr = (char *) cp;
        cnt = ent->num_lines;
 
        while (cnt && cp < sb->final_buf + sb->final_buf_size) {
@@ -1198,6 +1205,10 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                struct origin *porigin = sg_origin[i];
                if (!porigin)
                        continue;
+               if (!origin->previous) {
+                       origin_incref(porigin);
+                       origin->previous = porigin;
+               }
                if (pass_blame_to_parent(sb, origin, porigin))
                        goto finish;
        }
@@ -1264,11 +1275,12 @@ struct commit_info
  * Parse author/committer line in the commit object buffer
  */
 static void get_ac_line(const char *inbuf, const char *what,
-                       int bufsz, char *person, const char **mail,
+                       int person_len, char *person,
+                       int mail_len, char *mail,
                        unsigned long *time, const char **tz)
 {
        int len, tzlen, maillen;
-       char *tmp, *endp, *timepos;
+       char *tmp, *endp, *timepos, *mailpos;
 
        tmp = strstr(inbuf, what);
        if (!tmp)
@@ -1279,10 +1291,11 @@ static void get_ac_line(const char *inbuf, const char *what,
                len = strlen(tmp);
        else
                len = endp - tmp;
-       if (bufsz <= len) {
+       if (person_len <= len) {
        error_out:
                /* Ugh */
-               *mail = *tz = "(unknown)";
+               *tz = "(unknown)";
+               strcpy(mail, *tz);
                *time = 0;
                return;
        }
@@ -1305,9 +1318,10 @@ static void get_ac_line(const char *inbuf, const char *what,
        *tmp = 0;
        while (*tmp != ' ')
                tmp--;
-       *mail = tmp + 1;
+       mailpos = tmp + 1;
        *tmp = 0;
        maillen = timepos - tmp;
+       memcpy(mail, mailpos, maillen);
 
        if (!mailmap.nr)
                return;
@@ -1316,20 +1330,23 @@ static void get_ac_line(const char *inbuf, const char *what,
         * mailmap expansion may make the name longer.
         * make room by pushing stuff down.
         */
-       tmp = person + bufsz - (tzlen + 1);
+       tmp = person + person_len - (tzlen + 1);
        memmove(tmp, *tz, tzlen);
        tmp[tzlen] = 0;
        *tz = tmp;
 
-       tmp = tmp - (maillen + 1);
-       memmove(tmp, *mail, maillen);
-       tmp[maillen] = 0;
-       *mail = tmp;
-
        /*
-        * Now, convert e-mail using mailmap
+        * Now, convert both name and e-mail using mailmap
         */
-       map_email(&mailmap, tmp + 1, person, tmp-person-1);
+       if(map_user(&mailmap, mail+1, mail_len-1, person, tmp-person-1)) {
+               /* Add a trailing '>' to email, since map_user returns plain emails
+                  Note: It already has '<', since we replace from mail+1 */
+               mailpos = memchr(mail, '\0', mail_len);
+               if (mailpos && mailpos-mail < mail_len - 1) {
+                       *mailpos = '>';
+                       *(mailpos+1) = '\0';
+               }
+       }
 }
 
 static void get_commit_info(struct commit *commit,
@@ -1338,8 +1355,10 @@ static void get_commit_info(struct commit *commit,
 {
        int len;
        char *tmp, *endp, *reencoded, *message;
-       static char author_buf[1024];
-       static char committer_buf[1024];
+       static char author_name[1024];
+       static char author_mail[1024];
+       static char committer_name[1024];
+       static char committer_mail[1024];
        static char summary_buf[1024];
 
        /*
@@ -1357,9 +1376,11 @@ static void get_commit_info(struct commit *commit,
        }
        reencoded = reencode_commit_message(commit, NULL);
        message   = reencoded ? reencoded : commit->buffer;
-       ret->author = author_buf;
+       ret->author = author_name;
+       ret->author_mail = author_mail;
        get_ac_line(message, "\nauthor ",
-                   sizeof(author_buf), author_buf, &ret->author_mail,
+                   sizeof(author_name), author_name,
+                   sizeof(author_mail), author_mail,
                    &ret->author_time, &ret->author_tz);
 
        if (!detailed) {
@@ -1367,9 +1388,11 @@ static void get_commit_info(struct commit *commit,
                return;
        }
 
-       ret->committer = committer_buf;
+       ret->committer = committer_name;
+       ret->committer_mail = committer_mail;
        get_ac_line(message, "\ncommitter ",
-                   sizeof(committer_buf), committer_buf, &ret->committer_mail,
+                   sizeof(committer_name), committer_name,
+                   sizeof(committer_mail), committer_mail,
                    &ret->committer_time, &ret->committer_tz);
 
        ret->summary = summary_buf;
@@ -1402,6 +1425,39 @@ static void write_filename_info(const char *path)
        write_name_quoted(path, stdout, '\n');
 }
 
+/*
+ * Porcelain/Incremental format wants to show a lot of details per
+ * commit.  Instead of repeating this every line, emit it only once,
+ * the first time each commit appears in the output.
+ */
+static int emit_one_suspect_detail(struct origin *suspect)
+{
+       struct commit_info ci;
+
+       if (suspect->commit->object.flags & METAINFO_SHOWN)
+               return 0;
+
+       suspect->commit->object.flags |= METAINFO_SHOWN;
+       get_commit_info(suspect->commit, &ci, 1);
+       printf("author %s\n", ci.author);
+       printf("author-mail %s\n", ci.author_mail);
+       printf("author-time %lu\n", ci.author_time);
+       printf("author-tz %s\n", ci.author_tz);
+       printf("committer %s\n", ci.committer);
+       printf("committer-mail %s\n", ci.committer_mail);
+       printf("committer-time %lu\n", ci.committer_time);
+       printf("committer-tz %s\n", ci.committer_tz);
+       printf("summary %s\n", ci.summary);
+       if (suspect->commit->object.flags & UNINTERESTING)
+               printf("boundary\n");
+       if (suspect->previous) {
+               struct origin *prev = suspect->previous;
+               printf("previous %s ", sha1_to_hex(prev->commit->object.sha1));
+               write_name_quoted(prev->path, stdout, '\n');
+       }
+       return 1;
+}
+
 /*
  * The blame_entry is found to be guilty for the range.  Mark it
  * as such, and show it in incremental output.
@@ -1417,22 +1473,7 @@ static void found_guilty_entry(struct blame_entry *ent)
                printf("%s %d %d %d\n",
                       sha1_to_hex(suspect->commit->object.sha1),
                       ent->s_lno + 1, ent->lno + 1, ent->num_lines);
-               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-                       struct commit_info ci;
-                       suspect->commit->object.flags |= METAINFO_SHOWN;
-                       get_commit_info(suspect->commit, &ci, 1);
-                       printf("author %s\n", ci.author);
-                       printf("author-mail %s\n", ci.author_mail);
-                       printf("author-time %lu\n", ci.author_time);
-                       printf("author-tz %s\n", ci.author_tz);
-                       printf("committer %s\n", ci.committer);
-                       printf("committer-mail %s\n", ci.committer_mail);
-                       printf("committer-time %lu\n", ci.committer_time);
-                       printf("committer-tz %s\n", ci.committer_tz);
-                       printf("summary %s\n", ci.summary);
-                       if (suspect->commit->object.flags & UNINTERESTING)
-                               printf("boundary\n");
-               }
+               emit_one_suspect_detail(suspect);
                write_filename_info(suspect->path);
                maybe_flush_or_die(stdout, "stdout");
        }
@@ -1495,24 +1536,20 @@ static const char *format_time(unsigned long time, const char *tz_str,
                               int show_raw_time)
 {
        static char time_buf[128];
-       time_t t = time;
-       int minutes, tz;
-       struct tm *tm;
+       const char *time_str;
+       int time_len;
+       int tz;
 
        if (show_raw_time) {
                sprintf(time_buf, "%lu %s", time, tz_str);
-               return time_buf;
        }
-
-       tz = atoi(tz_str);
-       minutes = tz < 0 ? -tz : tz;
-       minutes = (minutes / 100)*60 + (minutes % 100);
-       minutes = tz < 0 ? -minutes : minutes;
-       t = time + minutes * 60;
-       tm = gmtime(&t);
-
-       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
-       strcat(time_buf, tz_str);
+       else {
+               tz = atoi(tz_str);
+               time_str = show_date(time, tz, blame_date_mode);
+               time_len = strlen(time_str);
+               memcpy(time_buf, time_str, time_len);
+               memset(time_buf + time_len, ' ', blame_date_width - time_len);
+       }
        return time_buf;
 }
 
@@ -1539,24 +1576,8 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
               ent->s_lno + 1,
               ent->lno + 1,
               ent->num_lines);
-       if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
-               struct commit_info ci;
-               suspect->commit->object.flags |= METAINFO_SHOWN;
-               get_commit_info(suspect->commit, &ci, 1);
-               printf("author %s\n", ci.author);
-               printf("author-mail %s\n", ci.author_mail);
-               printf("author-time %lu\n", ci.author_time);
-               printf("author-tz %s\n", ci.author_tz);
-               printf("committer %s\n", ci.committer);
-               printf("committer-mail %s\n", ci.committer_mail);
-               printf("committer-time %lu\n", ci.committer_time);
-               printf("committer-tz %s\n", ci.committer_tz);
-               write_filename_info(suspect->path);
-               printf("summary %s\n", ci.summary);
-               if (suspect->commit->object.flags & UNINTERESTING)
-                       printf("boundary\n");
-       }
-       else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
+       if (emit_one_suspect_detail(suspect) ||
+           (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
                write_filename_info(suspect->path);
 
        cp = nth_line(sb, ent->lno);
@@ -1683,7 +1704,7 @@ static int prepare_lines(struct scoreboard *sb)
        while (len--) {
                if (bol) {
                        sb->lineno = xrealloc(sb->lineno,
-                                             sizeof(int) * (num + 1));
+                                             sizeof(int *) * (num + 1));
                        sb->lineno[num] = buf - sb->final_buf;
                        bol = 0;
                }
@@ -1693,7 +1714,7 @@ static int prepare_lines(struct scoreboard *sb)
                }
        }
        sb->lineno = xrealloc(sb->lineno,
-                             sizeof(int) * (num + incomplete + 1));
+                             sizeof(int *) * (num + incomplete + 1));
        sb->lineno[num + incomplete] = buf - sb->final_buf;
        sb->num_lines = num + incomplete;
        return sb->num_lines;
@@ -1794,36 +1815,6 @@ static void sanity_check_refcnt(struct scoreboard *sb)
                        baa = 1;
                }
        }
-       for (ent = sb->ent; ent; ent = ent->next) {
-               /* Mark the ones that haven't been checked */
-               if (0 < ent->suspect->refcnt)
-                       ent->suspect->refcnt = -ent->suspect->refcnt;
-       }
-       for (ent = sb->ent; ent; ent = ent->next) {
-               /*
-                * ... then pick each and see if they have the the
-                * correct refcnt.
-                */
-               int found;
-               struct blame_entry *e;
-               struct origin *suspect = ent->suspect;
-
-               if (0 < suspect->refcnt)
-                       continue;
-               suspect->refcnt = -suspect->refcnt; /* Unmark */
-               for (found = 0, e = sb->ent; e; e = e->next) {
-                       if (e->suspect != suspect)
-                               continue;
-                       found++;
-               }
-               if (suspect->refcnt != found) {
-                       fprintf(stderr, "%s in %s has refcnt %d, not %d\n",
-                               ent->suspect->path,
-                               sha1_to_hex(ent->suspect->commit->object.sha1),
-                               ent->suspect->refcnt, found);
-                       baa = 2;
-               }
-       }
        if (baa) {
                int opt = 0160;
                find_alignment(sb, &opt);
@@ -1898,7 +1889,7 @@ static const char *parse_loc(const char *spec,
                return spec;
 
        /* it could be a regexp of form /.../ */
-       for (term = (char*) spec + 1; *term && *term != '/'; term++) {
+       for (term = (char *) spec + 1; *term && *term != '/'; term++) {
                if (*term == '\\')
                        term++;
        }
@@ -1963,6 +1954,12 @@ static int git_blame_config(const char *var, const char *value, void *cb)
                blank_boundary = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "blame.date")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               blame_date_mode = parse_date_format(value);
+               return 0;
+       }
        return git_default_config(var, value, cb);
 }
 
@@ -2227,6 +2224,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        git_config(git_blame_config, NULL);
        init_revisions(&revs, NULL);
+       revs.date_mode = blame_date_mode;
+
        save_commit_buffer = 0;
        dashdash_pos = 0;
 
@@ -2255,8 +2254,35 @@ parse_done:
                die("reading graft file %s failed: %s",
                    revs_file, strerror(errno));
 
-       if (cmd_is_annotate)
+       if (cmd_is_annotate) {
                output_option |= OUTPUT_ANNOTATE_COMPAT;
+               blame_date_mode = DATE_ISO8601;
+       } else {
+               blame_date_mode = revs.date_mode;
+       }
+
+       /* The maximum width used to show the dates */
+       switch (blame_date_mode) {
+       case DATE_RFC2822:
+               blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
+               break;
+       case DATE_ISO8601:
+               blame_date_width = sizeof("2006-10-19 16:00:04 -0700");
+               break;
+       case DATE_RAW:
+               blame_date_width = sizeof("1161298804 -0700");
+               break;
+       case DATE_SHORT:
+               blame_date_width = sizeof("2006-10-19");
+               break;
+       case DATE_RELATIVE:
+               /* "normal" is used as the fallback for "relative" */
+       case DATE_LOCAL:
+       case DATE_NORMAL:
+               blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
+               break;
+       }
+       blame_date_width -= 1; /* strip the null */
 
        if (DIFF_OPT_TST(&revs.diffopt, FIND_COPIES_HARDER))
                opt |= (PICKAXE_BLAME_COPY | PICKAXE_BLAME_MOVE |
@@ -2396,7 +2422,7 @@ parse_done:
        sb.ent = ent;
        sb.path = path;
 
-       read_mailmap(&mailmap, ".mailmap", NULL);
+       read_mailmap(&mailmap, NULL);
 
        if (!incremental)
                setup_pager();
index 23b6949fec30e8276d0ef3d34e0aaee10eb49165..91098ca9b106239916af000cb54a4bf09629e6b6 100644 (file)
@@ -32,18 +32,18 @@ static unsigned char head_sha1[20];
 
 static int branch_use_color = -1;
 static char branch_colors[][COLOR_MAXLEN] = {
-       "\033[m",       /* reset */
-       "",             /* PLAIN (normal) */
-       "\033[31m",     /* REMOTE (red) */
-       "",             /* LOCAL (normal) */
-       "\033[32m",     /* CURRENT (green) */
+       GIT_COLOR_RESET,
+       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_RED,          /* REMOTE */
+       GIT_COLOR_NORMAL,       /* LOCAL */
+       GIT_COLOR_GREEN,        /* CURRENT */
 };
 enum color_branch {
-       COLOR_BRANCH_RESET = 0,
-       COLOR_BRANCH_PLAIN = 1,
-       COLOR_BRANCH_REMOTE = 2,
-       COLOR_BRANCH_LOCAL = 3,
-       COLOR_BRANCH_CURRENT = 4,
+       BRANCH_COLOR_RESET = 0,
+       BRANCH_COLOR_PLAIN = 1,
+       BRANCH_COLOR_REMOTE = 2,
+       BRANCH_COLOR_LOCAL = 3,
+       BRANCH_COLOR_CURRENT = 4,
 };
 
 static enum merge_filter {
@@ -56,15 +56,15 @@ static unsigned char merge_filter_ref[20];
 static int parse_branch_color_slot(const char *var, int ofs)
 {
        if (!strcasecmp(var+ofs, "plain"))
-               return COLOR_BRANCH_PLAIN;
+               return BRANCH_COLOR_PLAIN;
        if (!strcasecmp(var+ofs, "reset"))
-               return COLOR_BRANCH_RESET;
+               return BRANCH_COLOR_RESET;
        if (!strcasecmp(var+ofs, "remote"))
-               return COLOR_BRANCH_REMOTE;
+               return BRANCH_COLOR_REMOTE;
        if (!strcasecmp(var+ofs, "local"))
-               return COLOR_BRANCH_LOCAL;
+               return BRANCH_COLOR_LOCAL;
        if (!strcasecmp(var+ofs, "current"))
-               return COLOR_BRANCH_CURRENT;
+               return BRANCH_COLOR_CURRENT;
        die("bad config variable '%s'", var);
 }
 
@@ -99,6 +99,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
        const char *fmt, *remote;
        int i;
        int ret = 0;
+       struct strbuf bname = STRBUF_INIT;
 
        switch (kinds) {
        case REF_REMOTE_BRANCH:
@@ -119,20 +120,21 @@ 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)) {
+               strbuf_branchname(&bname, argv[i]);
+               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 +154,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 (was %s).\n", remote, argv[i],
-                               find_unique_abbrev(sha1, DEFAULT_ABBREV));
-                       strbuf_addf(&buf, "branch.%s", argv[i]);
+                       printf("Deleted %sbranch %s (was %s).\n", remote,
+                              bname.buf,
+                              find_unique_abbrev(sha1, DEFAULT_ABBREV));
+                       strbuf_addf(&buf, "branch.%s", bname.buf);
                        if (git_config_rename_section(buf.buf, NULL) < 0)
                                warning("Update of config-file failed");
                        strbuf_release(&buf);
@@ -181,7 +184,8 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
 
 struct ref_item {
        char *name;
-       unsigned int kind;
+       char *dest;
+       unsigned int kind, len;
        struct commit *commit;
 };
 
@@ -193,19 +197,18 @@ struct ref_list {
        int kinds;
 };
 
-static int has_commit(struct commit *commit, struct commit_list *with_commit)
+static char *resolve_symref(const char *src, const char *prefix)
 {
-       if (!with_commit)
-               return 1;
-       while (with_commit) {
-               struct commit *other;
-
-               other = with_commit->item;
-               with_commit = with_commit->next;
-               if (in_merge_bases(other, &commit, 1))
-                       return 1;
-       }
-       return 0;
+       unsigned char sha1[20];
+       int flag;
+       const char *dst, *cp;
+
+       dst = resolve_ref(src, sha1, 0, &flag);
+       if (!(dst && (flag & REF_ISSYMREF)))
+               return NULL;
+       if (prefix && (cp = skip_prefix(dst, prefix)))
+               dst = cp;
+       return xstrdup(dst);
 }
 
 static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
@@ -213,17 +216,28 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        struct ref_list *ref_list = (struct ref_list*)(cb_data);
        struct ref_item *newitem;
        struct commit *commit;
-       int kind;
-       int len;
+       int kind, i;
+       const char *prefix, *orig_refname = refname;
+
+       static struct {
+               int kind;
+               const char *prefix;
+               int pfxlen;
+       } ref_kind[] = {
+               { REF_LOCAL_BRANCH, "refs/heads/", 11 },
+               { REF_REMOTE_BRANCH, "refs/remotes/", 13 },
+       };
 
        /* Detect kind */
-       if (!prefixcmp(refname, "refs/heads/")) {
-               kind = REF_LOCAL_BRANCH;
-               refname += 11;
-       } else if (!prefixcmp(refname, "refs/remotes/")) {
-               kind = REF_REMOTE_BRANCH;
-               refname += 13;
-       } else
+       for (i = 0; i < ARRAY_SIZE(ref_kind); i++) {
+               prefix = ref_kind[i].prefix;
+               if (strncmp(refname, prefix, ref_kind[i].pfxlen))
+                       continue;
+               kind = ref_kind[i].kind;
+               refname += ref_kind[i].pfxlen;
+               break;
+       }
+       if (ARRAY_SIZE(ref_kind) <= i)
                return 0;
 
        commit = lookup_commit_reference_gently(sha1, 1);
@@ -231,7 +245,7 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
                return error("branch '%s' does not point at a commit", refname);
 
        /* Filter with with_commit if specified */
-       if (!has_commit(commit, ref_list->with_commit))
+       if (!is_descendant_of(commit, ref_list->with_commit))
                return 0;
 
        /* Don't add types the caller doesn't want */
@@ -254,9 +268,14 @@ static int append_ref(const char *refname, const unsigned char *sha1, int flags,
        newitem->name = xstrdup(refname);
        newitem->kind = kind;
        newitem->commit = commit;
-       len = strlen(newitem->name);
-       if (len > ref_list->maxwidth)
-               ref_list->maxwidth = len;
+       newitem->len = strlen(refname);
+       newitem->dest = resolve_symref(orig_refname, prefix);
+       /* adjust for "remotes/" */
+       if (newitem->kind == REF_REMOTE_BRANCH &&
+           ref_list->kinds != REF_REMOTE_BRANCH)
+               newitem->len += 8;
+       if (newitem->len > ref_list->maxwidth)
+               ref_list->maxwidth = newitem->len;
 
        return 0;
 }
@@ -265,8 +284,10 @@ static void free_ref_list(struct ref_list *ref_list)
 {
        int i;
 
-       for (i = 0; i < ref_list->index; i++)
+       for (i = 0; i < ref_list->index; i++) {
                free(ref_list->list[i].name);
+               free(ref_list->list[i].dest);
+       }
        free(ref_list->list);
 }
 
@@ -280,19 +301,30 @@ static int ref_cmp(const void *r1, const void *r2)
        return strcmp(c1->name, c2->name);
 }
 
-static void fill_tracking_info(struct strbuf *stat, const char *branch_name)
+static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
+               int show_upstream_ref)
 {
        int ours, theirs;
        struct branch *branch = branch_get(branch_name);
 
-       if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs))
+       if (!stat_tracking_info(branch, &ours, &theirs)) {
+               if (branch && branch->merge && branch->merge[0]->dst &&
+                   show_upstream_ref)
+                       strbuf_addf(stat, "[%s] ",
+                           shorten_unambiguous_ref(branch->merge[0]->dst, 0));
                return;
+       }
+
+       strbuf_addch(stat, '[');
+       if (show_upstream_ref)
+               strbuf_addf(stat, "%s: ",
+                       shorten_unambiguous_ref(branch->merge[0]->dst, 0));
        if (!ours)
-               strbuf_addf(stat, "[behind %d] ", theirs);
+               strbuf_addf(stat, "behind %d] ", theirs);
        else if (!theirs)
-               strbuf_addf(stat, "[ahead %d] ", ours);
+               strbuf_addf(stat, "ahead %d] ", ours);
        else
-               strbuf_addf(stat, "[ahead %d, behind %d] ", ours, theirs);
+               strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
 }
 
 static int matches_merge_filter(struct commit *commit)
@@ -307,34 +339,46 @@ static int matches_merge_filter(struct commit *commit)
 }
 
 static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
-                          int abbrev, int current)
+                          int abbrev, int current, char *prefix)
 {
        char c;
        int color;
        struct commit *commit = item->commit;
+       struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
 
        if (!matches_merge_filter(commit))
                return;
 
        switch (item->kind) {
        case REF_LOCAL_BRANCH:
-               color = COLOR_BRANCH_LOCAL;
+               color = BRANCH_COLOR_LOCAL;
                break;
        case REF_REMOTE_BRANCH:
-               color = COLOR_BRANCH_REMOTE;
+               color = BRANCH_COLOR_REMOTE;
                break;
        default:
-               color = COLOR_BRANCH_PLAIN;
+               color = BRANCH_COLOR_PLAIN;
                break;
        }
 
        c = ' ';
        if (current) {
                c = '*';
-               color = COLOR_BRANCH_CURRENT;
+               color = BRANCH_COLOR_CURRENT;
        }
 
-       if (verbose) {
+       strbuf_addf(&name, "%s%s", prefix, item->name);
+       if (verbose)
+               strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
+                           maxwidth, name.buf,
+                           branch_get_color(BRANCH_COLOR_RESET));
+       else
+               strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
+                           name.buf, branch_get_color(BRANCH_COLOR_RESET));
+
+       if (item->dest)
+               strbuf_addf(&out, " -> %s", item->dest);
+       else if (verbose) {
                struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
                const char *sub = " **** invalid ref ****";
 
@@ -346,30 +390,27 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
                }
 
                if (item->kind == REF_LOCAL_BRANCH)
-                       fill_tracking_info(&stat, item->name);
+                       fill_tracking_info(&stat, item->name, verbose > 1);
 
-               printf("%c %s%-*s%s %s %s%s\n", c, branch_get_color(color),
-                      maxwidth, item->name,
-                      branch_get_color(COLOR_BRANCH_RESET),
-                      find_unique_abbrev(item->commit->object.sha1, abbrev),
-                      stat.buf, sub);
+               strbuf_addf(&out, " %s %s%s",
+                       find_unique_abbrev(item->commit->object.sha1, abbrev),
+                       stat.buf, sub);
                strbuf_release(&stat);
                strbuf_release(&subject);
-       } else {
-               printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
-                      branch_get_color(COLOR_BRANCH_RESET));
        }
+       printf("%s\n", out.buf);
+       strbuf_release(&name);
+       strbuf_release(&out);
 }
 
 static int calc_maxwidth(struct ref_list *refs)
 {
-       int i, l, w = 0;
+       int i, w = 0;
        for (i = 0; i < refs->index; i++) {
                if (!matches_merge_filter(refs->list[i].commit))
                        continue;
-               l = strlen(refs->list[i].name);
-               if (l > w)
-                       w = l;
+               if (refs->list[i].len > w)
+                       w = refs->list[i].len;
        }
        return w;
 }
@@ -401,14 +442,17 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
        qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
 
        detached = (detached && (kinds & REF_LOCAL_BRANCH));
-       if (detached && head_commit && has_commit(head_commit, with_commit)) {
+       if (detached && head_commit &&
+           is_descendant_of(head_commit, with_commit)) {
                struct ref_item item;
                item.name = xstrdup("(no branch)");
+               item.len = strlen(item.name);
                item.kind = REF_LOCAL_BRANCH;
+               item.dest = NULL;
                item.commit = head_commit;
-               if (strlen(item.name) > ref_list.maxwidth)
-                       ref_list.maxwidth = strlen(item.name);
-               print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1);
+               if (item.len > ref_list.maxwidth)
+                       ref_list.maxwidth = item.len;
+               print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1, "");
                free(item.name);
        }
 
@@ -416,8 +460,11 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
                int current = !detached &&
                        (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
                        !strcmp(ref_list.list[i].name, head);
+               char *prefix = (kinds != REF_REMOTE_BRANCH &&
+                               ref_list.list[i].kind == REF_REMOTE_BRANCH)
+                               ? "remotes/" : "";
                print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
-                              abbrev, current);
+                              abbrev, current, prefix);
        }
 
        free_ref_list(&ref_list);
@@ -428,22 +475,27 @@ static void rename_branch(const char *oldname, const char *newname, int force)
        struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
        unsigned char sha1[20];
        struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
+       int recovery = 0;
 
        if (!oldname)
                die("cannot rename the current branch while not on any.");
 
-       strbuf_addf(&oldref, "refs/heads/%s", oldname);
-
-       if (check_ref_format(oldref.buf))
-               die("Invalid branch name: %s", oldref.buf);
-
-       strbuf_addf(&newref, "refs/heads/%s", newname);
+       if (strbuf_check_branch_ref(&oldref, oldname)) {
+               /*
+                * Bad name --- this could be an attempt to rename a
+                * ref that we used to allow to be created by accident.
+                */
+               if (resolve_ref(oldref.buf, sha1, 1, NULL))
+                       recovery = 1;
+               else
+                       die("Invalid branch name: '%s'", oldname);
+       }
 
-       if (check_ref_format(newref.buf))
-               die("Invalid branch name: %s", newref.buf);
+       if (strbuf_check_branch_ref(&newref, newname))
+               die("Invalid branch name: '%s'", newname);
 
        if (resolve_ref(newref.buf, sha1, 1, NULL) && !force)
-               die("A branch named '%s' already exists.", newname);
+               die("A branch named '%s' already exists.", newref.buf + 11);
 
        strbuf_addf(&logmsg, "Branch: renamed %s to %s",
                 oldref.buf, newref.buf);
@@ -452,6 +504,9 @@ static void rename_branch(const char *oldname, const char *newname, int force)
                die("Branch rename failed");
        strbuf_release(&logmsg);
 
+       if (recovery)
+               warning("Renamed a misnamed branch '%s' away", oldref.buf + 11);
+
        /* no need to pass logmsg here as HEAD didn't really move */
        if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
                die("Branch renamed to %s, but HEAD is not updated!", newname);
@@ -466,22 +521,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 +556,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..43ffe7ffae90322d757e110d8693a936209236f0 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)
@@ -201,8 +201,8 @@ static int batch_objects(int print_contents)
 }
 
 static const char * const cat_file_usage[] = {
-       "git cat-file [-t|-s|-e|-p|<type>] <sha1>",
-       "git cat-file [--batch|--batch-check] < <list_of_sha1s>",
+       "git cat-file (-t|-s|-e|-p|<type>) <object>",
+       "git cat-file (--batch|--batch-check) < <list_of_objects>",
        NULL
 };
 
index 701de439ae0f508f3a8ab41559230357be60637e..f9381e07eaeda673e91ef6250eca4027c5ba0278 100644 (file)
@@ -5,9 +5,18 @@
 #include "cache.h"
 #include "refs.h"
 #include "builtin.h"
+#include "strbuf.h"
 
 int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
 {
+       if (argc == 3 && !strcmp(argv[1], "--branch")) {
+               struct strbuf sb = STRBUF_INIT;
+
+               if (strbuf_check_branch_ref(&sb, argv[2]))
+                       die("'%s' is not a valid branch name", argv[2]);
+               printf("%s\n", sb.buf + 11);
+               exit(0);
+       }
        if (argc != 2)
                usage("git check-ref-format refname");
        return !!check_ref_format(argv[1]);
index 0d534bc023a1a3367bdf19f6450ce17e627959f0..afe35e246c5ab2b262683308def787c6abce2a83 100644 (file)
@@ -124,7 +124,7 @@ static int checkout_file(const char *name, int prefix_length)
 static void checkout_all(const char *prefix, int prefix_length)
 {
        int i, errs = 0;
-       struct cache_entrylast_ce = NULL;
+       struct cache_entry *last_ce = NULL;
 
        for (i = 0; i < active_nr ; i++) {
                struct cache_entry *ce = active_cache[i];
@@ -278,7 +278,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
                p = prefix_path(prefix, prefix_length, arg);
                checkout_file(p, prefix_length);
                if (p < arg || p > arg + strlen(arg))
-                       free((char*)p);
+                       free((char *)p);
        }
 
        if (read_from_stdin) {
index f1342abd025a3dc5f533205f623c1a82ffbf2ebe..b8a4b0139b3b9c2f731315413a73db30edaf9231 100644 (file)
@@ -5,6 +5,7 @@
 #include "commit.h"
 #include "tree.h"
 #include "tree-walk.h"
+#include "cache-tree.h"
 #include "unpack-trees.h"
 #include "dir.h"
 #include "run-command.h"
@@ -38,23 +39,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,
@@ -63,9 +54,6 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
        int len;
        struct cache_entry *ce;
 
-       if (S_ISGITLINK(mode))
-               return 0;
-
        if (S_ISDIR(mode))
                return READ_TREE_RECURSIVE;
 
@@ -191,7 +179,7 @@ static int checkout_merged(int pos, struct checkout *state)
        /*
         * NEEDSWORK:
         * There is absolutely no reason to write this as a blob object
-        * and create a phoney cache entry just to leak.  This hack is
+        * and create a phony cache entry just to leak.  This hack is
         * primarily to get to the write_entry() machinery that massages
         * the contents to work-tree format and writes out which only
         * allows it for a cache entry.  The code in write_entry() needs
@@ -240,7 +228,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 +237,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 +262,7 @@ static int checkout_paths(struct tree *source_tree, const char **pathspec,
        state.refresh_cache = 1;
        for (pos = 0; pos < active_nr; pos++) {
                struct cache_entry *ce = active_cache[pos];
-               if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+               if (match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL)) {
                        if (!ce_stage(ce)) {
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
@@ -305,6 +293,8 @@ static void show_local_changes(struct object *head)
        init_revisions(&rev, NULL);
        rev.abbrev = 0;
        rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+       if (diff_setup_done(&rev.diffopt) < 0)
+               die("diff_setup_done failed");
        add_pending_object(&rev, head, NULL);
        run_diff_index(&rev, 0);
 }
@@ -361,8 +351,11 @@ 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);
+
+       strbuf_branchname(&buf, branch->name);
+       if (strcmp(buf.buf, branch->name))
+               branch->name = xstrdup(buf.buf);
+       strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
        branch->path = strbuf_detach(&buf, NULL);
 }
 
@@ -407,7 +400,7 @@ static int merge_working_tree(struct checkout_opts *opts,
                topts.verbose_update = !opts->quiet;
                topts.fn = twoway_merge;
                topts.dir = xcalloc(1, sizeof(*topts.dir));
-               topts.dir->show_ignored = 1;
+               topts.dir->flags |= DIR_SHOW_IGNORED;
                topts.dir->exclude_per_dir = ".gitignore";
                tree = parse_tree_indirect(old->commit->object.sha1);
                init_tree_desc(&trees[0], tree->buffer, tree->size);
@@ -503,10 +496,10 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                create_symref("HEAD", new->path, msg.buf);
                if (!opts->quiet) {
                        if (old->path && !strcmp(new->path, old->path))
-                               fprintf(stderr, "Already on \"%s\"\n",
+                               fprintf(stderr, "Already on '%s'\n",
                                        new->name);
                        else
-                               fprintf(stderr, "Switched to%s branch \"%s\"\n",
+                               fprintf(stderr, "Switched to%s branch '%s'\n",
                                        opts->new_branch ? " a new" : "",
                                        new->name);
                }
@@ -515,7 +508,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
                           REF_NODEREF, DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path)
-                               fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
+                               fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n  git checkout -b <new_branch_name>\n", new->name);
                        describe_detached_head("HEAD is now at", new->commit);
                }
        }
@@ -548,18 +541,10 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
                parse_commit(new->commit);
        }
 
-       /*
-        * If we were on a detached HEAD, but we are now moving to
-        * a new commit, we want to mention the old commit once more
-        * to remind the user that it might be lost.
-        */
-       if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
-               describe_detached_head("Previous HEAD position was", old.commit);
-
        if (!old.commit && !opts->force) {
                if (!opts->quiet) {
-                       fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n");
-                       fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name);
+                       warning("You appear to be on a branch yet to be born.");
+                       warning("Forcing checkout of %s.", new->name);
                }
                opts->force = 1;
        }
@@ -568,6 +553,14 @@ static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
        if (ret)
                return ret;
 
+       /*
+        * If we were on a detached HEAD, but have now moved to
+        * a new commit, we want to mention the old commit once more
+        * to remind the user that it might be lost.
+        */
+       if (!opts->quiet && !old.path && old.commit && new->commit != old.commit)
+               describe_detached_head("Previous HEAD position was", old.commit);
+
        update_refs_for_switch(opts, &old, new);
 
        ret = post_checkout_hook(old.commit, new->commit, 1);
@@ -671,6 +664,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);
@@ -735,12 +731,11 @@ no_reference:
 
        if (opts.new_branch) {
                struct strbuf buf = STRBUF_INIT;
-               strbuf_addstr(&buf, "refs/heads/");
-               strbuf_addstr(&buf, opts.new_branch);
+               if (strbuf_check_branch_ref(&buf, opts.new_branch))
+                       die("git checkout: we do not like '%s' as a branch name.",
+                           opts.new_branch);
                if (!get_sha1(buf.buf, rev))
                        die("git checkout: branch %s already exists", opts.new_branch);
-               if (check_ref_format(buf.buf))
-                       die("git checkout: we do not like '%s' as a branch name.", opts.new_branch);
                strbuf_release(&buf);
        }
 
index f78c2fb108bc667079290f9b2fa82f47da5eb34c..c5ad33d3e665f6d1613df2dfba235b03765565ac 100644 (file)
@@ -60,7 +60,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
 
        memset(&dir, 0, sizeof(dir));
        if (ignored_only)
-               dir.show_ignored = 1;
+               dir.flags |= DIR_SHOW_IGNORED;
 
        if (ignored && ignored_only)
                die("-x and -X cannot be used together");
@@ -69,7 +69,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix)
                die("clean.requireForce%s set and -n or -f not given; "
                    "refusing to clean", config_set ? "" : " not");
 
-       dir.show_other_directories = 1;
+       dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
 
        if (!ignored)
                setup_standard_excludes(&dir);
index 2feac9c5cb8e85ae2b25c5a7a0a602e84e210f4a..ba286e0160c494244810334e3a0b43a59756e84a 100644 (file)
 #include "strbuf.h"
 #include "dir.h"
 #include "pack-refs.h"
+#include "sigchain.h"
+#include "branch.h"
+#include "remote.h"
+#include "run-command.h"
 
 /*
  * Overall FIXMEs:
@@ -192,15 +196,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 +228,17 @@ 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: %s",
+                           dest->buf, strerror(errno));
                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);
 }
@@ -266,7 +271,7 @@ static const struct ref *clone_local(const char *src_repo,
 
 static const char *junk_work_tree;
 static const char *junk_git_dir;
-pid_t junk_pid;
+static pid_t junk_pid;
 
 static void remove_junk(void)
 {
@@ -288,47 +293,10 @@ static void remove_junk(void)
 static void remove_junk_on_signal(int signo)
 {
        remove_junk();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
-static const struct ref *locate_head(const struct ref *refs,
-                                    const struct ref *mapped_refs,
-                                    const struct ref **remote_head_p)
-{
-       const struct ref *remote_head = NULL;
-       const struct ref *remote_master = NULL;
-       const struct ref *r;
-       for (r = refs; r; r = r->next)
-               if (!strcmp(r->name, "HEAD"))
-                       remote_head = r;
-
-       for (r = mapped_refs; r; r = r->next)
-               if (!strcmp(r->name, "refs/heads/master"))
-                       remote_master = r;
-
-       if (remote_head_p)
-               *remote_head_p = remote_head;
-
-       /* If there's no HEAD value at all, never mind. */
-       if (!remote_head)
-               return NULL;
-
-       /* If refs/heads/master could be right, it is. */
-       if (remote_master && !hashcmp(remote_master->old_sha1,
-                                     remote_head->old_sha1))
-               return remote_master;
-
-       /* Look for another ref that points there */
-       for (r = mapped_refs; r; r = r->next)
-               if (r != remote_head &&
-                   !hashcmp(r->old_sha1, remote_head->old_sha1))
-                       return r;
-
-       /* Nothing is the same */
-       return NULL;
-}
-
 static struct ref *write_remote_refs(const struct ref *refs,
                struct refspec *refspec, const char *reflog)
 {
@@ -351,19 +319,20 @@ static struct ref *write_remote_refs(const struct ref *refs,
 
 int cmd_clone(int argc, const char **argv, const char *prefix)
 {
-       int use_local_hardlinks = 1;
-       int use_separate_remote = 1;
        int is_bundle = 0;
        struct stat buf;
        const char *repo_name, *repo, *work_tree, *git_dir;
        char *path, *dir;
+       int dest_exists;
        const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
        struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
        struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
        struct transport *transport = NULL;
        char *src_ref_prefix = "refs/heads/";
+       int err = 0;
 
-       struct refspec refspec;
+       struct refspec *refspec;
+       const char *fetch_pattern;
 
        junk_pid = getpid();
 
@@ -373,9 +342,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        if (argc == 0)
                die("You must specify a repository to clone.");
 
-       if (option_no_hardlinks)
-               use_local_hardlinks = 0;
-
        if (option_mirror)
                option_bare = 1;
 
@@ -384,7 +350,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                        die("--bare and --origin %s options are incompatible.",
                            option_origin);
                option_no_checkout = 1;
-               use_separate_remote = 0;
        }
 
        if (!option_origin)
@@ -406,8 +371,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,16 +398,16 @@ 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);
+       setenv(CONFIG_ENVIRONMENT, mkpath("%s/config", git_dir), 1);
 
        if (safe_create_leading_directories_const(git_dir) < 0)
                die("could not create leading directories of '%s'", git_dir);
@@ -470,8 +437,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin);
        }
 
+       strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
+
        if (option_mirror || !option_bare) {
                /* Configure the remote */
+               strbuf_addf(&key, "remote.%s.fetch", option_origin);
+               git_config_set_multivar(key.buf, value.buf, "^$", 0);
+               strbuf_reset(&key);
+
                if (option_mirror) {
                        strbuf_addf(&key, "remote.%s.mirror", option_origin);
                        git_config_set(key.buf, "true");
@@ -480,19 +453,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 
                strbuf_addf(&key, "remote.%s.url", option_origin);
                git_config_set(key.buf, repo);
-                       strbuf_reset(&key);
-
-               strbuf_addf(&key, "remote.%s.fetch", option_origin);
-               strbuf_addf(&value, "+%s*:%s*", src_ref_prefix, branch_top.buf);
-               git_config_set_multivar(key.buf, value.buf, "^$", 0);
                strbuf_reset(&key);
-               strbuf_reset(&value);
        }
 
-       refspec.force = 0;
-       refspec.pattern = 1;
-       refspec.src = src_ref_prefix;
-       refspec.dst = branch_top.buf;
+       fetch_pattern = value.buf;
+       refspec = parse_fetch_refspec(1, &fetch_pattern);
+
+       strbuf_reset(&value);
 
        if (path && !is_bundle)
                refs = clone_local(path, git_dir);
@@ -519,14 +486,27 @@ 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);
+               remote_head = find_ref_by_name(refs, "HEAD");
+               head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
+       }
+       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(0, "master", option_origin,
+                                             "refs/heads/master");
+       }
 
        if (head_points_at) {
                /* Local default branch link */
@@ -554,11 +534,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(0, head, option_origin,
+                                             head_points_at->name);
                }
        } else if (remote_head) {
                /* Source had detached HEAD pointing somewhere. */
@@ -605,6 +582,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
                if (write_cache(fd, active_cache, active_nr) ||
                    commit_locked_index(lock_file))
                        die("unable to write new index file");
+
+               err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
+                               sha1_to_hex(remote_head->old_sha1), "1", NULL);
        }
 
        strbuf_release(&reflog_msg);
@@ -612,5 +592,5 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
        strbuf_release(&key);
        strbuf_release(&value);
        junk_pid = 0;
-       return 0;
+       return err;
 }
index 72dd0b95531cc8024ea106b14bbe847e956d308c..81371b1d2698a48dba36046d7ff9d849f830a762 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);
        }
@@ -362,40 +362,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);
@@ -596,7 +562,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";
 
@@ -608,16 +573,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);
@@ -884,7 +841,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);
 
@@ -911,7 +868,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
 
-       printf("[%s%s]: created ",
+       printf("[%s%s ",
                !prefixcmp(head, "refs/heads/") ?
                        head + 11 :
                        !strcmp(head, "HEAD") ?
index f71016204b540d0d935323c909a0ffccb1abdbe2..a81bc8bbf033fac83ad6deecbf4a67ba44e06a0b 100644 (file)
@@ -1,9 +1,12 @@
 #include "builtin.h"
 #include "cache.h"
 #include "color.h"
+#include "parse-options.h"
 
-static const char git_config_set_usage[] =
-"git config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --int | --bool-or-int ] [ -z | --null ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --remove-section name | --list | --get-color var [default] | --get-colorbool name [stdout-is-tty]";
+static const char *const builtin_config_usage[] = {
+       "git config [options]",
+       NULL
+};
 
 static char *key;
 static regex_t *key_regexp;
@@ -16,7 +19,67 @@ static int seen;
 static char delim = '=';
 static char key_delim = ' ';
 static char term = '\n';
-static enum { T_RAW, T_INT, T_BOOL, T_BOOL_OR_INT } type = T_RAW;
+
+static int use_global_config, use_system_config;
+static const char *given_config_file;
+static int actions, types;
+static const char *get_color_slot, *get_colorbool_slot;
+static int end_null;
+
+#define ACTION_GET (1<<0)
+#define ACTION_GET_ALL (1<<1)
+#define ACTION_GET_REGEXP (1<<2)
+#define ACTION_REPLACE_ALL (1<<3)
+#define ACTION_ADD (1<<4)
+#define ACTION_UNSET (1<<5)
+#define ACTION_UNSET_ALL (1<<6)
+#define ACTION_RENAME_SECTION (1<<7)
+#define ACTION_REMOVE_SECTION (1<<8)
+#define ACTION_LIST (1<<9)
+#define ACTION_EDIT (1<<10)
+#define ACTION_SET (1<<11)
+#define ACTION_SET_ALL (1<<12)
+#define ACTION_GET_COLOR (1<<13)
+#define ACTION_GET_COLORBOOL (1<<14)
+
+#define TYPE_BOOL (1<<0)
+#define TYPE_INT (1<<1)
+#define TYPE_BOOL_OR_INT (1<<2)
+
+static struct option builtin_config_options[] = {
+       OPT_GROUP("Config file location"),
+       OPT_BOOLEAN(0, "global", &use_global_config, "use global config file"),
+       OPT_BOOLEAN(0, "system", &use_system_config, "use system config file"),
+       OPT_STRING('f', "file", &given_config_file, "FILE", "use given config file"),
+       OPT_GROUP("Action"),
+       OPT_BIT(0, "get", &actions, "get value: name [value-regex]", ACTION_GET),
+       OPT_BIT(0, "get-all", &actions, "get all values: key [value-regex]", ACTION_GET_ALL),
+       OPT_BIT(0, "get-regexp", &actions, "get values for regexp: name-regex [value-regex]", ACTION_GET_REGEXP),
+       OPT_BIT(0, "replace-all", &actions, "replace all matching variables: name value [value_regex]", ACTION_REPLACE_ALL),
+       OPT_BIT(0, "add", &actions, "adds a new variable: name value", ACTION_ADD),
+       OPT_BIT(0, "unset", &actions, "removes a variable: name [value-regex]", ACTION_UNSET),
+       OPT_BIT(0, "unset-all", &actions, "removes all matches: name [value-regex]", ACTION_UNSET_ALL),
+       OPT_BIT(0, "rename-section", &actions, "rename section: old-name new-name", ACTION_RENAME_SECTION),
+       OPT_BIT(0, "remove-section", &actions, "remove a section: name", ACTION_REMOVE_SECTION),
+       OPT_BIT('l', "list", &actions, "list all", ACTION_LIST),
+       OPT_BIT('e', "edit", &actions, "opens an editor", ACTION_EDIT),
+       OPT_STRING(0, "get-color", &get_color_slot, "slot", "find the color configured: [default]"),
+       OPT_STRING(0, "get-colorbool", &get_colorbool_slot, "slot", "find the color setting: [stdout-is-tty]"),
+       OPT_GROUP("Type"),
+       OPT_BIT(0, "bool", &types, "value is \"true\" or \"false\"", TYPE_BOOL),
+       OPT_BIT(0, "int", &types, "value is decimal number", TYPE_INT),
+       OPT_BIT(0, "bool-or-int", &types, "value is --bool or --int", TYPE_BOOL_OR_INT),
+       OPT_GROUP("Other"),
+       OPT_BOOLEAN('z', "null", &end_null, "terminate values with NUL byte"),
+       OPT_END(),
+};
+
+static void check_argc(int argc, int min, int max) {
+       if (argc >= min && argc <= max)
+               return;
+       error("wrong number of arguments");
+       usage_with_options(builtin_config_usage, builtin_config_options);
+}
 
 static int show_all_config(const char *key_, const char *value_, void *cb)
 {
@@ -27,7 +90,7 @@ static int show_all_config(const char *key_, const char *value_, void *cb)
        return 0;
 }
 
-static int show_config(const char* key_, const char* value_, void *cb)
+static int show_config(const char *key_, const char *value_, void *cb)
 {
        char value[256];
        const char *vptr = value;
@@ -49,11 +112,11 @@ static int show_config(const char* key_, const char* value_, void *cb)
        }
        if (seen && !do_all)
                dup_error = 1;
-       if (type == T_INT)
+       if (types == TYPE_INT)
                sprintf(value, "%d", git_config_int(key_, value_?value_:""));
-       else if (type == T_BOOL)
+       else if (types == TYPE_BOOL)
                vptr = git_config_bool(key_, value_) ? "true" : "false";
-       else if (type == T_BOOL_OR_INT) {
+       else if (types == TYPE_BOOL_OR_INT) {
                int is_bool, v;
                v = git_config_bool_or_int(key_, value_, &is_bool);
                if (is_bool)
@@ -74,7 +137,7 @@ static int show_config(const char* key_, const char* value_, void *cb)
        return 0;
 }
 
-static int get_value(const char* key_, const char* regex_)
+static int get_value(const char *key_, const char *regex_)
 {
        int ret = -1;
        char *tl;
@@ -152,18 +215,18 @@ static char *normalize_value(const char *key, const char *value)
        if (!value)
                return NULL;
 
-       if (type == T_RAW)
+       if (types == 0)
                normalized = xstrdup(value);
        else {
                normalized = xmalloc(64);
-               if (type == T_INT) {
+               if (types == TYPE_INT) {
                        int v = git_config_int(key, value);
                        sprintf(normalized, "%d", v);
                }
-               else if (type == T_BOOL)
+               else if (types == TYPE_BOOL)
                        sprintf(normalized, "%s",
                                git_config_bool(key, value) ? "true" : "false");
-               else if (type == T_BOOL_OR_INT) {
+               else if (types == TYPE_BOOL_OR_INT) {
                        int is_bool, v;
                        v = git_config_bool_or_int(key, value, &is_bool);
                        if (!is_bool)
@@ -178,6 +241,7 @@ static char *normalize_value(const char *key, const char *value)
 
 static int get_color_found;
 static const char *get_color_slot;
+static const char *get_colorbool_slot;
 static char parsed_color[COLOR_MAXLEN];
 
 static int git_get_color_config(const char *var, const char *value, void *cb)
@@ -191,29 +255,8 @@ static int git_get_color_config(const char *var, const char *value, void *cb)
        return 0;
 }
 
-static int get_color(int argc, const char **argv)
+static void get_color(const char *def_color)
 {
-       /*
-        * grab the color setting for the given slot from the configuration,
-        * or parse the default value if missing, and return ANSI color
-        * escape sequence.
-        *
-        * e.g.
-        * git config --get-color color.diff.whitespace "blue reverse"
-        */
-       const char *def_color = NULL;
-
-       switch (argc) {
-       default:
-               usage(git_config_set_usage);
-       case 2:
-               def_color = argv[1];
-               /* fallthru */
-       case 1:
-               get_color_slot = argv[0];
-               break;
-       }
-
        get_color_found = 0;
        parsed_color[0] = '\0';
        git_config(git_get_color_config, NULL);
@@ -222,7 +265,6 @@ static int get_color(int argc, const char **argv)
                color_parse(def_color, "command line", parsed_color);
 
        fputs(parsed_color, stdout);
-       return 0;
 }
 
 static int stdout_is_tty;
@@ -231,7 +273,7 @@ static int get_diff_color_found;
 static int git_get_colorbool_config(const char *var, const char *value,
                void *cb)
 {
-       if (!strcmp(var, get_color_slot)) {
+       if (!strcmp(var, get_colorbool_slot)) {
                get_colorbool_found =
                        git_config_colorbool(var, value, stdout_is_tty);
        }
@@ -246,183 +288,190 @@ static int git_get_colorbool_config(const char *var, const char *value,
        return 0;
 }
 
-static int get_colorbool(int argc, const char **argv)
+static int get_colorbool(int print)
 {
-       /*
-        * git config --get-colorbool <slot> [<stdout-is-tty>]
-        *
-        * returns "true" or "false" depending on how <slot>
-        * is configured.
-        */
-
-       if (argc == 2)
-               stdout_is_tty = git_config_bool("command line", argv[1]);
-       else if (argc == 1)
-               stdout_is_tty = isatty(1);
-       else
-               usage(git_config_set_usage);
        get_colorbool_found = -1;
        get_diff_color_found = -1;
-       get_color_slot = argv[0];
        git_config(git_get_colorbool_config, NULL);
 
        if (get_colorbool_found < 0) {
-               if (!strcmp(get_color_slot, "color.diff"))
+               if (!strcmp(get_colorbool_slot, "color.diff"))
                        get_colorbool_found = get_diff_color_found;
                if (get_colorbool_found < 0)
                        get_colorbool_found = git_use_color_default;
        }
 
-       if (argc == 1) {
-               return get_colorbool_found ? 0 : 1;
-       } else {
+       if (print) {
                printf("%s\n", get_colorbool_found ? "true" : "false");
                return 0;
-       }
+       } else
+               return get_colorbool_found ? 0 : 1;
 }
 
-int cmd_config(int argc, const char **argv, const char *prefix)
+int cmd_config(int argc, const char **argv, const char *unused_prefix)
 {
        int nongit;
-       charvalue;
-       const char *file = setup_git_directory_gently(&nongit);
+       char *value;
+       const char *prefix = setup_git_directory_gently(&nongit);
 
        config_exclusive_filename = getenv(CONFIG_ENVIRONMENT);
 
-       while (1 < argc) {
-               if (!strcmp(argv[1], "--int"))
-                       type = T_INT;
-               else if (!strcmp(argv[1], "--bool"))
-                       type = T_BOOL;
-               else if (!strcmp(argv[1], "--bool-or-int"))
-                       type = T_BOOL_OR_INT;
-               else if (!strcmp(argv[1], "--list") || !strcmp(argv[1], "-l")) {
-                       if (argc != 2)
-                               usage(git_config_set_usage);
-                       if (git_config(show_all_config, NULL) < 0 &&
-                                       file && errno)
-                               die("unable to read config file %s: %s", file,
-                                   strerror(errno));
-                       return 0;
-               }
-               else if (!strcmp(argv[1], "--global")) {
-                       char *home = getenv("HOME");
-                       if (home) {
-                               char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
-                               config_exclusive_filename = user_config;
-                       } else {
-                               die("$HOME not set");
-                       }
-               }
-               else if (!strcmp(argv[1], "--system"))
-                       config_exclusive_filename = git_etc_gitconfig();
-               else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) {
-                       if (argc < 3)
-                               usage(git_config_set_usage);
-                       if (!is_absolute_path(argv[2]) && file)
-                               file = prefix_filename(file, strlen(file),
-                                                      argv[2]);
-                       else
-                               file = argv[2];
-                       config_exclusive_filename = file;
-                       argc--;
-                       argv++;
-               }
-               else if (!strcmp(argv[1], "--null") || !strcmp(argv[1], "-z")) {
-                       term = '\0';
-                       delim = '\n';
-                       key_delim = '\n';
-               }
-               else if (!strcmp(argv[1], "--rename-section")) {
-                       int ret;
-                       if (argc != 4)
-                               usage(git_config_set_usage);
-                       ret = git_config_rename_section(argv[2], argv[3]);
-                       if (ret < 0)
-                               return ret;
-                       if (ret == 0) {
-                               fprintf(stderr, "No such section!\n");
-                               return 1;
-                       }
-                       return 0;
-               }
-               else if (!strcmp(argv[1], "--remove-section")) {
-                       int ret;
-                       if (argc != 3)
-                               usage(git_config_set_usage);
-                       ret = git_config_rename_section(argv[2], NULL);
-                       if (ret < 0)
-                               return ret;
-                       if (ret == 0) {
-                               fprintf(stderr, "No such section!\n");
-                               return 1;
-                       }
-                       return 0;
-               } else if (!strcmp(argv[1], "--get-color")) {
-                       return get_color(argc-2, argv+2);
-               } else if (!strcmp(argv[1], "--get-colorbool")) {
-                       return get_colorbool(argc-2, argv+2);
-               } else
-                       break;
-               argc--;
-               argv++;
-       }
-
-       switch (argc) {
-       case 2:
-               return get_value(argv[1], NULL);
-       case 3:
-               if (!strcmp(argv[1], "--unset"))
-                       return git_config_set(argv[2], NULL);
-               else if (!strcmp(argv[1], "--unset-all"))
-                       return git_config_set_multivar(argv[2], NULL, NULL, 1);
-               else if (!strcmp(argv[1], "--get"))
-                       return get_value(argv[2], NULL);
-               else if (!strcmp(argv[1], "--get-all")) {
-                       do_all = 1;
-                       return get_value(argv[2], NULL);
-               } else if (!strcmp(argv[1], "--get-regexp")) {
-                       show_keys = 1;
-                       use_key_regexp = 1;
-                       do_all = 1;
-                       return get_value(argv[2], NULL);
+       argc = parse_options(argc, argv, builtin_config_options, builtin_config_usage,
+                            PARSE_OPT_STOP_AT_NON_OPTION);
+
+       if (use_global_config + use_system_config + !!given_config_file > 1) {
+               error("only one config file at a time.");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+
+       if (use_global_config) {
+               char *home = getenv("HOME");
+               if (home) {
+                       char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
+                       config_exclusive_filename = user_config;
                } else {
-                       value = normalize_value(argv[1], argv[2]);
-                       return git_config_set(argv[1], value);
+                       die("$HOME not set");
                }
-       case 4:
-               if (!strcmp(argv[1], "--unset"))
-                       return git_config_set_multivar(argv[2], NULL, argv[3], 0);
-               else if (!strcmp(argv[1], "--unset-all"))
-                       return git_config_set_multivar(argv[2], NULL, argv[3], 1);
-               else if (!strcmp(argv[1], "--get"))
-                       return get_value(argv[2], argv[3]);
-               else if (!strcmp(argv[1], "--get-all")) {
-                       do_all = 1;
-                       return get_value(argv[2], argv[3]);
-               } else if (!strcmp(argv[1], "--get-regexp")) {
-                       show_keys = 1;
-                       use_key_regexp = 1;
-                       do_all = 1;
-                       return get_value(argv[2], argv[3]);
-               } else if (!strcmp(argv[1], "--add")) {
-                       value = normalize_value(argv[2], argv[3]);
-                       return git_config_set_multivar(argv[2], value, "^$", 0);
-               } else if (!strcmp(argv[1], "--replace-all")) {
-                       value = normalize_value(argv[2], argv[3]);
-                       return git_config_set_multivar(argv[2], value, NULL, 1);
-               } else {
-                       value = normalize_value(argv[1], argv[2]);
-                       return git_config_set_multivar(argv[1], value, argv[3], 0);
+       }
+       else if (use_system_config)
+               config_exclusive_filename = git_etc_gitconfig();
+       else if (given_config_file) {
+               if (!is_absolute_path(given_config_file) && prefix)
+                       config_exclusive_filename = prefix_filename(prefix,
+                                                                   strlen(prefix),
+                                                                   argv[2]);
+               else
+                       config_exclusive_filename = given_config_file;
+       }
+
+       if (end_null) {
+               term = '\0';
+               delim = '\n';
+               key_delim = '\n';
+       }
+
+       if (HAS_MULTI_BITS(types)) {
+               error("only one type at a time.");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+
+       if (get_color_slot)
+           actions |= ACTION_GET_COLOR;
+       if (get_colorbool_slot)
+           actions |= ACTION_GET_COLORBOOL;
+
+       if ((get_color_slot || get_colorbool_slot) && types) {
+               error("--get-color and variable type are incoherent");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+
+       if (HAS_MULTI_BITS(actions)) {
+               error("only one action at a time.");
+               usage_with_options(builtin_config_usage, builtin_config_options);
+       }
+       if (actions == 0)
+               switch (argc) {
+               case 1: actions = ACTION_GET; break;
+               case 2: actions = ACTION_SET; break;
+               case 3: actions = ACTION_SET_ALL; break;
+               default:
+                       usage_with_options(builtin_config_usage, builtin_config_options);
                }
-       case 5:
-               if (!strcmp(argv[1], "--replace-all")) {
-                       value = normalize_value(argv[2], argv[3]);
-                       return git_config_set_multivar(argv[2], value, argv[4], 1);
+
+       if (actions == ACTION_LIST) {
+               check_argc(argc, 0, 0);
+               if (git_config(show_all_config, NULL) < 0) {
+                       if (config_exclusive_filename)
+                               die("unable to read config file %s: %s",
+                                   config_exclusive_filename, strerror(errno));
+                       else
+                               die("error processing config file(s)");
                }
-       case 1:
-       default:
-               usage(git_config_set_usage);
        }
+       else if (actions == ACTION_EDIT) {
+               check_argc(argc, 0, 0);
+               if (!config_exclusive_filename && nongit)
+                       die("not in a git directory");
+               git_config(git_default_config, NULL);
+               launch_editor(config_exclusive_filename ?
+                             config_exclusive_filename : git_path("config"),
+                             NULL, NULL);
+       }
+       else if (actions == ACTION_SET) {
+               check_argc(argc, 2, 2);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set(argv[0], value);
+       }
+       else if (actions == ACTION_SET_ALL) {
+               check_argc(argc, 2, 3);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set_multivar(argv[0], value, argv[2], 0);
+       }
+       else if (actions == ACTION_ADD) {
+               check_argc(argc, 2, 2);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set_multivar(argv[0], value, "^$", 0);
+       }
+       else if (actions == ACTION_REPLACE_ALL) {
+               check_argc(argc, 2, 3);
+               value = normalize_value(argv[0], argv[1]);
+               return git_config_set_multivar(argv[0], value, argv[2], 1);
+       }
+       else if (actions == ACTION_GET) {
+               check_argc(argc, 1, 2);
+               return get_value(argv[0], argv[1]);
+       }
+       else if (actions == ACTION_GET_ALL) {
+               do_all = 1;
+               check_argc(argc, 1, 2);
+               return get_value(argv[0], argv[1]);
+       }
+       else if (actions == ACTION_GET_REGEXP) {
+               show_keys = 1;
+               use_key_regexp = 1;
+               do_all = 1;
+               check_argc(argc, 1, 2);
+               return get_value(argv[0], argv[1]);
+       }
+       else if (actions == ACTION_UNSET) {
+               check_argc(argc, 1, 2);
+               if (argc == 2)
+                       return git_config_set_multivar(argv[0], NULL, argv[1], 0);
+               else
+                       return git_config_set(argv[0], NULL);
+       }
+       else if (actions == ACTION_UNSET_ALL) {
+               check_argc(argc, 1, 2);
+               return git_config_set_multivar(argv[0], NULL, argv[1], 1);
+       }
+       else if (actions == ACTION_RENAME_SECTION) {
+               int ret;
+               check_argc(argc, 2, 2);
+               ret = git_config_rename_section(argv[0], argv[1]);
+               if (ret < 0)
+                       return ret;
+               if (ret == 0)
+                       die("No such section!");
+       }
+       else if (actions == ACTION_REMOVE_SECTION) {
+               int ret;
+               check_argc(argc, 1, 1);
+               ret = git_config_rename_section(argv[0], NULL);
+               if (ret < 0)
+                       return ret;
+               if (ret == 0)
+                       die("No such section!");
+       }
+       else if (actions == ACTION_GET_COLOR) {
+               get_color(argv[0]);
+       }
+       else if (actions == ACTION_GET_COLORBOOL) {
+               if (argc == 1)
+                       stdout_is_tty = git_config_bool("command line", argv[0]);
+               else if (argc == 0)
+                       stdout_is_tty = isatty(1);
+               return get_colorbool(argc != 0);
+       }
+
        return 0;
 }
index 38b033fd711ac7428bea6e56eddef18d875befd3..b814fe5070873f5c87fc6bbfde480e3b0a83e397 100644 (file)
@@ -5,6 +5,7 @@
  */
 
 #include "cache.h"
+#include "dir.h"
 #include "builtin.h"
 #include "parse-options.h"
 
@@ -21,9 +22,7 @@ static void count_objects(DIR *d, char *path, int len, int verbose,
                const char *cp;
                int bad = 0;
 
-               if ((ent->d_name[0] == '.') &&
-                   (ent->d_name[1] == 0 ||
-                    ((ent->d_name[1] == '.') && (ent->d_name[2] == 0))))
+               if (is_dot_or_dotdot(ent->d_name))
                        continue;
                for (cp = ent->d_name; *cp; cp++) {
                        int ch = *cp;
index 3a007ed1cafcab82f466d355b63e416b06eb8864..63c6a19da5b38bc7c00c624c080ba0afbb10ff8a 100644 (file)
@@ -334,7 +334,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                die("--long is incompatible with --abbrev=0");
 
        if (contains) {
-               const char **args = xmalloc((7 + argc) * sizeof(char*));
+               const char **args = xmalloc((7 + argc) * sizeof(char *));
                int i = 0;
                args[i++] = "name-rev";
                args[i++] = "--name-only";
@@ -349,7 +349,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
                                args[i++] = s;
                        }
                }
-               memcpy(args + i, argv, argc * sizeof(char*));
+               memcpy(args + i, argv, argc * sizeof(char *));
                args[i + argc] = NULL;
                return cmd_name_rev(i + argc, args, prefix);
        }
index 8ecefd4f0f99117534deb9ca7d4446ade5c00223..79cedb72c44dca3edf400d86e401a93531b54407 100644 (file)
@@ -102,7 +102,6 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix)
 
        init_revisions(opt, prefix);
        git_config(git_diff_basic_config, NULL); /* no "diff" UI options */
-       nr_sha1 = 0;
        opt->abbrev = 0;
        opt->diff = 1;
        argc = setup_revisions(argc, argv, opt, NULL);
index fdf4ae9ebdba7832a0ac736d56a7b564bba41baa..6731713223d4df24614417cc4285cbee793151d3 100644 (file)
@@ -221,7 +221,8 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
        if (message)
                message += 2;
 
-       if (commit->parents) {
+       if (commit->parents &&
+           get_object_mark(&commit->parents->item->object) != 0) {
                parse_commit(commit->parents->item);
                diff_tree_sha1(commit->parents->item->tree->object.sha1,
                               commit->tree->object.sha1, "", &rev->diffopt);
@@ -362,7 +363,10 @@ static void get_tags_and_duplicates(struct object_array *pending,
                        break;
                case OBJ_TAG:
                        tag = (struct tag *)e->item;
+
+                       /* handle nested tags */
                        while (tag && tag->object.type == OBJ_TAG) {
+                               parse_object(tag->object.sha1);
                                string_list_append(full_name, extra_refs)->util = tag;
                                tag = (struct tag *)tag->tagged;
                        }
@@ -375,11 +379,17 @@ static void get_tags_and_duplicates(struct object_array *pending,
                        case OBJ_BLOB:
                                handle_object(tag->object.sha1);
                                continue;
+                       default: /* OBJ_TAG (nested tags) is already handled */
+                               warning("Tag points to object of unexpected type %s, skipping.",
+                                       typename(tag->object.type));
+                               continue;
                        }
                        break;
                default:
-                       die ("Unexpected object of type %s",
-                            typename(e->item->type));
+                       warning("%s: Unexpected object of type %s, skipping.",
+                               e->name,
+                               typename(e->item->type));
+                       continue;
                }
                if (commit->util)
                        /* more than one name for the same object */
index 469b07e240953aa21fd67eb2563e094a7f0f3d42..29356d25db910c6d90df46da87aa374467611350 100644 (file)
@@ -2,6 +2,7 @@
 #include "cache.h"
 #include "refs.h"
 #include "commit.h"
+#include "sigchain.h"
 
 static char *get_stdin(void)
 {
@@ -186,7 +187,7 @@ static void remove_keep(void)
 static void remove_keep_on_signal(int signo)
 {
        remove_keep();
-       signal(SIGINT, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -245,7 +246,7 @@ static int fetch_native_store(FILE *fp,
        char buffer[1024];
        int err = 0;
 
-       signal(SIGINT, remove_keep_on_signal);
+       sigchain_push_common(remove_keep_on_signal);
        atexit(remove_keep);
 
        while (fgets(buffer, sizeof(buffer), stdin)) {
index 67fb80ec48d6d8f0327628afd86fb366d8f97617..6202462216f3b8c3cbe924d4cc002616e69d75ab 100644 (file)
@@ -13,6 +13,7 @@
 static int transfer_unpack_limit = -1;
 static int fetch_unpack_limit = -1;
 static int unpack_limit = 100;
+static int prefer_ofs_delta = 1;
 static struct fetch_pack_args args = {
        /* .uploadpack = */ "git-upload-pack",
 };
@@ -111,7 +112,7 @@ static void mark_common(struct commit *commit,
   Get the next rev to send, ignoring the common.
 */
 
-static const unsigned charget_rev(void)
+static const unsigned char *get_rev(void)
 {
        struct commit *commit = NULL;
 
@@ -200,7 +201,7 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                                     (args.use_thin_pack ? " thin-pack" : ""),
                                     (args.no_progress ? " no-progress" : ""),
                                     (args.include_tag ? " include-tag" : ""),
-                                    " ofs-delta");
+                                    (prefer_ofs_delta ? " ofs-delta" : ""));
                else
                        packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
                fetching++;
@@ -216,9 +217,8 @@ static int find_common(int fd[2], unsigned char *result_sha1,
        if (args.depth > 0) {
                char line[1024];
                unsigned char sha1[20];
-               int len;
 
-               while ((len = packet_read_line(fd[0], line, sizeof(line)))) {
+               while (packet_read_line(fd[0], line, sizeof(line))) {
                        if (!prefixcmp(line, "shallow ")) {
                                if (get_sha1_hex(line + 8, sha1))
                                        die("invalid shallow line: %s", line);
@@ -483,7 +483,7 @@ static int sideband_demux(int fd, void *data)
 {
        int *xd = data;
 
-       return recv_sideband("fetch-pack", xd[0], fd, 2);
+       return recv_sideband("fetch-pack", xd[0], fd);
 }
 
 static int get_pack(int xd[2], char **pack_lockfile)
@@ -597,6 +597,11 @@ static struct ref *do_fetch_pack(int fd[2],
                        fprintf(stderr, "Server supports side-band\n");
                use_sideband = 1;
        }
+       if (server_supports("ofs-delta")) {
+               if (args.verbose)
+                       fprintf(stderr, "Server supports ofs-delta\n");
+       } else
+               prefer_ofs_delta = 0;
        if (everything_local(&ref, nr_match, match)) {
                packet_flush(fd[1]);
                goto all_done;
@@ -606,7 +611,7 @@ static struct ref *do_fetch_pack(int fd[2],
                        /* When cloning, it is not unusual to have
                         * no common commit.
                         */
-                       fprintf(stderr, "warning: no common commits\n");
+                       warning("no common commits");
 
        if (get_pack(fd, pack_lockfile))
                die("git fetch-pack: fetch failed.");
@@ -649,6 +654,11 @@ static int fetch_pack_config(const char *var, const char *value, void *cb)
                return 0;
        }
 
+       if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+               prefer_ofs_delta = git_config_bool(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
@@ -801,15 +811,13 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
                int fd;
 
                mtime.sec = st.st_mtime;
-#ifdef USE_NSEC
-               mtime.usec = st.st_mtim.usec;
-#endif
+               mtime.nsec = ST_MTIME_NSEC(st);
                if (stat(shallow, &st)) {
                        if (mtime.sec)
                                die("shallow file was removed during fetch");
                } else if (st.st_mtime != mtime.sec
 #ifdef USE_NSEC
-                               || st.st_mtim.usec != mtime.usec
+                               || ST_MTIME_NSEC(st) != mtime.nsec
 #endif
                          )
                        die("shallow file was changed during fetch");
@@ -817,7 +825,7 @@ struct ref *fetch_pack(struct fetch_pack_args *my_args,
                fd = hold_lock_file_for_update(&lock, shallow,
                                               LOCK_DIE_ON_ERROR);
                if (!write_shallow_commits(fd, 0)) {
-                       unlink(shallow);
+                       unlink_or_warn(shallow);
                        rollback_lock_file(&lock);
                } else {
                        commit_lock_file(&lock);
index 7568163af24df630c215e05b6082ed764150a315..1f7a3f1ce66434a84ff96ef352245862fe088a70 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);
 }
 
@@ -166,6 +167,9 @@ static struct ref *get_ref_map(struct transport *transport,
        return ref_map;
 }
 
+#define STORE_REF_ERROR_OTHER 1
+#define STORE_REF_ERROR_DF_CONFLICT 2
+
 static int s_update_ref(const char *action,
                        struct ref *ref,
                        int check_old)
@@ -180,9 +184,11 @@ static int s_update_ref(const char *action,
        lock = lock_any_ref_for_update(ref->name,
                                       check_old ? ref->old_sha1 : NULL, 0);
        if (!lock)
-               return 2;
+               return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+                                         STORE_REF_ERROR_OTHER;
        if (write_ref_sha1(lock, ref->new_sha1, msg) < 0)
-               return 2;
+               return errno == ENOTDIR ? STORE_REF_ERROR_DF_CONFLICT :
+                                         STORE_REF_ERROR_OTHER;
        return 0;
 }
 
@@ -196,11 +202,7 @@ static int update_local_ref(struct ref *ref,
        struct commit *current = NULL, *updated;
        enum object_type type;
        struct branch *current_branch = branch_get(NULL);
-       const char *pretty_ref = ref->name + (
-               !prefixcmp(ref->name, "refs/heads/") ? 11 :
-               !prefixcmp(ref->name, "refs/tags/") ? 10 :
-               !prefixcmp(ref->name, "refs/remotes/") ? 13 :
-               0);
+       const char *pretty_ref = prettify_ref(ref);
 
        *display = 0;
        type = sha1_object_info(ref->new_sha1, NULL);
@@ -380,7 +382,7 @@ static int store_updated_refs(const char *url, const char *remote_name,
                }
        }
        fclose(fp);
-       if (rc & 2)
+       if (rc & STORE_REF_ERROR_DF_CONFLICT)
                error("some local refs could not be updated; try running\n"
                      " 'git remote prune %s' to remove any old, conflicting "
                      "branches", remote_name);
@@ -543,7 +545,8 @@ static void check_not_current_branch(struct ref *ref_map)
        for (; ref_map; ref_map = ref_map->next)
                if (ref_map->peer_ref && !strcmp(current_branch->refname,
                                        ref_map->peer_ref->name))
-                       die("Refusing to fetch into current branch");
+                       die("Refusing to fetch into current branch %s "
+                           "of non-bare repository", current_branch->refname);
 }
 
 static int do_fetch(struct transport *transport,
@@ -607,7 +610,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",
@@ -635,6 +638,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        else
                remote = remote_get(argv[0]);
 
+       if (!remote)
+               die("Where do you want to fetch from today?");
+
        transport = transport_get(remote, remote->url[0]);
        if (verbosity >= 2)
                transport->verbose = 1;
@@ -647,9 +653,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
        if (depth)
                set_option(TRANS_OPT_DEPTH, depth);
 
-       if (!transport->url)
-               die("Where do you want to fetch from today?");
-
        if (argc > 1) {
                int j = 0;
                refs = xcalloc(argc + 1, sizeof(const char *));
@@ -672,7 +675,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
                ref_nr = j;
        }
 
-       signal(SIGINT, unlock_pack_on_signal);
+       sigchain_push_common(unlock_pack_on_signal);
        atexit(unlock_pack);
        exit_code = do_fetch(transport,
                        parse_fetch_refspec(ref_nr, refs), ref_nr);
index df18f4070f3877ed907476f2179590001ec972b7..a7883690d74cb1bcef7b20f27ebbea6f6c5dcc53 100644 (file)
@@ -256,8 +256,7 @@ static void shortlog(const char *name, unsigned char *sha1,
 
 int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
        int limit = 20, i = 0, pos = 0;
-       char line[1024];
-       char *p = line, *sep = "";
+       char *sep = "";
        unsigned char head_sha1[20];
        const char *current_branch;
 
@@ -271,9 +270,8 @@ int fmt_merge_msg(int merge_summary, struct strbuf *in, struct strbuf *out) {
        /* get a line */
        while (pos < in->len) {
                int len;
-               char *newline;
+               char *newline, *p = in->buf + pos;
 
-               p = in->buf + pos;
                newline = strchr(p, '\n');
                len = newline ? newline - p : strlen(p);
                pos += len + !!newline;
index e46b7adc9719e147536398e8e365d6d3e65a4ba7..d091e04af9549b70a1e15a4b845383056e71932e 100644 (file)
@@ -8,6 +8,7 @@
 #include "blob.h"
 #include "quote.h"
 #include "parse-options.h"
+#include "remote.h"
 
 /* Quoting styles */
 #define QUOTE_NONE 0
@@ -66,6 +67,7 @@ static struct {
        { "subject" },
        { "body" },
        { "contents" },
+       { "upstream" },
 };
 
 /*
@@ -337,8 +339,11 @@ static const char *copy_name(const char *buf)
 static const char *copy_email(const char *buf)
 {
        const char *email = strchr(buf, '<');
-       const char *eoemail = strchr(email, '>');
-       if (!email || !eoemail)
+       const char *eoemail;
+       if (!email)
+               return "";
+       eoemail = strchr(email, '>');
+       if (!eoemail)
                return "";
        return xmemdupz(email, eoemail + 1 - email);
 }
@@ -543,109 +548,6 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v
        }
 }
 
-/*
- * generate a format suitable for scanf from a ref_rev_parse_rules
- * rule, that is replace the "%.*s" spec with a "%s" spec
- */
-static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
-{
-       char *spec;
-
-       spec = strstr(rule, "%.*s");
-       if (!spec || strstr(spec + 4, "%.*s"))
-               die("invalid rule in ref_rev_parse_rules: %s", rule);
-
-       /* copy all until spec */
-       strncpy(scanf_fmt, rule, spec - rule);
-       scanf_fmt[spec - rule] = '\0';
-       /* copy new spec */
-       strcat(scanf_fmt, "%s");
-       /* copy remaining rule */
-       strcat(scanf_fmt, spec + 4);
-
-       return;
-}
-
-/*
- * Shorten the refname to an non-ambiguous form
- */
-static char *get_short_ref(struct refinfo *ref)
-{
-       int i;
-       static char **scanf_fmts;
-       static int nr_rules;
-       char *short_name;
-
-       /* pre generate scanf formats from ref_rev_parse_rules[] */
-       if (!nr_rules) {
-               size_t total_len = 0;
-
-               /* the rule list is NULL terminated, count them first */
-               for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
-                       /* no +1 because strlen("%s") < strlen("%.*s") */
-                       total_len += strlen(ref_rev_parse_rules[nr_rules]);
-
-               scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
-
-               total_len = 0;
-               for (i = 0; i < nr_rules; i++) {
-                       scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
-                                       + total_len;
-                       gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
-                       total_len += strlen(ref_rev_parse_rules[i]);
-               }
-       }
-
-       /* bail out if there are no rules */
-       if (!nr_rules)
-               return ref->refname;
-
-       /* buffer for scanf result, at most ref->refname must fit */
-       short_name = xstrdup(ref->refname);
-
-       /* skip first rule, it will always match */
-       for (i = nr_rules - 1; i > 0 ; --i) {
-               int j;
-               int short_name_len;
-
-               if (1 != sscanf(ref->refname, scanf_fmts[i], short_name))
-                       continue;
-
-               short_name_len = strlen(short_name);
-
-               /*
-                * check if the short name resolves to a valid ref,
-                * but use only rules prior to the matched one
-                */
-               for (j = 0; j < i; j++) {
-                       const char *rule = ref_rev_parse_rules[j];
-                       unsigned char short_objectname[20];
-                       char refname[PATH_MAX];
-
-                       /*
-                        * the short name is ambiguous, if it resolves
-                        * (with this previous rule) to a valid ref
-                        * read_ref() returns 0 on success
-                        */
-                       mksnpath(refname, sizeof(refname),
-                                rule, short_name_len, short_name);
-                       if (!read_ref(refname, short_objectname))
-                               break;
-               }
-
-               /*
-                * short name is non-ambiguous if all previous rules
-                * haven't resolved to a valid ref
-                */
-               if (j == i)
-                       return short_name;
-       }
-
-       free(short_name);
-       return ref->refname;
-}
-
-
 /*
  * Parse the object referred by ref, and grab needed value.
  */
@@ -672,32 +574,50 @@ static void populate_value(struct refinfo *ref)
                const char *name = used_atom[i];
                struct atom_value *v = &ref->value[i];
                int deref = 0;
+               const char *refname;
+               const char *formatp;
+
                if (*name == '*') {
                        deref = 1;
                        name++;
                }
-               if (!prefixcmp(name, "refname")) {
-                       const char *formatp = strchr(name, ':');
-                       const char *refname = ref->refname;
-
-                       /* look for "short" refname format */
-                       if (formatp) {
-                               formatp++;
-                               if (!strcmp(formatp, "short"))
-                                       refname = get_short_ref(ref);
-                               else
-                                       die("unknown refname format %s",
-                                           formatp);
-                       }
 
-                       if (!deref)
-                               v->s = refname;
-                       else {
-                               int len = strlen(refname);
-                               char *s = xmalloc(len + 4);
-                               sprintf(s, "%s^{}", refname);
-                               v->s = s;
-                       }
+               if (!prefixcmp(name, "refname"))
+                       refname = ref->refname;
+               else if(!prefixcmp(name, "upstream")) {
+                       struct branch *branch;
+                       /* only local branches may have an upstream */
+                       if (prefixcmp(ref->refname, "refs/heads/"))
+                               continue;
+                       branch = branch_get(ref->refname + 11);
+
+                       if (!branch || !branch->merge || !branch->merge[0] ||
+                           !branch->merge[0]->dst)
+                               continue;
+                       refname = branch->merge[0]->dst;
+               }
+               else
+                       continue;
+
+               formatp = strchr(name, ':');
+               /* look for "short" refname format */
+               if (formatp) {
+                       formatp++;
+                       if (!strcmp(formatp, "short"))
+                               refname = shorten_unambiguous_ref(refname,
+                                                     warn_ambiguous_refs);
+                       else
+                               die("unknown %.*s format %s",
+                                   (int)(formatp - name), name, formatp);
+               }
+
+               if (!deref)
+                       v->s = refname;
+               else {
+                       int len = strlen(refname);
+                       char *s = xmalloc(len + 4);
+                       sprintf(s, "%s^{}", refname);
+                       v->s = s;
                }
        }
 
@@ -943,7 +863,6 @@ static int opt_parse_sort(const struct option *opt, const char *arg, int unset)
                return -1;
 
        *sort_tail = s = xcalloc(1, sizeof(*s));
-       sort_tail = &s->next;
 
        if (*arg == '-') {
                s->reverse = 1;
@@ -1002,6 +921,9 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
                sort = default_sort();
        sort_atom_limit = used_atom_cnt;
 
+       /* for warn_ambiguous_refs */
+       git_config(git_default_config, NULL);
+
        memset(&cbdata, 0, sizeof(cbdata));
        cbdata.grab_pattern = argv;
        for_each_ref(grab_single_ref, &cbdata);
index a60d199526a7c88572d3ccb4b5f4f040a282cd49..6436bc224840f11af2f7fa26c61b62c25d78d865 100644 (file)
@@ -10,6 +10,7 @@
 #include "tree-walk.h"
 #include "fsck.h"
 #include "parse-options.h"
+#include "dir.h"
 
 #define REACHABLE 0x0001
 #define SEEN      0x0002
@@ -22,6 +23,7 @@ static int check_full;
 static int check_strict;
 static int keep_cache_objects;
 static unsigned char head_sha1[20];
+static const char *head_points_at;
 static int errors_found;
 static int write_lost_and_found;
 static int verbose;
@@ -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..fc556ed7f3fb68734fd783e5b38e6b050bed9c5b 100644 (file)
@@ -23,7 +23,7 @@ static const char * const builtin_gc_usage[] = {
 };
 
 static int pack_refs = 1;
-static int aggressive_window = -1;
+static int aggressive_window = 250;
 static int gc_auto_threshold = 6700;
 static int gc_auto_pack_limit = 50;
 static const char *prune_expire = "2.weeks.ago";
@@ -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"),
@@ -226,6 +200,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
 
        if (aggressive) {
                append_option(argv_repack, "-f", MAX_ADD);
+               append_option(argv_repack, "--depth=250", MAX_ADD);
                if (aggressive_window > 0) {
                        sprintf(buf, "--window=%d", aggressive_window);
                        append_option(argv_repack, buf, MAX_ADD);
@@ -246,7 +221,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 +234,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 3f12ba382690699d96580c3ddb1a61c79520e694..f88a912ace9195c387566770432a904e2d7adcb7 100644 (file)
 
 static int builtin_grep;
 
+static int grep_config(const char *var, const char *value, void *cb)
+{
+       struct grep_opt *opt = cb;
+
+       if (!strcmp(var, "color.grep")) {
+               opt->color = git_config_colorbool(var, value, -1);
+               return 0;
+       }
+       if (!strcmp(var, "color.grep.external"))
+               return git_config_string(&(opt->color_external), var, value);
+       if (!strcmp(var, "color.grep.match")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               color_parse(value, var, opt->color_match);
+               return 0;
+       }
+       return git_color_default_config(var, value, cb);
+}
+
 /*
  * git grep pathspecs are somewhat different from diff-tree pathspecs;
  * pathname wildcards are allowed.
@@ -269,6 +288,21 @@ static int flush_grep(struct grep_opt *opt,
        return status;
 }
 
+static void grep_add_color(struct strbuf *sb, const char *escape_seq)
+{
+       size_t orig_len = sb->len;
+
+       while (*escape_seq) {
+               if (*escape_seq == 'm')
+                       strbuf_addch(sb, ';');
+               else if (*escape_seq != '\033' && *escape_seq  != '[')
+                       strbuf_addch(sb, *escape_seq);
+               escape_seq++;
+       }
+       if (sb->len > orig_len && sb->buf[sb->len - 1] == ';')
+               strbuf_setlen(sb, sb->len - 1);
+}
+
 static int external_grep(struct grep_opt *opt, const char **paths, int cached)
 {
        int i, nr, argc, hit, len, status;
@@ -339,6 +373,23 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                push_arg("-e");
                push_arg(p->pattern);
        }
+       if (opt->color) {
+               struct strbuf sb = STRBUF_INIT;
+
+               grep_add_color(&sb, opt->color_match);
+               setenv("GREP_COLOR", sb.buf, 1);
+
+               strbuf_reset(&sb);
+               strbuf_addstr(&sb, "mt=");
+               grep_add_color(&sb, opt->color_match);
+               strbuf_addstr(&sb, ":sl=:cx=:fn=:ln=:bn=:se=");
+               setenv("GREP_COLORS", sb.buf, 1);
+
+               strbuf_release(&sb);
+
+               if (opt->color_external && strlen(opt->color_external) > 0)
+                       push_arg(opt->color_external);
+       }
 
        hit = 0;
        argc = nr;
@@ -536,6 +587,12 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
        opt.pattern_tail = &opt.pattern_list;
        opt.regflags = REG_NEWLINE;
 
+       strcpy(opt.color_match, GIT_COLOR_RED GIT_COLOR_BOLD);
+       opt.color = -1;
+       git_config(grep_config, &opt);
+       if (opt.color == -1)
+               opt.color = git_use_color_default;
+
        /*
         * If there is no -- then the paths must exist in the working
         * tree.  If there is no explicit pattern specified with -e or
@@ -732,6 +789,14 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                        opt.relative = 0;
                        continue;
                }
+               if (!strcmp("--color", arg)) {
+                       opt.color = 1;
+                       continue;
+               }
+               if (!strcmp("--no-color", arg)) {
+                       opt.color = 0;
+                       continue;
+               }
                if (!strcmp("--", arg)) {
                        /* later processing wants to have this at argv[1] */
                        argv--;
@@ -757,6 +822,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
                }
        }
 
+       if (opt.color && !opt.color_external)
+               builtin_grep = 1;
        if (!opt.pattern_list)
                die("no pattern given.");
        if ((opt.regflags != REG_NEWLINE) && opt.fixed)
index f076efa9211ddcffdc81ffd7c0c56fdb197a11b0..67dda3e6e63c9130a6f1e1c94cb35e519d765a95 100644 (file)
@@ -114,7 +114,7 @@ static int check_emacsclient_version(void)
        return 0;
 }
 
-static void exec_woman_emacs(const charpath, const char *page)
+static void exec_woman_emacs(const char *path, const char *page)
 {
        if (!check_emacsclient_version()) {
                /* This works only with emacsclient version >= 22. */
@@ -128,7 +128,7 @@ static void exec_woman_emacs(const char* path, const char *page)
        }
 }
 
-static void exec_man_konqueror(const charpath, const char *page)
+static void exec_man_konqueror(const char *path, const char *page)
 {
        const char *display = getenv("DISPLAY");
        if (display && *display) {
@@ -156,7 +156,7 @@ static void exec_man_konqueror(const char* path, const char *page)
        }
 }
 
-static void exec_man_man(const charpath, const char *page)
+static void exec_man_man(const char *path, const char *page)
 {
        if (!path)
                path = "man";
@@ -236,7 +236,7 @@ static int add_man_viewer_info(const char *var, const char *value)
        const char *subkey = strrchr(name, '.');
 
        if (!subkey)
-               return error("Config with no key for man viewer: %s", name);
+               return 0;
 
        if (!strcmp(subkey, ".path")) {
                if (!value)
@@ -249,7 +249,6 @@ static int add_man_viewer_info(const char *var, const char *value)
                return add_man_viewer_cmd(name, subkey - name, value);
        }
 
-       warning("'%s': unsupported man viewer sub key.", subkey);
        return 0;
 }
 
@@ -329,7 +328,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 +374,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 6cc945d5075b03727f93194cd67122e6bff49764..d1fa12a59efb34256b2cc80b03c637cc844d84ff 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,
@@ -132,8 +132,7 @@ static void copy_templates(const char *template_dir)
        }
        dir = opendir(template_path);
        if (!dir) {
-               fprintf(stderr, "warning: templates not found %s\n",
-                       template_dir);
+               warning("templates not found %s", template_dir);
                return;
        }
 
@@ -146,8 +145,8 @@ static void copy_templates(const char *template_dir)
 
        if (repository_format_version &&
            repository_format_version != GIT_REPO_VERSION) {
-               fprintf(stderr, "warning: not copying templates of "
-                       "a wrong format version %d from '%s'\n",
+               warning("not copying templates of "
+                       "a wrong format version %d from '%s'",
                        repository_format_version,
                        template_dir);
                closedir(dir);
@@ -197,6 +196,8 @@ static int create_default_files(const char *template_path)
 
        git_config(git_default_config, NULL);
        is_bare_repository_cfg = init_is_bare_repository;
+
+       /* reading existing config may have overwrote it */
        if (init_shared_repository != -1)
                shared_repository = init_shared_repository;
 
@@ -315,12 +316,15 @@ int init_db(const char *template_dir, unsigned int flags)
                 * and compatibility values for PERM_GROUP and
                 * PERM_EVERYBODY.
                 */
-               if (shared_repository == PERM_GROUP)
+               if (shared_repository < 0)
+                       /* force to the mode value */
+                       sprintf(buf, "0%o", -shared_repository);
+               else if (shared_repository == PERM_GROUP)
                        sprintf(buf, "%d", OLD_PERM_GROUP);
                else if (shared_repository == PERM_EVERYBODY)
                        sprintf(buf, "%d", OLD_PERM_EVERYBODY);
                else
-                       sprintf(buf, "0%o", shared_repository);
+                       die("oops");
                git_config_set("core.sharedrepository", buf);
                git_config_set("receive.denyNonFastforwards", "true");
        }
@@ -400,6 +404,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                        usage(init_db_usage);
        }
 
+       if (init_shared_repository != -1)
+               shared_repository = init_shared_repository;
+
        /*
         * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
         * without --bare.  Catch the error early.
index 60f8dd86048fd3a4ec51d7296b85984a0cefd602..f10cfebdbbabac67dea41639289e7ad968d5bbe3 100644 (file)
@@ -16,6 +16,8 @@
 #include "patch-ids.h"
 #include "run-command.h"
 #include "shortlog.h"
+#include "remote.h"
+#include "string-list.h"
 
 /* Set a default date-time format for git log ("log.date" config variable) */
 static const char *default_date_mode = NULL;
@@ -249,22 +251,13 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 
 static void show_tagger(char *buf, int len, struct rev_info *rev)
 {
-       char *email_end, *p;
-       unsigned long date;
-       int tz;
+       struct strbuf out = STRBUF_INIT;
 
-       email_end = memchr(buf, '>', len);
-       if (!email_end)
-               return;
-       p = ++email_end;
-       while (isspace(*p))
-               p++;
-       date = strtoul(p, &p, 10);
-       while (isspace(*p))
-               p++;
-       tz = (int)strtol(p, NULL, 10);
-       printf("Tagger: %.*s\nDate:   %s\n", (int)(email_end - buf), buf,
-              show_date(date, tz, rev->date_mode));
+       pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
+               git_log_output_encoding ?
+               git_log_output_encoding: git_commit_encoding);
+       printf("%s\n", out.buf);
+       strbuf_release(&out);
 }
 
 static int show_object(const unsigned char *sha1, int show_tag_object,
@@ -424,18 +417,13 @@ int cmd_log(int argc, const char **argv, const char *prefix)
 }
 
 /* format-patch */
-#define FORMAT_PATCH_NAME_MAX 64
-
-static int istitlechar(char c)
-{
-       return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
-               (c >= '0' && c <= '9') || c == '.' || c == '_';
-}
 
 static const char *fmt_patch_suffix = ".patch";
 static int numbered = 0;
 static int auto_number = 1;
 
+static char *default_attach = NULL;
+
 static char **extra_hdr;
 static int extra_hdr_nr;
 static int extra_hdr_alloc;
@@ -467,6 +455,11 @@ static void add_header(const char *value)
        extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
 }
 
+#define THREAD_SHALLOW 1
+#define THREAD_DEEP 2
+static int thread = 0;
+static int do_signoff = 0;
+
 static int git_format_config(const char *var, const char *value, void *cb)
 {
        if (!strcmp(var, "format.headers")) {
@@ -496,94 +489,60 @@ static int git_format_config(const char *var, const char *value, void *cb)
                auto_number = auto_number && numbered;
                return 0;
        }
-
-       return git_log_config(var, value, cb);
-}
-
-
-static const char *get_oneline_for_filename(struct commit *commit,
-                                           int keep_subject)
-{
-       static char filename[PATH_MAX];
-       char *sol;
-       int len = 0;
-       int suffix_len = strlen(fmt_patch_suffix) + 1;
-
-       sol = strstr(commit->buffer, "\n\n");
-       if (!sol)
-               filename[0] = '\0';
-       else {
-               int j, space = 0;
-
-               sol += 2;
-               /* strip [PATCH] or [PATCH blabla] */
-               if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
-                       char *eos = strchr(sol + 6, ']');
-                       if (eos) {
-                               while (isspace(*eos))
-                                       eos++;
-                               sol = eos;
-                       }
+       if (!strcmp(var, "format.attach")) {
+               if (value && *value)
+                       default_attach = xstrdup(value);
+               else
+                       default_attach = xstrdup(git_version_string);
+               return 0;
+       }
+       if (!strcmp(var, "format.thread")) {
+               if (value && !strcasecmp(value, "deep")) {
+                       thread = THREAD_DEEP;
+                       return 0;
                }
-
-               for (j = 0;
-                    j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
-                            len < sizeof(filename) - suffix_len &&
-                            sol[j] && sol[j] != '\n';
-                    j++) {
-                       if (istitlechar(sol[j])) {
-                               if (space) {
-                                       filename[len++] = '-';
-                                       space = 0;
-                               }
-                               filename[len++] = sol[j];
-                               if (sol[j] == '.')
-                                       while (sol[j + 1] == '.')
-                                               j++;
-                       } else
-                               space = 1;
+               if (value && !strcasecmp(value, "shallow")) {
+                       thread = THREAD_SHALLOW;
+                       return 0;
                }
-               while (filename[len - 1] == '.'
-                      || filename[len - 1] == '-')
-                       len--;
-               filename[len] = '\0';
+               thread = git_config_bool(var, value) && THREAD_SHALLOW;
+               return 0;
+       }
+       if (!strcmp(var, "format.signoff")) {
+               do_signoff = git_config_bool(var, value);
+               return 0;
        }
-       return filename;
+
+       return git_log_config(var, value, cb);
 }
 
 static FILE *realstdout = NULL;
 static const char *output_directory = NULL;
 static int outdir_offset;
 
-static int reopen_stdout(const char *oneline, int nr, int total)
+static int reopen_stdout(struct commit *commit, struct rev_info *rev)
 {
-       char filename[PATH_MAX];
-       int len = 0;
+       struct strbuf filename = STRBUF_INIT;
        int suffix_len = strlen(fmt_patch_suffix) + 1;
 
        if (output_directory) {
-               len = snprintf(filename, sizeof(filename), "%s",
-                               output_directory);
-               if (len >=
-                   sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len)
+               strbuf_addstr(&filename, output_directory);
+               if (filename.len >=
+                   PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len)
                        return error("name of output directory is too long");
-               if (filename[len - 1] != '/')
-                       filename[len++] = '/';
+               if (filename.buf[filename.len - 1] != '/')
+                       strbuf_addch(&filename, '/');
        }
 
-       if (!oneline)
-               len += sprintf(filename + len, "%d", nr);
-       else {
-               len += sprintf(filename + len, "%04d-", nr);
-               len += snprintf(filename + len, sizeof(filename) - len - 1
-                               - suffix_len, "%s", oneline);
-               strcpy(filename + len, fmt_patch_suffix);
-       }
+       get_patch_filename(commit, rev->nr, fmt_patch_suffix, &filename);
 
-       fprintf(realstdout, "%s\n", filename + outdir_offset);
-       if (freopen(filename, "w", stdout) == NULL)
-               return error("Cannot open patch file %s",filename);
+       if (!DIFF_OPT_TST(&rev->diffopt, QUIET))
+               fprintf(realstdout, "%s\n", filename.buf + outdir_offset);
 
+       if (freopen(filename.buf, "w", stdout) == NULL)
+               return error("Cannot open patch file %s", filename.buf);
+
+       strbuf_release(&filename);
        return 0;
 }
 
@@ -653,7 +612,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
                              int nr, struct commit **list, struct commit *head)
 {
        const char *committer;
-       char *head_sha1;
        const char *subject_start = NULL;
        const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
        const char *msg;
@@ -664,20 +622,40 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout,
        const char *encoding = "utf-8";
        struct diff_options opts;
        int need_8bit_cte = 0;
+       struct commit *commit = NULL;
 
        if (rev->commit_format != CMIT_FMT_EMAIL)
                die("Cover letter needs email format");
 
-       if (!use_stdout && reopen_stdout(numbered_files ?
-                               NULL : "cover-letter", 0, rev->total))
+       committer = git_committer_info(0);
+
+       if (!numbered_files) {
+               /*
+                * We fake a commit for the cover letter so we get the filename
+                * desired.
+                */
+               commit = xcalloc(1, sizeof(*commit));
+               commit->buffer = xmalloc(400);
+               snprintf(commit->buffer, 400,
+                       "tree 0000000000000000000000000000000000000000\n"
+                       "parent %s\n"
+                       "author %s\n"
+                       "committer %s\n\n"
+                       "cover letter\n",
+                       sha1_to_hex(head->object.sha1), committer, committer);
+       }
+
+       if (!use_stdout && reopen_stdout(commit, rev))
                return;
 
-       head_sha1 = sha1_to_hex(head->object.sha1);
+       if (commit) {
 
-       log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers,
-                               &need_8bit_cte);
+               free(commit->buffer);
+               free(commit);
+       }
 
-       committer = git_committer_info(0);
+       log_write_email_headers(rev, head, &subject_start, &extra_headers,
+                               &need_8bit_cte);
 
        msg = body;
        pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
@@ -774,10 +752,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int numbered_files = 0;         /* _just_ numbers */
        int subject_prefix = 0;
        int ignore_if_in_upstream = 0;
-       int thread = 0;
        int cover_letter = 0;
        int boundary_count = 0;
        int no_binary_diff = 0;
+       int numbered_cmdline_opt = 0;
        struct commit *origin = NULL, *head = NULL;
        const char *in_reply_to = NULL;
        struct patch_ids ids;
@@ -795,6 +773,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
        rev.subject_prefix = fmt_patch_subject_prefix;
 
+       if (default_attach) {
+               rev.mime_boundary = default_attach;
+               rev.no_inline = 1;
+       }
+
        /*
         * Parse the arguments before setup_revisions(), or something
         * like "git format-patch -o a123 HEAD^.." may fail; a123 is
@@ -804,8 +787,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                if (!strcmp(argv[i], "--stdout"))
                        use_stdout = 1;
                else if (!strcmp(argv[i], "-n") ||
-                               !strcmp(argv[i], "--numbered"))
+                               !strcmp(argv[i], "--numbered")) {
                        numbered = 1;
+                       numbered_cmdline_opt = 1;
+               }
                else if (!strcmp(argv[i], "-N") ||
                                !strcmp(argv[i], "--no-numbered")) {
                        numbered = 0;
@@ -841,13 +826,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                }
                else if (!strcmp(argv[i], "--signoff") ||
                         !strcmp(argv[i], "-s")) {
-                       const char *committer;
-                       const char *endpos;
-                       committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
-                       endpos = strchr(committer, '>');
-                       if (!endpos)
-                               die("bogus committer info %s\n", committer);
-                       add_signoff = xmemdupz(committer, endpos - committer + 1);
+                       do_signoff = 1;
                }
                else if (!strcmp(argv[i], "--attach")) {
                        rev.mime_boundary = git_version_string;
@@ -857,6 +836,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        rev.mime_boundary = argv[i] + 9;
                        rev.no_inline = 1;
                }
+               else if (!strcmp(argv[i], "--no-attach")) {
+                       rev.mime_boundary = NULL;
+                       rev.no_inline = 0;
+               }
                else if (!strcmp(argv[i], "--inline")) {
                        rev.mime_boundary = git_version_string;
                        rev.no_inline = 0;
@@ -867,8 +850,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                }
                else if (!strcmp(argv[i], "--ignore-if-in-upstream"))
                        ignore_if_in_upstream = 1;
-               else if (!strcmp(argv[i], "--thread"))
-                       thread = 1;
+               else if (!strcmp(argv[i], "--thread")
+                       || !strcmp(argv[i], "--thread=shallow"))
+                       thread = THREAD_SHALLOW;
+               else if (!strcmp(argv[i], "--thread=deep"))
+                       thread = THREAD_DEEP;
+               else if (!strcmp(argv[i], "--no-thread"))
+                       thread = 0;
                else if (!prefixcmp(argv[i], "--in-reply-to="))
                        in_reply_to = argv[i] + 14;
                else if (!strcmp(argv[i], "--in-reply-to")) {
@@ -885,11 +873,23 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        cover_letter = 1;
                else if (!strcmp(argv[i], "--no-binary"))
                        no_binary_diff = 1;
+               else if (!prefixcmp(argv[i], "--add-header="))
+                       add_header(argv[i] + 13);
                else
                        argv[j++] = argv[i];
        }
        argc = j;
 
+       if (do_signoff) {
+               const char *committer;
+               const char *endpos;
+               committer = git_committer_info(IDENT_ERROR_ON_NO_NAME);
+               endpos = strchr(committer, '>');
+               if (!endpos)
+                       die("bogus committer info %s", committer);
+               add_signoff = xmemdupz(committer, endpos - committer + 1);
+       }
+
        for (i = 0; i < extra_hdr_nr; i++) {
                strbuf_addstr(&buf, extra_hdr[i]);
                strbuf_addch(&buf, '\n');
@@ -921,12 +921,19 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 
        if (start_number < 0)
                start_number = 1;
+
+       /*
+        * If numbered is set solely due to format.numbered in config,
+        * and it would conflict with --keep-subject (-k) from the
+        * command line, reset "numbered".
+        */
+       if (numbered && keep_subject && !numbered_cmdline_opt)
+               numbered = 0;
+
        if (numbered && keep_subject)
                die ("-n and -k are mutually exclusive.");
        if (keep_subject && subject_prefix)
                die ("--subject-prefix and -k are mutually exclusive.");
-       if (numbered_files && use_stdout)
-               die ("--numbered-files and --stdout are mutually exclusive.");
 
        argc = setup_revisions(argc, argv, &rev, "HEAD");
        if (argc > 1)
@@ -1019,8 +1026,14 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                numbered = 1;
        if (numbered)
                rev.total = total + start_number - 1;
-       if (in_reply_to)
-               rev.ref_message_id = clean_message_id(in_reply_to);
+       if (in_reply_to || thread || cover_letter)
+               rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
+       if (in_reply_to) {
+               const char *msgid = clean_message_id(in_reply_to);
+               string_list_append(msgid, rev.ref_message_ids);
+       }
+       rev.numbered_files = numbered_files;
+       rev.patch_suffix = fmt_patch_suffix;
        if (cover_letter) {
                if (thread)
                        gen_message_id(&rev, "cover");
@@ -1039,21 +1052,39 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        /* Have we already had a message ID? */
                        if (rev.message_id) {
                                /*
-                                * If we've got the ID to be a reply
-                                * to, discard the current ID;
-                                * otherwise, make everything a reply
-                                * to that.
+                                * For deep threading: make every mail
+                                * a reply to the previous one, no
+                                * matter what other options are set.
+                                *
+                                * For shallow threading:
+                                *
+                                * Without --cover-letter and
+                                * --in-reply-to, make every mail a
+                                * reply to the one before.
+                                *
+                                * With --in-reply-to but no
+                                * --cover-letter, make every mail a
+                                * reply to the <reply-to>.
+                                *
+                                * With --cover-letter, make every
+                                * mail but the cover letter a reply
+                                * to the cover letter.  The cover
+                                * letter is a reply to the
+                                * --in-reply-to, if specified.
                                 */
-                               if (rev.ref_message_id)
+                               if (thread == THREAD_SHALLOW
+                                   && rev.ref_message_ids->nr > 0
+                                   && (!cover_letter || rev.nr > 1))
                                        free(rev.message_id);
                                else
-                                       rev.ref_message_id = rev.message_id;
+                                       string_list_append(rev.message_id,
+                                                          rev.ref_message_ids);
                        }
                        gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
                }
-               if (!use_stdout && reopen_stdout(numbered_files ? NULL :
-                               get_oneline_for_filename(commit, keep_subject),
-                               rev.nr, rev.total))
+
+               if (!use_stdout && reopen_stdout(numbered_files ? NULL : commit,
+                                                &rev))
                        die("Failed to create output files");
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
@@ -1099,13 +1130,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 +1160,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..da2daf45acd2154b7f00da34d41a5ab8acaf0c38 100644 (file)
@@ -10,6 +10,7 @@
 #include "dir.h"
 #include "builtin.h"
 #include "tree.h"
+#include "parse-options.h"
 
 static int abbrev;
 static int show_deleted;
@@ -28,6 +29,7 @@ static const char **pathspec;
 static int error_unmatch;
 static char *ps_matched;
 static const char *with_tree;
+static int exc_given;
 
 static const char *tag_cached = "";
 static const char *tag_unmerged = "";
@@ -36,42 +38,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 +46,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 +122,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 &&
@@ -210,7 +176,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                for (i = 0; i < active_nr; i++) {
                        struct cache_entry *ce = active_cache[i];
                        int dtype = ce_to_dtype(ce);
-                       if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
+                       if (excluded(dir, ce->name, &dtype) !=
+                                       !!(dir->flags & DIR_SHOW_IGNORED))
                                continue;
                        if (show_unmerged && !ce_stage(ce))
                                continue;
@@ -225,7 +192,8 @@ static void show_files(struct dir_struct *dir, const char *prefix)
                        struct stat st;
                        int err;
                        int dtype = ce_to_dtype(ce);
-                       if (excluded(dir, ce->name, &dtype) != dir->show_ignored)
+                       if (excluded(dir, ce->name, &dtype) !=
+                                       !!(dir->flags & DIR_SHOW_IGNORED))
                                continue;
                        if (ce->ce_flags & CE_UPDATE)
                                continue;
@@ -298,6 +266,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
@@ -395,156 +378,144 @@ int report_path_error(const char *ps_matched, const char **pathspec, int prefix_
        return errors;
 }
 
-static const char ls_files_usage[] =
-       "git ls-files [-z] [-t] [-v] (--[cached|deleted|others|stage|unmerged|killed|modified])* "
-       "[ --ignored ] [--exclude=<pattern>] [--exclude-from=<file>] "
-       "[ --exclude-per-directory=<filename> ] [--exclude-standard] "
-       "[--full-name] [--abbrev] [--] [<file>]*";
+static const char * const ls_files_usage[] = {
+       "git ls-files [options] [<file>]*",
+       NULL
+};
+
+static int option_parse_z(const struct option *opt,
+                         const char *arg, int unset)
+{
+       line_terminator = unset ? '\n' : '\0';
+
+       return 0;
+}
+
+static int option_parse_exclude(const struct option *opt,
+                               const char *arg, int unset)
+{
+       struct exclude_list *list = opt->value;
+
+       exc_given = 1;
+       add_exclude(arg, "", 0, list);
+
+       return 0;
+}
+
+static int option_parse_exclude_from(const struct option *opt,
+                                    const char *arg, int unset)
+{
+       struct dir_struct *dir = opt->value;
+
+       exc_given = 1;
+       add_excludes_from_file(dir, arg);
+
+       return 0;
+}
+
+static int option_parse_exclude_standard(const struct option *opt,
+                                        const char *arg, int unset)
+{
+       struct dir_struct *dir = opt->value;
+
+       exc_given = 1;
+       setup_standard_excludes(dir);
+
+       return 0;
+}
 
 int cmd_ls_files(int argc, const char **argv, const char *prefix)
 {
-       int i;
-       int exc_given = 0, require_work_tree = 0;
+       int require_work_tree = 0, show_tag = 0;
        struct dir_struct dir;
+       struct option builtin_ls_files_options[] = {
+               { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
+                       "paths are separated with NUL character",
+                       PARSE_OPT_NOARG, option_parse_z },
+               OPT_BOOLEAN('t', NULL, &show_tag,
+                       "identify the file status with tags"),
+               OPT_BOOLEAN('v', NULL, &show_valid_bit,
+                       "use lowercase letters for 'assume unchanged' files"),
+               OPT_BOOLEAN('c', "cached", &show_cached,
+                       "show cached files in the output (default)"),
+               OPT_BOOLEAN('d', "deleted", &show_deleted,
+                       "show deleted files in the output"),
+               OPT_BOOLEAN('m', "modified", &show_modified,
+                       "show modified files in the output"),
+               OPT_BOOLEAN('o', "others", &show_others,
+                       "show other files in the output"),
+               OPT_BIT('i', "ignored", &dir.flags,
+                       "show ignored files in the output",
+                       DIR_SHOW_IGNORED),
+               OPT_BOOLEAN('s', "stage", &show_stage,
+                       "show staged contents' object name in the output"),
+               OPT_BOOLEAN('k', "killed", &show_killed,
+                       "show files on the filesystem that need to be removed"),
+               OPT_BIT(0, "directory", &dir.flags,
+                       "show 'other' directories' name only",
+                       DIR_SHOW_OTHER_DIRECTORIES),
+               OPT_BIT(0, "no-empty-directory", &dir.flags,
+                       "don't show empty directories",
+                       DIR_HIDE_EMPTY_DIRECTORIES),
+               OPT_BOOLEAN('u', "unmerged", &show_unmerged,
+                       "show unmerged files in the output"),
+               { OPTION_CALLBACK, 'x', "exclude", &dir.exclude_list[EXC_CMDL], "pattern",
+                       "skip files matching pattern",
+                       0, option_parse_exclude },
+               { OPTION_CALLBACK, 'X', "exclude-from", &dir, "file",
+                       "exclude patterns are read from <file>",
+                       0, option_parse_exclude_from },
+               OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, "file",
+                       "read additional per-directory exclude patterns in <file>"),
+               { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL,
+                       "add the standard git exclusions",
+                       PARSE_OPT_NOARG, option_parse_exclude_standard },
+               { OPTION_SET_INT, 0, "full-name", &prefix_offset, NULL,
+                       "make the output relative to the project top directory",
+                       PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
+               OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
+                       "if any <file> is not in the index, treat this as an error"),
+               OPT_STRING(0, "with-tree", &with_tree, "tree-ish",
+                       "pretend that paths removed since <tree-ish> are still present"),
+               OPT__ABBREV(&abbrev),
+               OPT_END()
+       };
 
        memset(&dir, 0, sizeof(dir));
        if (prefix)
                prefix_offset = strlen(prefix);
        git_config(git_default_config, NULL);
 
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (!strcmp(arg, "--")) {
-                       i++;
-                       break;
-               }
-               if (!strcmp(arg, "-z")) {
-                       line_terminator = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "-t") || !strcmp(arg, "-v")) {
-                       tag_cached = "H ";
-                       tag_unmerged = "M ";
-                       tag_removed = "R ";
-                       tag_modified = "C ";
-                       tag_other = "? ";
-                       tag_killed = "K ";
-                       if (arg[1] == 'v')
-                               show_valid_bit = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-c") || !strcmp(arg, "--cached")) {
-                       show_cached = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-d") || !strcmp(arg, "--deleted")) {
-                       show_deleted = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
-                       show_modified = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
-                       show_others = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
-                       dir.show_ignored = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
-                       show_stage = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
-                       show_killed = 1;
-                       require_work_tree = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--directory")) {
-                       dir.show_other_directories = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "--no-empty-directory")) {
-                       dir.hide_empty_directories = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-u") || !strcmp(arg, "--unmerged")) {
-                       /* There's no point in showing unmerged unless
-                        * you also show the stage information.
-                        */
-                       show_stage = 1;
-                       show_unmerged = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-x") && i+1 < argc) {
-                       exc_given = 1;
-                       add_exclude(argv[++i], "", 0, &dir.exclude_list[EXC_CMDL]);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--exclude=")) {
-                       exc_given = 1;
-                       add_exclude(arg+10, "", 0, &dir.exclude_list[EXC_CMDL]);
-                       continue;
-               }
-               if (!strcmp(arg, "-X") && i+1 < argc) {
-                       exc_given = 1;
-                       add_excludes_from_file(&dir, argv[++i]);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--exclude-from=")) {
-                       exc_given = 1;
-                       add_excludes_from_file(&dir, arg+15);
-                       continue;
-               }
-               if (!prefixcmp(arg, "--exclude-per-directory=")) {
-                       exc_given = 1;
-                       dir.exclude_per_dir = arg + 24;
-                       continue;
-               }
-               if (!strcmp(arg, "--exclude-standard")) {
-                       exc_given = 1;
-                       setup_standard_excludes(&dir);
-                       continue;
-               }
-               if (!strcmp(arg, "--full-name")) {
-                       prefix_offset = 0;
-                       continue;
-               }
-               if (!strcmp(arg, "--error-unmatch")) {
-                       error_unmatch = 1;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--with-tree=")) {
-                       with_tree = arg + 12;
-                       continue;
-               }
-               if (!prefixcmp(arg, "--abbrev=")) {
-                       abbrev = strtoul(arg+9, NULL, 10);
-                       if (abbrev && abbrev < MINIMUM_ABBREV)
-                               abbrev = MINIMUM_ABBREV;
-                       else if (abbrev > 40)
-                               abbrev = 40;
-                       continue;
-               }
-               if (!strcmp(arg, "--abbrev")) {
-                       abbrev = DEFAULT_ABBREV;
-                       continue;
-               }
-               if (*arg == '-')
-                       usage(ls_files_usage);
-               break;
+       argc = parse_options(argc, argv, builtin_ls_files_options,
+                       ls_files_usage, 0);
+       if (show_tag || show_valid_bit) {
+               tag_cached = "H ";
+               tag_unmerged = "M ";
+               tag_removed = "R ";
+               tag_modified = "C ";
+               tag_other = "? ";
+               tag_killed = "K ";
        }
+       if (show_modified || show_others || show_deleted || (dir.flags & DIR_SHOW_IGNORED) || show_killed)
+               require_work_tree = 1;
+       if (show_unmerged)
+               /*
+                * There's no point in showing unmerged unless
+                * you also show the stage information.
+                */
+               show_stage = 1;
+       if (dir.exclude_per_dir)
+               exc_given = 1;
 
        if (require_work_tree && !is_inside_work_tree())
                setup_work_tree();
 
-       pathspec = get_pathspec(prefix, argv + i);
+       pathspec = get_pathspec(prefix, argv);
+
+       /* be nice with submodule paths ending in a slash */
+       read_cache();
+       if (pathspec)
+               strip_trailing_slash_from_submodules();
 
        /* Verify that the pathspec matches the prefix */
        if (pathspec)
@@ -558,7 +529,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                ps_matched = xcalloc(1, num);
        }
 
-       if (dir.show_ignored && !exc_given) {
+       if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) {
                fprintf(stderr, "%s: --ignored needs some exclude pattern\n",
                        argv[0]);
                exit(1);
@@ -569,7 +540,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..22008dfa8fb94d971b6fb1bd7342ede02a8d3fd0 100644 (file)
@@ -60,7 +60,6 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
 {
        int retval = 0;
        const char *type = blob_type;
-       unsigned long size;
 
        if (S_ISGITLINK(mode)) {
                /*
@@ -68,13 +67,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;
@@ -95,17 +89,20 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
 
        if (!(ls_options & LS_NAME_ONLY)) {
                if (ls_options & LS_SHOW_SIZE) {
+                       char size_text[24];
                        if (!strcmp(type, blob_type)) {
-                               sha1_object_info(sha1, &size);
-                               printf("%06o %s %s %7lu\t", mode, type,
-                                      abbrev ? find_unique_abbrev(sha1, abbrev)
-                                             : sha1_to_hex(sha1),
-                                      size);
+                               unsigned long size;
+                               if (sha1_object_info(sha1, &size) == OBJ_BAD)
+                                       strcpy(size_text, "BAD");
+                               else
+                                       snprintf(size_text, sizeof(size_text),
+                                                "%lu", size);
                        } else
-                               printf("%06o %s %s %7c\t", mode, type,
-                                      abbrev ? find_unique_abbrev(sha1, abbrev)
-                                             : sha1_to_hex(sha1),
-                                      '-');
+                               strcpy(size_text, "-");
+                       printf("%06o %s %s %7s\t", mode, type,
+                              abbrev ? find_unique_abbrev(sha1, abbrev)
+                                     : sha1_to_hex(sha1),
+                              size_text);
                } else
                        printf("%06o %s %s\t", mode, type,
                               abbrev ? find_unique_abbrev(sha1, abbrev)
index dacc8ac2d0e63e46dd1aa8fee6ba949bdd319e43..1eeeb4de6d0d54e3fd753b7f057351094e10a24e 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);
@@ -487,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));
 }
@@ -526,7 +537,6 @@ static int decode_header_bq(struct strbuf *it)
                                 */
                                strbuf_add(&outbuf, in, ep - in);
                        }
-                       in = ep;
                }
                /* E.g.
                 * ep : "=?iso-2022-jp?B?GyR...?= foo"
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 2179e0686dda496fb6ec83bd03fc7135f2e6e040..0b58e5eda1f38c1560cc9dcf69be37fa5fc9886f 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,13 @@ 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;
 
+       strbuf_branchname(&bname, remote);
+       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 +375,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 +415,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 +437,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)
@@ -656,7 +635,7 @@ static int checkout_fast_forward(unsigned char *head, unsigned char *remote)
        memset(&opts, 0, sizeof(opts));
        memset(&t, 0, sizeof(t));
        memset(&dir, 0, sizeof(dir));
-       dir.show_ignored = 1;
+       dir.flags |= DIR_SHOW_IGNORED;
        dir.exclude_per_dir = ".gitignore";
        opts.dir = &dir;
 
index d1553455bae5818d2ce9d706f8ba6cf8b97b8d5a..9742b45c4da7f9330491d0b4c6d3ed60aadb0f4c 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;
@@ -293,7 +293,7 @@ static unsigned long write_object(struct sha1file *f,
                                die("unable to read %s", sha1_to_hex(entry->idx.sha1));
                        /*
                         * make sure no cached delta data remains from a
-                        * previous attempt before a pack split occured.
+                        * previous attempt before a pack split occurred.
                         */
                        free(entry->delta_data);
                        entry->delta_data = NULL;
@@ -1611,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 up to %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;
@@ -1894,19 +1901,25 @@ static void read_object_list_from_stdin(void)
 
 #define OBJECT_ADDED (1u<<20)
 
-static void show_commit(struct commit *commit)
+static void show_commit(struct commit *commit, void *data)
 {
        add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
        commit->object.flags |= OBJECT_ADDED;
 }
 
-static void show_object(struct object_array_entry *p)
+static void show_object(struct object *obj, const struct name_path *path, const char *last)
 {
-       add_preferred_base_object(p->name);
-       add_object_entry(p->item->sha1, p->item->type, p->name, 0);
-       p->item->flags |= OBJECT_ADDED;
-       free((char *)p->name);
-       p->name = NULL;
+       char *name = path_name(path, last);
+
+       add_preferred_base_object(name);
+       add_object_entry(obj->sha1, obj->type, name, 0);
+       obj->flags |= OBJECT_ADDED;
+
+       /*
+        * We will have generated the hash from the name,
+        * but not saved a pointer to it - we can free it
+        */
+       free((char *)name);
 }
 
 static void show_edge(struct commit *commit)
@@ -2066,7 +2079,7 @@ static void get_object_list(int ac, const char **av)
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        mark_edges_uninteresting(revs.commits, &revs, show_edge);
-       traverse_commit_list(&revs, show_commit, show_object);
+       traverse_commit_list(&revs, show_commit, show_object, NULL);
 
        if (keep_unreachable)
                add_objects_in_unpacked_packs(&revs);
index 2d5b2cd353a5e80a74e3828aae8e0c7dfa0e1997..00590b1c3c2cceda7a75537c8680a96cd2ef84d4 100644 (file)
@@ -28,8 +28,8 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
                memcpy(pathname + len, de->d_name, 38);
                if (opts & DRY_RUN)
                        printf("rm -f %s\n", pathname);
-               else if (unlink(pathname) < 0)
-                       error("unable to unlink %s", pathname);
+               else
+                       unlink_or_warn(pathname);
                display_progress(progress, i + 1);
        }
        pathname[len] = 0;
@@ -55,6 +55,7 @@ void prune_packed_objects(int opts)
        for (i = 0; i < 256; i++) {
                DIR *d;
 
+               display_progress(progress, i + 1);
                sprintf(pathname + len, "%02x/", i);
                d = opendir(pathname);
                if (!d)
index 7b4ec80e62997fe70f53c92380cd4d66e80d62a0..145ba83651e9c8560e20d6ab449ce64783bc65f5 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>...]",
@@ -26,7 +27,7 @@ static int prune_tmp_object(const char *path, const char *filename)
        }
        printf("Removing stale temporary file %s\n", fullpath);
        if (!show_only)
-               unlink(fullpath);
+               unlink_or_warn(fullpath);
        return 0;
 }
 
@@ -46,7 +47,7 @@ static int prune_object(char *path, const char *filename, const unsigned char *s
                       (type > 0) ? typename(type) : "unknown");
        }
        if (!show_only)
-               unlink(fullpath);
+               unlink_or_warn(fullpath);
        return 0;
 }
 
@@ -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 122fdcfbdc13a11388a091e5ec33d077648fab0a..2eabcd3bdfb3f5d5705125a8f74d21d4ab1deafc 100644 (file)
@@ -48,13 +48,81 @@ static void set_refspecs(const char **refs, int nr)
        }
 }
 
+static void setup_push_tracking(void)
+{
+       struct strbuf refspec = STRBUF_INIT;
+       struct branch *branch = branch_get(NULL);
+       if (!branch)
+               die("You are not currently on a branch.");
+       if (!branch->merge_nr)
+               die("The current branch %s is not tracking anything.",
+                   branch->name);
+       if (branch->merge_nr != 1)
+               die("The current branch %s is tracking multiple branches, "
+                   "refusing to push.", branch->name);
+       strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
+       add_refspec(refspec.buf);
+}
+
+static const char *warn_unconfigured_push_msg[] = {
+       "You did not specify any refspecs to push, and the current remote",
+       "has not configured any push refspecs. The default action in this",
+       "case is to push all matching refspecs, that is, all branches",
+       "that exist both locally and remotely will be updated.  This may",
+       "not necessarily be what you want to happen.",
+       "",
+       "You can specify what action you want to take in this case, and",
+       "avoid seeing this message again, by configuring 'push.default' to:",
+       "  'nothing'  : Do not push anything",
+       "  'matching' : Push all matching branches (default)",
+       "  'tracking' : Push the current branch to whatever it is tracking",
+       "  'current'  : Push the current branch"
+};
+
+static void warn_unconfigured_push(void)
+{
+       int i;
+       for (i = 0; i < ARRAY_SIZE(warn_unconfigured_push_msg); i++)
+               warning("%s", warn_unconfigured_push_msg[i]);
+}
+
+static void setup_default_push_refspecs(void)
+{
+       git_config(git_default_config, NULL);
+       switch (push_default) {
+       case PUSH_DEFAULT_UNSPECIFIED:
+               warn_unconfigured_push();
+               /* fallthrough */
+
+       case PUSH_DEFAULT_MATCHING:
+               add_refspec(":");
+               break;
+
+       case PUSH_DEFAULT_TRACKING:
+               setup_push_tracking();
+               break;
+
+       case PUSH_DEFAULT_CURRENT:
+               add_refspec("HEAD");
+               break;
+
+       case PUSH_DEFAULT_NOTHING:
+               die("You didn't specify any refspecs to push, and "
+                   "push.default is \"nothing\".");
+               break;
+       }
+}
+
 static int do_push(const char *repo, int flags)
 {
        int i, errs;
        struct remote *remote = remote_get(repo);
 
-       if (!remote)
-               die("bad repository '%s'", repo);
+       if (!remote) {
+               if (repo)
+                       die("bad repository '%s'", repo);
+               die("No destination configured to push to.");
+       }
 
        if (remote->mirror)
                flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
@@ -76,11 +144,12 @@ static int do_push(const char *repo, int flags)
                return error("--all and --mirror are incompatible");
        }
 
-       if (!refspec
-               && !(flags & TRANSPORT_PUSH_ALL)
-               && remote->push_refspec_nr) {
-               refspec = remote->push_refspec;
-               refspec_nr = remote->push_refspec_nr;
+       if (!refspec && !(flags & TRANSPORT_PUSH_ALL)) {
+               if (remote->push_refspec_nr) {
+                       refspec = remote->push_refspec;
+                       refspec_nr = remote->push_refspec_nr;
+               } else if (!(flags & TRANSPORT_PUSH_MIRROR))
+                       setup_default_push_refspecs();
        }
        errs = 0;
        for (i = 0; i < remote->url_nr; i++) {
index 38fef34d3fb6c24ab89043951f1ecf6c96e9bf51..82e25eaa0758d8b9584592f4072f81eeccb0bf1d 100644 (file)
@@ -29,41 +29,6 @@ static int list_tree(unsigned char *sha1)
        return 0;
 }
 
-static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
-{
-       struct tree_desc desc;
-       struct name_entry entry;
-       int cnt;
-
-       hashcpy(it->sha1, tree->object.sha1);
-       init_tree_desc(&desc, tree->buffer, tree->size);
-       cnt = 0;
-       while (tree_entry(&desc, &entry)) {
-               if (!S_ISDIR(entry.mode))
-                       cnt++;
-               else {
-                       struct cache_tree_sub *sub;
-                       struct tree *subtree = lookup_tree(entry.sha1);
-                       if (!subtree->object.parsed)
-                               parse_tree(subtree);
-                       sub = cache_tree_sub(it, entry.path);
-                       sub->cache_tree = cache_tree();
-                       prime_cache_tree_rec(sub->cache_tree, subtree);
-                       cnt += sub->cache_tree->entry_count;
-               }
-       }
-       it->entry_count = cnt;
-}
-
-static void prime_cache_tree(void)
-{
-       if (!nr_trees)
-               return;
-       active_cache_tree = cache_tree();
-       prime_cache_tree_rec(active_cache_tree, trees[0]);
-
-}
-
 static const char read_tree_usage[] = "git read-tree (<sha> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
 
 static struct lock_file lock_file;
@@ -170,7 +135,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                                die("more than one --exclude-per-directory are given.");
 
                        dir = xcalloc(1, sizeof(*opts.dir));
-                       dir->show_ignored = 1;
+                       dir->flags |= DIR_SHOW_IGNORED;
                        dir->exclude_per_dir = arg + 24;
                        opts.dir = dir;
                        /* We do not need to nor want to do read-directory
@@ -211,7 +176,6 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                case 3:
                default:
                        opts.fn = threeway_merge;
-                       cache_tree_free(&active_cache_tree);
                        break;
                }
 
@@ -221,6 +185,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        opts.head_idx = 1;
        }
 
+       cache_tree_free(&active_cache_tree);
        for (i = 0; i < nr_trees; i++) {
                struct tree *tree = trees[i];
                parse_tree(tree);
@@ -234,11 +199,14 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
         * "-m ent" or "--reset ent" form), we can obtain a fully
         * valid cache-tree because the index must match exactly
         * what came from the tree.
+        *
+        * The same holds true if we are switching between two trees
+        * using read-tree -m A B.  The index must match B after that.
         */
-       if (nr_trees && !opts.prefix && (!opts.merge || (stage == 2))) {
-               cache_tree_free(&active_cache_tree);
-               prime_cache_tree();
-       }
+       if (nr_trees == 1 && !opts.prefix)
+               prime_cache_tree(&active_cache_tree, trees[0]);
+       else if (nr_trees == 2 && opts.merge)
+               prime_cache_tree(&active_cache_tree, trees[1]);
 
        if (write_cache(newfd, active_cache, active_nr) ||
            commit_locked_index(&lock_file))
index db67c3162c6b8ffaba793fd6082324aeae158f86..0b08da9b595432efa231eb59d88ecd2b422eb56a 100644 (file)
@@ -9,25 +9,27 @@
 #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 char capabilities[] = " report-status delete-refs ";
-static int capabilities_sent;
+static int prefer_ofs_delta = 1;
+static const char *head_name;
+static char *capabilities_to_send;
 
 static enum deny_action parse_deny_action(const char *var, const char *value)
 {
@@ -76,24 +78,34 @@ 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;
+       }
+
+       if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+               prefer_ofs_delta = git_config_bool(var, value);
+               return 0;
+       }
+
        return git_default_config(var, value, cb);
 }
 
 static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       if (capabilities_sent)
+       if (!capabilities_to_send)
                packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
        else
                packet_write(1, "%s %s%c%s\n",
-                            sha1_to_hex(sha1), path, 0, capabilities);
-       capabilities_sent = 1;
+                            sha1_to_hex(sha1), path, 0, capabilities_to_send);
+       capabilities_to_send = NULL;
        return 0;
 }
 
 static void write_head_info(void)
 {
        for_each_ref(show_ref, NULL);
-       if (!capabilities_sent)
+       if (capabilities_to_send)
                show_ref("capabilities^{}", null_sha1, 0, NULL);
 
 }
@@ -136,7 +148,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 +214,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 +290,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 +311,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 +428,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 +438,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 +446,8 @@ static void execute_commands(const char *unpacker_error)
                return;
        }
 
+       head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
        while (cmd) {
                cmd->error_string = update(cmd);
                cmd = cmd->next;
@@ -597,7 +679,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        setup_path();
 
        if (!enter_repo(dir, 0))
-               die("'%s': unable to chdir or not a git archive", dir);
+               die("'%s' does not appear to be a git repository", dir);
 
        if (is_repository_shallow())
                die("attempt to push into a shallow repository");
@@ -609,6 +691,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
        else if (0 <= receive_unpack_limit)
                unpack_limit = receive_unpack_limit;
 
+       capabilities_to_send = (prefer_ofs_delta) ?
+               " report-status delete-refs ofs-delta " :
+               " report-status delete-refs ";
+
        add_alternate_refs();
        write_head_info();
        clear_extra_refs();
@@ -624,10 +710,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
                        unpack_status = unpack();
                execute_commands(unpack_status);
                if (pack_lockfile)
-                       unlink(pack_lockfile);
+                       unlink_or_warn(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 d95f515f2e32aa71c3a09fe5ed9486f352713360..ddfdf5a3cbc79003dfbad14af8d1f047e3594aa6 100644 (file)
@@ -52,6 +52,7 @@ struct collect_reflog_cb {
 
 #define INCOMPLETE     (1u<<10)
 #define STUDYING       (1u<<11)
+#define REACHABLE      (1u<<12)
 
 static int tree_is_complete(const unsigned char *sha1)
 {
@@ -209,6 +210,70 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
        return 1;
 }
 
+static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+{
+       /*
+        * We may or may not have the commit yet - if not, look it
+        * up using the supplied sha1.
+        */
+       if (!commit) {
+               if (is_null_sha1(sha1))
+                       return 0;
+
+               commit = lookup_commit_reference_gently(sha1, 1);
+
+               /* Not a commit -- keep it */
+               if (!commit)
+                       return 0;
+       }
+
+       /* Reachable from the current ref?  Don't prune. */
+       if (commit->object.flags & REACHABLE)
+               return 0;
+       if (in_merge_bases(commit, &cb->ref_commit, 1))
+               return 0;
+
+       /* We can't reach it - prune it. */
+       return 1;
+}
+
+static void mark_reachable(struct commit *commit, unsigned long expire_limit)
+{
+       /*
+        * We need to compute whether the commit on either side of a reflog
+        * entry is reachable from the tip of the ref for all entries.
+        * Mark commits that are reachable from the tip down to the
+        * time threshold first; we know a commit marked thusly is
+        * reachable from the tip without running in_merge_bases()
+        * at all.
+        */
+       struct commit_list *pending = NULL;
+
+       commit_list_insert(commit, &pending);
+       while (pending) {
+               struct commit_list *entry = pending;
+               struct commit_list *parent;
+               pending = entry->next;
+               commit = entry->item;
+               free(entry);
+               if (commit->object.flags & REACHABLE)
+                       continue;
+               if (parse_commit(commit))
+                       continue;
+               commit->object.flags |= REACHABLE;
+               if (commit->date < expire_limit)
+                       continue;
+               parent = commit->parents;
+               while (parent) {
+                       commit = parent->item;
+                       parent = parent->next;
+                       if (commit->object.flags & REACHABLE)
+                               continue;
+                       commit_list_insert(commit, &pending);
+               }
+       }
+}
+
 static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
                const char *email, unsigned long timestamp, int tz,
                const char *message, void *cb_data)
@@ -230,12 +295,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
        if (timestamp < cb->cmd->expire_unreachable) {
                if (!cb->ref_commit)
                        goto prune;
-               if (!old && !is_null_sha1(osha1))
-                       old = lookup_commit_reference_gently(osha1, 1);
-               if (!new && !is_null_sha1(nsha1))
-                       new = lookup_commit_reference_gently(nsha1, 1);
-               if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) ||
-                   (new && !in_merge_bases(new, &cb->ref_commit, 1)))
+               if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
                        goto prune;
        }
 
@@ -288,7 +348,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
        cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
        cb.ref = ref;
        cb.cmd = cmd;
+       if (cb.ref_commit)
+               mark_reachable(cb.ref_commit, cmd->expire_total);
        for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+       if (cb.ref_commit)
+               clear_commit_marks(cb.ref_commit, REACHABLE);
  finish:
        if (cb.newlog) {
                if (fclose(cb.newlog)) {
index db18bcfc97f0739c2e77164dc0222148a44c67a6..71abf68404f5b260ba96208717d89e50e778dd36 100644 (file)
@@ -12,15 +12,21 @@ static const char * const builtin_remote_usage[] = {
        "git remote add [-t <branch>] [-m <master>] [-f] [--mirror] <name> <url>",
        "git remote rename <old> <new>",
        "git remote rm <name>",
+       "git remote set-head <name> [-a | -d | <branch>]",
        "git remote show [-n] <name>",
        "git remote prune [-n | --dry-run] <name>",
-       "git remote [-v | --verbose] update [group]",
+       "git remote [-v | --verbose] update [-p | --prune] [group]",
        NULL
 };
 
+#define GET_REF_STATES (1<<0)
+#define GET_HEAD_NAMES (1<<1)
+#define GET_PUSH_REF_STATES (1<<2)
+
 static int verbose;
 
 static int show_all(void);
+static int prune_remote(const char *remote, int dry_run);
 
 static inline int postfixcmp(const char *string, const char *postfix)
 {
@@ -143,8 +149,9 @@ static int add(int argc, const char **argv)
 }
 
 struct branch_info {
-       char *remote;
+       char *remote_name;
        struct string_list merge;
+       int rebase;
 };
 
 static struct string_list branch_list;
@@ -161,10 +168,11 @@ static const char *abbrev_ref(const char *name, const char *prefix)
 static int config_read_branches(const char *key, const char *value, void *cb)
 {
        if (!prefixcmp(key, "branch.")) {
+               const char *orig_key = key;
                char *name;
                struct string_list_item *item;
                struct branch_info *info;
-               enum { REMOTE, MERGE } type;
+               enum { REMOTE, MERGE, REBASE } type;
 
                key += 7;
                if (!postfixcmp(key, ".remote")) {
@@ -173,6 +181,9 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                } else if (!postfixcmp(key, ".merge")) {
                        name = xstrndup(key, strlen(key) - 6);
                        type = MERGE;
+               } else if (!postfixcmp(key, ".rebase")) {
+                       name = xstrndup(key, strlen(key) - 7);
+                       type = REBASE;
                } else
                        return 0;
 
@@ -182,10 +193,10 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                        item->util = xcalloc(sizeof(struct branch_info), 1);
                info = item->util;
                if (type == REMOTE) {
-                       if (info->remote)
-                               warning("more than one branch.%s", key);
-                       info->remote = xstrdup(value);
-               } else {
+                       if (info->remote_name)
+                               warning("more than one %s", orig_key);
+                       info->remote_name = xstrdup(value);
+               } else if (type == MERGE) {
                        char *space = strchr(value, ' ');
                        value = abbrev_branch(value);
                        while (space) {
@@ -196,7 +207,8 @@ static int config_read_branches(const char *key, const char *value, void *cb)
                                space = strchr(value, ' ');
                        }
                        string_list_append(xstrdup(value), &info->merge);
-               }
+               } else
+                       info->rebase = git_config_bool(orig_key, value);
        }
        return 0;
 }
@@ -206,12 +218,12 @@ static void read_branches(void)
        if (branch_list.nr)
                return;
        git_config(config_read_branches, NULL);
-       sort_string_list(&branch_list);
 }
 
 struct ref_states {
        struct remote *remote;
-       struct string_list new, stale, tracked;
+       struct string_list new, stale, tracked, heads, push;
+       int queried;
 };
 
 static int handle_one_branch(const char *refname,
@@ -227,10 +239,8 @@ static int handle_one_branch(const char *refname,
                const char *name = abbrev_branch(refspec.src);
                /* symbolic refs pointing nowhere were handled already */
                if ((flags & REF_ISSYMREF) ||
-                               unsorted_string_list_has_string(&states->tracked,
-                                       name) ||
-                               unsorted_string_list_has_string(&states->new,
-                                       name))
+                   string_list_has_string(&states->tracked, name) ||
+                   string_list_has_string(&states->new, name))
                        return 0;
                item = string_list_append(name, &states->stale);
                item->util = xstrdup(refname);
@@ -238,39 +248,154 @@ static int handle_one_branch(const char *refname,
        return 0;
 }
 
-static int get_ref_states(const struct ref *ref, struct ref_states *states)
+static int get_ref_states(const struct ref *remote_refs, struct ref_states *states)
 {
        struct ref *fetch_map = NULL, **tail = &fetch_map;
+       struct ref *ref;
        int i;
 
        for (i = 0; i < states->remote->fetch_refspec_nr; i++)
-               if (get_fetch_map(ref, states->remote->fetch + i, &tail, 1))
+               if (get_fetch_map(remote_refs, states->remote->fetch + i, &tail, 1))
                        die("Could not get fetch map for refspec %s",
                                states->remote->fetch_refspec[i]);
 
        states->new.strdup_strings = states->tracked.strdup_strings = 1;
        for (ref = fetch_map; ref; ref = ref->next) {
-               struct string_list *target = &states->tracked;
                unsigned char sha1[20];
-               void *util = NULL;
-
                if (!ref->peer_ref || read_ref(ref->peer_ref->name, sha1))
-                       target = &states->new;
-               else {
-                       target = &states->tracked;
-                       if (hashcmp(sha1, ref->new_sha1))
-                               util = &states;
-               }
-               string_list_append(abbrev_branch(ref->name), target)->util = util;
+                       string_list_append(abbrev_branch(ref->name), &states->new);
+               else
+                       string_list_append(abbrev_branch(ref->name), &states->tracked);
        }
        free_refs(fetch_map);
 
+       sort_string_list(&states->new);
+       sort_string_list(&states->tracked);
        for_each_ref(handle_one_branch, states);
        sort_string_list(&states->stale);
 
        return 0;
 }
 
+struct push_info {
+       char *dest;
+       int forced;
+       enum {
+               PUSH_STATUS_CREATE = 0,
+               PUSH_STATUS_DELETE,
+               PUSH_STATUS_UPTODATE,
+               PUSH_STATUS_FASTFORWARD,
+               PUSH_STATUS_OUTOFDATE,
+               PUSH_STATUS_NOTQUERIED,
+       } status;
+};
+
+static int get_push_ref_states(const struct ref *remote_refs,
+       struct ref_states *states)
+{
+       struct remote *remote = states->remote;
+       struct ref *ref, *local_refs, *push_map, **push_tail;
+       if (remote->mirror)
+               return 0;
+
+       local_refs = get_local_heads();
+       ref = push_map = copy_ref_list(remote_refs);
+       while (ref->next)
+               ref = ref->next;
+       push_tail = &ref->next;
+
+       match_refs(local_refs, push_map, &push_tail, remote->push_refspec_nr,
+                  remote->push_refspec, MATCH_REFS_NONE);
+
+       states->push.strdup_strings = 1;
+       for (ref = push_map; ref; ref = ref->next) {
+               struct string_list_item *item;
+               struct push_info *info;
+
+               if (!ref->peer_ref)
+                       continue;
+               hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
+
+               item = string_list_append(abbrev_branch(ref->peer_ref->name),
+                                         &states->push);
+               item->util = xcalloc(sizeof(struct push_info), 1);
+               info = item->util;
+               info->forced = ref->force;
+               info->dest = xstrdup(abbrev_branch(ref->name));
+
+               if (is_null_sha1(ref->new_sha1)) {
+                       info->status = PUSH_STATUS_DELETE;
+               } else if (!hashcmp(ref->old_sha1, ref->new_sha1))
+                       info->status = PUSH_STATUS_UPTODATE;
+               else if (is_null_sha1(ref->old_sha1))
+                       info->status = PUSH_STATUS_CREATE;
+               else if (has_sha1_file(ref->old_sha1) &&
+                        ref_newer(ref->new_sha1, ref->old_sha1))
+                       info->status = PUSH_STATUS_FASTFORWARD;
+               else
+                       info->status = PUSH_STATUS_OUTOFDATE;
+       }
+       free_refs(local_refs);
+       free_refs(push_map);
+       return 0;
+}
+
+static int get_push_ref_states_noquery(struct ref_states *states)
+{
+       int i;
+       struct remote *remote = states->remote;
+       struct string_list_item *item;
+       struct push_info *info;
+
+       if (remote->mirror)
+               return 0;
+
+       states->push.strdup_strings = 1;
+       if (!remote->push_refspec_nr) {
+               item = string_list_append("(matching)", &states->push);
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->status = PUSH_STATUS_NOTQUERIED;
+               info->dest = xstrdup(item->string);
+       }
+       for (i = 0; i < remote->push_refspec_nr; i++) {
+               struct refspec *spec = remote->push + i;
+               if (spec->matching)
+                       item = string_list_append("(matching)", &states->push);
+               else if (strlen(spec->src))
+                       item = string_list_append(spec->src, &states->push);
+               else
+                       item = string_list_append("(delete)", &states->push);
+
+               info = item->util = xcalloc(sizeof(struct push_info), 1);
+               info->forced = spec->force;
+               info->status = PUSH_STATUS_NOTQUERIED;
+               info->dest = xstrdup(spec->dst ? spec->dst : item->string);
+       }
+       return 0;
+}
+
+static int get_head_names(const struct ref *remote_refs, struct ref_states *states)
+{
+       struct ref *ref, *matches;
+       struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map;
+       struct refspec refspec;
+
+       refspec.force = 0;
+       refspec.pattern = 1;
+       refspec.src = refspec.dst = "refs/heads/*";
+       states->heads.strdup_strings = 1;
+       get_fetch_map(remote_refs, &refspec, &fetch_map_tail, 0);
+       matches = guess_remote_head(find_ref_by_name(remote_refs, "HEAD"),
+                                   fetch_map, 1);
+       for(ref = matches; ref; ref = ref->next)
+               string_list_append(abbrev_branch(ref->name), &states->heads);
+
+       free_refs(fetch_map);
+       free_refs(matches);
+
+       return 0;
+}
+
 struct known_remote {
        struct known_remote *next;
        struct remote *remote;
@@ -400,8 +525,8 @@ static int migrate_file(struct remote *remote)
                path = git_path("remotes/%s", remote->name);
        else if (remote->origin == REMOTE_BRANCHES)
                path = git_path("branches/%s", remote->name);
-       if (path && unlink(path))
-               warning("failed to remove '%s'", path);
+       if (path)
+               unlink_or_warn(path);
        return 0;
 }
 
@@ -466,7 +591,7 @@ static int mv(int argc, const char **argv)
        for (i = 0; i < branch_list.nr; i++) {
                struct string_list_item *item = branch_list.items + i;
                struct branch_info *info = item->util;
-               if (info->remote && !strcmp(info->remote, rename.old)) {
+               if (info->remote_name && !strcmp(info->remote_name, rename.old)) {
                        strbuf_reset(&buf);
                        strbuf_addf(&buf, "branch.%s.remote", item->string);
                        if (git_config_set(buf.buf, rename.new)) {
@@ -484,9 +609,8 @@ static int mv(int argc, const char **argv)
                struct string_list_item *item = remote_branches.items + i;
                int flag = 0;
                unsigned char sha1[20];
-               const char *symref;
 
-               symref = resolve_ref(item->string, sha1, 1, &flag);
+               resolve_ref(item->string, sha1, 1, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
                if (delete_ref(item->string, NULL, REF_NODEREF))
@@ -576,7 +700,7 @@ static int rm(int argc, const char **argv)
        for (i = 0; i < branch_list.nr; i++) {
                struct string_list_item *item = branch_list.items + i;
                struct branch_info *info = item->util;
-               if (info->remote && !strcmp(info->remote, remote->name)) {
+               if (info->remote_name && !strcmp(info->remote_name, remote->name)) {
                        const char *keys[] = { "remote", "merge", NULL }, **k;
                        for (k = keys; *k; k++) {
                                strbuf_reset(&buf);
@@ -618,18 +742,37 @@ static int rm(int argc, const char **argv)
        return result;
 }
 
-static void show_list(const char *title, struct string_list *list,
-                     const char *extra_arg)
+void clear_push_info(void *util, const char *string)
 {
-       int i;
+       struct push_info *info = util;
+       free(info->dest);
+       free(info);
+}
 
-       if (!list->nr)
-               return;
+static void free_remote_ref_states(struct ref_states *states)
+{
+       string_list_clear(&states->new, 0);
+       string_list_clear(&states->stale, 0);
+       string_list_clear(&states->tracked, 0);
+       string_list_clear(&states->heads, 0);
+       string_list_clear_func(&states->push, clear_push_info);
+}
 
-       printf(title, list->nr > 1 ? "es" : "", extra_arg);
-       printf("\n");
-       for (i = 0; i < list->nr; i++)
-               printf("    %s\n", list->items[i].string);
+static int append_ref_to_tracked_list(const char *refname,
+       const unsigned char *sha1, int flags, void *cb_data)
+{
+       struct ref_states *states = cb_data;
+       struct refspec refspec;
+
+       if (flags & REF_ISSYMREF)
+               return 0;
+
+       memset(&refspec, 0, sizeof(refspec));
+       refspec.dst = (char *)refname;
+       if (!remote_find_tracking(states->remote, &refspec))
+               string_list_append(abbrev_branch(refspec.src), &states->tracked);
+
+       return 0;
 }
 
 static int get_remote_ref_states(const char *name,
@@ -637,7 +780,7 @@ static int get_remote_ref_states(const char *name,
                                 int query)
 {
        struct transport *transport;
-       const struct ref *ref;
+       const struct ref *remote_refs;
 
        states->remote = remote_get(name);
        if (!states->remote)
@@ -648,102 +791,333 @@ static int get_remote_ref_states(const char *name,
        if (query) {
                transport = transport_get(NULL, states->remote->url_nr > 0 ?
                        states->remote->url[0] : NULL);
-               ref = transport_get_remote_refs(transport);
+               remote_refs = transport_get_remote_refs(transport);
                transport_disconnect(transport);
 
-               get_ref_states(ref, states);
+               states->queried = 1;
+               if (query & GET_REF_STATES)
+                       get_ref_states(remote_refs, states);
+               if (query & GET_HEAD_NAMES)
+                       get_head_names(remote_refs, states);
+               if (query & GET_PUSH_REF_STATES)
+                       get_push_ref_states(remote_refs, states);
+       } else {
+               for_each_ref(append_ref_to_tracked_list, states);
+               sort_string_list(&states->tracked);
+               get_push_ref_states_noquery(states);
        }
 
        return 0;
 }
 
-static int append_ref_to_tracked_list(const char *refname,
-       const unsigned char *sha1, int flags, void *cb_data)
+struct show_info {
+       struct string_list *list;
+       struct ref_states *states;
+       int width, width2;
+       int any_rebase;
+};
+
+int add_remote_to_show_info(struct string_list_item *item, void *cb_data)
 {
-       struct ref_states *states = cb_data;
-       struct refspec refspec;
+       struct show_info *info = cb_data;
+       int n = strlen(item->string);
+       if (n > info->width)
+               info->width = n;
+       string_list_insert(item->string, info->list);
+       return 0;
+}
 
-       memset(&refspec, 0, sizeof(refspec));
-       refspec.dst = (char *)refname;
-       if (!remote_find_tracking(states->remote, &refspec))
-               string_list_append(abbrev_branch(refspec.src), &states->tracked);
+int show_remote_info_item(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *info = cb_data;
+       struct ref_states *states = info->states;
+       const char *name = item->string;
+
+       if (states->queried) {
+               const char *fmt = "%s";
+               const char *arg = "";
+               if (string_list_has_string(&states->new, name)) {
+                       fmt = " new (next fetch will store in remotes/%s)";
+                       arg = states->remote->name;
+               } else if (string_list_has_string(&states->tracked, name))
+                       arg = " tracked";
+               else if (string_list_has_string(&states->stale, name))
+                       arg = " stale (use 'git remote prune' to remove)";
+               else
+                       arg = " ???";
+               printf("    %-*s", info->width, name);
+               printf(fmt, arg);
+               printf("\n");
+       } else
+               printf("    %s\n", name);
 
        return 0;
 }
 
+int add_local_to_show_info(struct string_list_item *branch_item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct ref_states *states = show_info->states;
+       struct branch_info *branch_info = branch_item->util;
+       struct string_list_item *item;
+       int n;
+
+       if (!branch_info->merge.nr || !branch_info->remote_name ||
+           strcmp(states->remote->name, branch_info->remote_name))
+               return 0;
+       if ((n = strlen(branch_item->string)) > show_info->width)
+               show_info->width = n;
+       if (branch_info->rebase)
+               show_info->any_rebase = 1;
+
+       item = string_list_insert(branch_item->string, show_info->list);
+       item->util = branch_info;
+
+       return 0;
+}
+
+int show_local_info_item(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct branch_info *branch_info = item->util;
+       struct string_list *merge = &branch_info->merge;
+       const char *also;
+       int i;
+
+       if (branch_info->rebase && branch_info->merge.nr > 1) {
+               error("invalid branch.%s.merge; cannot rebase onto > 1 branch",
+                       item->string);
+               return 0;
+       }
+
+       printf("    %-*s ", show_info->width, item->string);
+       if (branch_info->rebase) {
+               printf("rebases onto remote %s\n", merge->items[0].string);
+               return 0;
+       } else if (show_info->any_rebase) {
+               printf(" merges with remote %s\n", merge->items[0].string);
+               also = "    and with remote";
+       } else {
+               printf("merges with remote %s\n", merge->items[0].string);
+               also = "   and with remote";
+       }
+       for (i = 1; i < merge->nr; i++)
+               printf("    %-*s %s %s\n", show_info->width, "", also,
+                      merge->items[i].string);
+
+       return 0;
+}
+
+int add_push_to_show_info(struct string_list_item *push_item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = push_item->util;
+       struct string_list_item *item;
+       int n;
+       if ((n = strlen(push_item->string)) > show_info->width)
+               show_info->width = n;
+       if ((n = strlen(push_info->dest)) > show_info->width2)
+               show_info->width2 = n;
+       item = string_list_append(push_item->string, show_info->list);
+       item->util = push_item->util;
+       return 0;
+}
+
+/*
+ * Sorting comparison for a string list that has push_info
+ * structs in its util field
+ */
+static int cmp_string_with_push(const void *va, const void *vb)
+{
+       const struct string_list_item *a = va;
+       const struct string_list_item *b = vb;
+       const struct push_info *a_push = a->util;
+       const struct push_info *b_push = b->util;
+       int cmp = strcmp(a->string, b->string);
+       return cmp ? cmp : strcmp(a_push->dest, b_push->dest);
+}
+
+int show_push_info_item(struct string_list_item *item, void *cb_data)
+{
+       struct show_info *show_info = cb_data;
+       struct push_info *push_info = item->util;
+       char *src = item->string, *status = NULL;
+
+       switch (push_info->status) {
+       case PUSH_STATUS_CREATE:
+               status = "create";
+               break;
+       case PUSH_STATUS_DELETE:
+               status = "delete";
+               src = "(none)";
+               break;
+       case PUSH_STATUS_UPTODATE:
+               status = "up to date";
+               break;
+       case PUSH_STATUS_FASTFORWARD:
+               status = "fast forwardable";
+               break;
+       case PUSH_STATUS_OUTOFDATE:
+               status = "local out of date";
+               break;
+       case PUSH_STATUS_NOTQUERIED:
+               break;
+       }
+       if (status)
+               printf("    %-*s %s to %-*s (%s)\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       show_info->width2, push_info->dest, status);
+       else
+               printf("    %-*s %s to %s\n", show_info->width, src,
+                       push_info->forced ? "forces" : "pushes",
+                       push_info->dest);
+       return 0;
+}
+
 static int show(int argc, const char **argv)
 {
-       int no_query = 0, result = 0;
+       int no_query = 0, result = 0, query_flag = 0;
        struct option options[] = {
                OPT_GROUP("show specific options"),
                OPT_BOOLEAN('n', NULL, &no_query, "do not query remotes"),
                OPT_END()
        };
        struct ref_states states;
+       struct string_list info_list = { NULL, 0, 0, 0 };
+       struct show_info info;
 
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
 
        if (argc < 1)
                return show_all();
 
+       if (!no_query)
+               query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES);
+
        memset(&states, 0, sizeof(states));
+       memset(&info, 0, sizeof(info));
+       info.states = &states;
+       info.list = &info_list;
        for (; argc; argc--, argv++) {
                int i;
 
-               get_remote_ref_states(*argv, &states, !no_query);
+               get_remote_ref_states(*argv, &states, query_flag);
 
                printf("* remote %s\n  URL: %s\n", *argv,
                        states.remote->url_nr > 0 ?
                                states.remote->url[0] : "(no URL)");
-
-               for (i = 0; i < branch_list.nr; i++) {
-                       struct string_list_item *branch = branch_list.items + i;
-                       struct branch_info *info = branch->util;
-                       int j;
-
-                       if (!info->merge.nr || strcmp(*argv, info->remote))
-                               continue;
-                       printf("  Remote branch%s merged with 'git pull' "
-                               "while on branch %s\n   ",
-                               info->merge.nr > 1 ? "es" : "",
-                               branch->string);
-                       for (j = 0; j < info->merge.nr; j++)
-                               printf(" %s", info->merge.items[j].string);
-                       printf("\n");
+               if (no_query)
+                       printf("  HEAD branch: (not queried)\n");
+               else if (!states.heads.nr)
+                       printf("  HEAD branch: (unknown)\n");
+               else if (states.heads.nr == 1)
+                       printf("  HEAD branch: %s\n", states.heads.items[0].string);
+               else {
+                       printf("  HEAD branch (remote HEAD is ambiguous,"
+                              " may be one of the following):\n");
+                       for (i = 0; i < states.heads.nr; i++)
+                               printf("    %s\n", states.heads.items[i].string);
                }
 
-               if (!no_query) {
-                       show_list("  New remote branch%s (next fetch "
-                               "will store in remotes/%s)",
-                               &states.new, states.remote->name);
-                       show_list("  Stale tracking branch%s (use 'git remote "
-                               "prune')", &states.stale, "");
-               }
+               /* remote branch info */
+               info.width = 0;
+               for_each_string_list(add_remote_to_show_info, &states.new, &info);
+               for_each_string_list(add_remote_to_show_info, &states.tracked, &info);
+               for_each_string_list(add_remote_to_show_info, &states.stale, &info);
+               if (info.list->nr)
+                       printf("  Remote branch%s:%s\n",
+                              info.list->nr > 1 ? "es" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(show_remote_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+
+               /* git pull info */
+               info.width = 0;
+               info.any_rebase = 0;
+               for_each_string_list(add_local_to_show_info, &branch_list, &info);
+               if (info.list->nr)
+                       printf("  Local branch%s configured for 'git pull':\n",
+                              info.list->nr > 1 ? "es" : "");
+               for_each_string_list(show_local_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+
+               /* git push info */
+               if (states.remote->mirror)
+                       printf("  Local refs will be mirrored by 'git push'\n");
+
+               info.width = info.width2 = 0;
+               for_each_string_list(add_push_to_show_info, &states.push, &info);
+               qsort(info.list->items, info.list->nr,
+                       sizeof(*info.list->items), cmp_string_with_push);
+               if (info.list->nr)
+                       printf("  Local ref%s configured for 'git push'%s:\n",
+                               info.list->nr > 1 ? "s" : "",
+                               no_query ? " (status not queried)" : "");
+               for_each_string_list(show_push_info_item, info.list, &info);
+               string_list_clear(info.list, 0);
+
+               free_remote_ref_states(&states);
+       }
 
-               if (no_query)
-                       for_each_ref(append_ref_to_tracked_list, &states);
-               show_list("  Tracked remote branch%s", &states.tracked, "");
-
-               if (states.remote->push_refspec_nr) {
-                       printf("  Local branch%s pushed with 'git push'\n",
-                               states.remote->push_refspec_nr > 1 ?
-                                       "es" : "");
-                       for (i = 0; i < states.remote->push_refspec_nr; i++) {
-                               struct refspec *spec = states.remote->push + i;
-                               printf("    %s%s%s%s\n",
-                                      spec->force ? "+" : "",
-                                      abbrev_branch(spec->src),
-                                      spec->dst ? ":" : "",
-                                      spec->dst ? abbrev_branch(spec->dst) : "");
-                       }
-               }
+       return result;
+}
+
+static int set_head(int argc, const char **argv)
+{
+       int i, opt_a = 0, opt_d = 0, result = 0;
+       struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT;
+       char *head_name = NULL;
+
+       struct option options[] = {
+               OPT_GROUP("set-head specific options"),
+               OPT_BOOLEAN('a', "auto", &opt_a,
+                           "set refs/remotes/<name>/HEAD according to remote"),
+               OPT_BOOLEAN('d', "delete", &opt_d,
+                           "delete refs/remotes/<name>/HEAD"),
+               OPT_END()
+       };
+       argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
+       if (argc)
+               strbuf_addf(&buf, "refs/remotes/%s/HEAD", argv[0]);
+
+       if (!opt_a && !opt_d && argc == 2) {
+               head_name = xstrdup(argv[1]);
+       } else if (opt_a && !opt_d && argc == 1) {
+               struct ref_states states;
+               memset(&states, 0, sizeof(states));
+               get_remote_ref_states(argv[0], &states, GET_HEAD_NAMES);
+               if (!states.heads.nr)
+                       result |= error("Cannot determine remote HEAD");
+               else if (states.heads.nr > 1) {
+                       result |= error("Multiple remote HEAD branches. "
+                                       "Please choose one explicitly with:");
+                       for (i = 0; i < states.heads.nr; i++)
+                               fprintf(stderr, "  git remote set-head %s %s\n",
+                                       argv[0], states.heads.items[i].string);
+               } else
+                       head_name = xstrdup(states.heads.items[0].string);
+               free_remote_ref_states(&states);
+       } else if (opt_d && !opt_a && argc == 1) {
+               if (delete_ref(buf.buf, NULL, REF_NODEREF))
+                       result |= error("Could not delete %s", buf.buf);
+       } else
+               usage_with_options(builtin_remote_usage, options);
 
-               /* NEEDSWORK: free remote */
-               string_list_clear(&states.new, 0);
-               string_list_clear(&states.stale, 0);
-               string_list_clear(&states.tracked, 0);
+       if (head_name) {
+               unsigned char sha1[20];
+               strbuf_addf(&buf2, "refs/remotes/%s/%s", argv[0], head_name);
+               /* make sure it's valid */
+               if (!resolve_ref(buf2.buf, sha1, 1, NULL))
+                       result |= error("Not a valid ref: %s", buf2.buf);
+               else if (create_symref(buf.buf, buf2.buf, "remote set-head"))
+                       result |= error("Could not setup %s", buf.buf);
+               if (opt_a)
+                       printf("%s/HEAD set to %s\n", argv[0], head_name);
+               free(head_name);
        }
 
+       strbuf_release(&buf);
+       strbuf_release(&buf2);
        return result;
 }
 
@@ -755,43 +1129,49 @@ static int prune(int argc, const char **argv)
                OPT__DRY_RUN(&dry_run),
                OPT_END()
        };
-       struct ref_states states;
 
        argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
 
        if (argc < 1)
                usage_with_options(builtin_remote_usage, options);
 
-       memset(&states, 0, sizeof(states));
-       for (; argc; argc--, argv++) {
-               int i;
+       for (; argc; argc--, argv++)
+               result |= prune_remote(*argv, dry_run);
 
-               get_remote_ref_states(*argv, &states, 1);
+       return result;
+}
 
-               if (states.stale.nr) {
-                       printf("Pruning %s\n", *argv);
-                       printf("URL: %s\n",
-                              states.remote->url_nr
-                              ? states.remote->url[0]
-                              : "(no URL)");
-               }
+static int prune_remote(const char *remote, int dry_run)
+{
+       int result = 0, i;
+       struct ref_states states;
+       const char *dangling_msg = dry_run
+               ? " %s will become dangling!\n"
+               : " %s has become dangling!\n";
 
-               for (i = 0; i < states.stale.nr; i++) {
-                       const char *refname = states.stale.items[i].util;
+       memset(&states, 0, sizeof(states));
+       get_remote_ref_states(remote, &states, GET_REF_STATES);
+
+       if (states.stale.nr) {
+               printf("Pruning %s\n", remote);
+               printf("URL: %s\n",
+                      states.remote->url_nr
+                      ? states.remote->url[0]
+                      : "(no URL)");
+       }
 
-                       if (!dry_run)
-                               result |= delete_ref(refname, NULL, 0);
+       for (i = 0; i < states.stale.nr; i++) {
+               const char *refname = states.stale.items[i].util;
 
-                       printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
-                              abbrev_ref(refname, "refs/remotes/"));
-               }
+               if (!dry_run)
+                       result |= delete_ref(refname, NULL, 0);
 
-               /* NEEDSWORK: free remote */
-               string_list_clear(&states.new, 0);
-               string_list_clear(&states.stale, 0);
-               string_list_clear(&states.tracked, 0);
+               printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
+                      abbrev_ref(refname, "refs/remotes/"));
+               warn_dangling_symref(dangling_msg, refname);
        }
 
+       free_remote_ref_states(&states);
        return result;
 }
 
@@ -808,16 +1188,18 @@ struct remote_group {
        struct string_list *list;
 } remote_group;
 
-static int get_remote_group(const char *key, const char *value, void *cb)
+static int get_remote_group(const char *key, const char *value, void *num_hits)
 {
        if (!prefixcmp(key, "remotes.") &&
                        !strcmp(key + 8, remote_group.name)) {
                /* split list by white space */
                int space = strcspn(value, " \t\n");
                while (*value) {
-                       if (space > 1)
+                       if (space > 1) {
                                string_list_append(xstrndup(value, space),
                                                remote_group.list);
+                               ++*((int *)num_hits);
+                       }
                        value += space + (value[space] != '\0');
                        space = strcspn(value, " \t\n");
                }
@@ -828,10 +1210,18 @@ static int get_remote_group(const char *key, const char *value, void *cb)
 
 static int update(int argc, const char **argv)
 {
-       int i, result = 0;
+       int i, result = 0, prune = 0;
        struct string_list list = { NULL, 0, 0, 0 };
        static const char *default_argv[] = { NULL, "default", NULL };
+       struct option options[] = {
+               OPT_GROUP("update specific options"),
+               OPT_BOOLEAN('p', "prune", &prune,
+                           "prune remotes after fetching"),
+               OPT_END()
+       };
 
+       argc = parse_options(argc, argv, options, builtin_remote_usage,
+                            PARSE_OPT_KEEP_ARGV0);
        if (argc < 2) {
                argc = 2;
                argv = default_argv;
@@ -839,15 +1229,28 @@ static int update(int argc, const char **argv)
 
        remote_group.list = &list;
        for (i = 1; i < argc; i++) {
+               int groups_found = 0;
                remote_group.name = argv[i];
-               result = git_config(get_remote_group, NULL);
+               result = git_config(get_remote_group, &groups_found);
+               if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) {
+                       struct remote *remote;
+                       if (!remote_is_configured(argv[i]))
+                               die("No such remote or remote group: %s",
+                                   argv[i]);
+                       remote = remote_get(argv[i]);
+                       string_list_append(remote->name, remote_group.list);
+               }
        }
 
        if (!result && !list.nr  && argc == 2 && !strcmp(argv[1], "default"))
                result = for_each_remote(get_one_remote_for_update, &list);
 
-       for (i = 0; i < list.nr; i++)
-               result |= fetch_remote(list.items[i].string);
+       for (i = 0; i < list.nr; i++) {
+               int err = fetch_remote(list.items[i].string);
+               result |= err;
+               if (!err && prune)
+                       result |= prune_remote(list.items[i].string, 0);
+       }
 
        /* all names were strdup()ed or strndup()ed */
        list.strdup_strings = 1;
@@ -914,6 +1317,8 @@ int cmd_remote(int argc, const char **argv, const char *prefix)
                result = mv(argc, argv);
        else if (!strcmp(argv[0], "rm"))
                result = rm(argc, argv);
+       else if (!strcmp(argv[0], "set-head"))
+               result = set_head(argc, argv);
        else if (!strcmp(argv[0], "show"))
                result = show(argc, argv);
        else if (!strcmp(argv[0], "prune"))
index d4dec6b7156b081bf63933d4de6d0b62ab2212c8..adfb7b5f48597c19c23235b74cf1a0c779985dc6 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "dir.h"
 #include "string-list.h"
 #include "rerere.h"
 #include "xdiff/xdiff.h"
@@ -12,28 +13,17 @@ static const char git_rerere_usage[] =
 static int cutoff_noresolve = 15;
 static int cutoff_resolve = 60;
 
-static const char *rr_path(const char *name, const char *file)
-{
-       return git_path("rr-cache/%s/%s", name, file);
-}
-
 static time_t rerere_created_at(const char *name)
 {
        struct stat st;
-       return stat(rr_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
-}
-
-static int has_resolution(const char *name)
-{
-       struct stat st;
-       return !stat(rr_path(name, "postimage"), &st);
+       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
 }
 
 static void unlink_rr_item(const char *name)
 {
-       unlink(rr_path(name, "thisimage"));
-       unlink(rr_path(name, "preimage"));
-       unlink(rr_path(name, "postimage"));
+       unlink(rerere_path(name, "thisimage"));
+       unlink(rerere_path(name, "preimage"));
+       unlink(rerere_path(name, "postimage"));
        rmdir(git_path("rr-cache/%s", name));
 }
 
@@ -59,17 +49,15 @@ static void garbage_collect(struct string_list *rr)
        git_config(git_rerere_gc_config, NULL);
        dir = opendir(git_path("rr-cache"));
        while ((e = readdir(dir))) {
-               const char *name = e->d_name;
-               if (name[0] == '.' &&
-                   (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')))
+               if (is_dot_or_dotdot(e->d_name))
                        continue;
-               then = rerere_created_at(name);
+               then = rerere_created_at(e->d_name);
                if (!then)
                        continue;
-               cutoff = (has_resolution(name)
+               cutoff = (has_rerere_resolution(e->d_name)
                          ? cutoff_resolve : cutoff_noresolve);
                if (then < now - cutoff * 86400)
-                       string_list_append(name, &to_remove);
+                       string_list_append(e->d_name, &to_remove);
        }
        for (i = 0; i < to_remove.nr; i++)
                unlink_rr_item(to_remove.items[i].string);
@@ -125,10 +113,10 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
        if (!strcmp(argv[1], "clear")) {
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *name = (const char *)merge_rr.items[i].util;
-                       if (!has_resolution(name))
+                       if (!has_rerere_resolution(name))
                                unlink_rr_item(name);
                }
-               unlink(git_path("rr-cache/MERGE_RR"));
+               unlink_or_warn(git_path("rr-cache/MERGE_RR"));
        } else if (!strcmp(argv[1], "gc"))
                garbage_collect(&merge_rr);
        else if (!strcmp(argv[1], "status"))
@@ -138,7 +126,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
                        const char *name = (const char *)merge_rr.items[i].util;
-                       diff_two(rr_path(name, "preimage"), path, path, path);
+                       diff_two(rerere_path(name, "preimage"), path, path, path);
                }
        else
                usage(git_rerere_usage);
index 9514b77f8c0b4e8a576e888df905b501e198df24..7e7ebabaa865ebd749502774638efe7b6d26bec1 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()
@@ -218,7 +228,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                }
                /*
                 * Otherwise, argv[i] could be either <rev> or <paths> and
-                * has to be unambigous.
+                * has to be unambiguous.
                 */
                else if (!get_sha1(argv[i], sha1)) {
                        /*
@@ -266,7 +276,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
                if (is_merge() || read_cache() < 0 || unmerged_cache())
                        die("Cannot do a soft reset in the middle of a merge.");
        }
-       else if (reset_index_file(sha1, (reset_type == HARD), quiet))
+       else if (reset_index_file(sha1, reset_type, quiet))
                die("Could not reset index file to revision '%s'.", rev);
 
        /* Any resets update HEAD to the head being switched to,
index 436afa45f5b7569551aa8301aee8a0752009a900..38a8f234de8120d15eaff9ee0d342e334ee006ca 100644 (file)
@@ -1,20 +1,12 @@
 #include "cache.h"
-#include "refs.h"
-#include "tag.h"
 #include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "tree-walk.h"
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
 #include "builtin.h"
 #include "log-tree.h"
 #include "graph.h"
-
-/* bits #0-15 in revision.h */
-
-#define COUNTED                (1u<<16)
+#include "bisect.h"
 
 static const char rev_list_usage[] =
 "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -50,73 +42,69 @@ static const char rev_list_usage[] =
 "    --bisect-all"
 ;
 
-static struct rev_info revs;
-
-static int bisect_list;
-static int show_timestamp;
-static int hdr_termination;
-static const char *header_prefix;
-
-static void finish_commit(struct commit *commit);
-static void show_commit(struct commit *commit)
+static void finish_commit(struct commit *commit, void *data);
+static void show_commit(struct commit *commit, void *data)
 {
-       graph_show_commit(revs.graph);
+       struct rev_list_info *info = data;
+       struct rev_info *revs = info->revs;
+
+       graph_show_commit(revs->graph);
 
-       if (show_timestamp)
+       if (info->show_timestamp)
                printf("%lu ", commit->date);
-       if (header_prefix)
-               fputs(header_prefix, stdout);
+       if (info->header_prefix)
+               fputs(info->header_prefix, stdout);
 
-       if (!revs.graph) {
+       if (!revs->graph) {
                if (commit->object.flags & BOUNDARY)
                        putchar('-');
                else if (commit->object.flags & UNINTERESTING)
                        putchar('^');
-               else if (revs.left_right) {
+               else if (revs->left_right) {
                        if (commit->object.flags & SYMMETRIC_LEFT)
                                putchar('<');
                        else
                                putchar('>');
                }
        }
-       if (revs.abbrev_commit && revs.abbrev)
-               fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
+       if (revs->abbrev_commit && revs->abbrev)
+               fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
                      stdout);
        else
                fputs(sha1_to_hex(commit->object.sha1), stdout);
-       if (revs.print_parents) {
+       if (revs->print_parents) {
                struct commit_list *parents = commit->parents;
                while (parents) {
                        printf(" %s", sha1_to_hex(parents->item->object.sha1));
                        parents = parents->next;
                }
        }
-       if (revs.children.name) {
+       if (revs->children.name) {
                struct commit_list *children;
 
-               children = lookup_decoration(&revs.children, &commit->object);
+               children = lookup_decoration(&revs->children, &commit->object);
                while (children) {
                        printf(" %s", sha1_to_hex(children->item->object.sha1));
                        children = children->next;
                }
        }
-       show_decorations(&revs, commit);
-       if (revs.commit_format == CMIT_FMT_ONELINE)
+       show_decorations(revs, commit);
+       if (revs->commit_format == CMIT_FMT_ONELINE)
                putchar(' ');
        else
                putchar('\n');
 
-       if (revs.verbose_header && commit->buffer) {
+       if (revs->verbose_header && commit->buffer) {
                struct strbuf buf = STRBUF_INIT;
-               pretty_print_commit(revs.commit_format, commit,
-                                   &buf, revs.abbrev, NULL, NULL,
-                                   revs.date_mode, 0);
-               if (revs.graph) {
+               pretty_print_commit(revs->commit_format, commit,
+                                   &buf, revs->abbrev, NULL, NULL,
+                                   revs->date_mode, 0);
+               if (revs->graph) {
                        if (buf.len) {
-                               if (revs.commit_format != CMIT_FMT_ONELINE)
-                                       graph_show_oneline(revs.graph);
+                               if (revs->commit_format != CMIT_FMT_ONELINE)
+                                       graph_show_oneline(revs->graph);
 
-                               graph_show_commit_msg(revs.graph, &buf);
+                               graph_show_commit_msg(revs->graph, &buf);
 
                                /*
                                 * Add a newline after the commit message.
@@ -134,7 +122,7 @@ static void show_commit(struct commit *commit)
                                 * format doesn't explicitly end in a newline.)
                                 */
                                if (buf.len && buf.buf[buf.len - 1] == '\n')
-                                       graph_show_padding(revs.graph);
+                                       graph_show_padding(revs->graph);
                                putchar('\n');
                        } else {
                                /*
@@ -142,23 +130,23 @@ static void show_commit(struct commit *commit)
                                 * the rest of the graph output for this
                                 * commit.
                                 */
-                               if (graph_show_remainder(revs.graph))
+                               if (graph_show_remainder(revs->graph))
                                        putchar('\n');
                        }
                } else {
                        if (buf.len)
-                               printf("%s%c", buf.buf, hdr_termination);
+                               printf("%s%c", buf.buf, info->hdr_termination);
                }
                strbuf_release(&buf);
        } else {
-               if (graph_show_remainder(revs.graph))
+               if (graph_show_remainder(revs->graph))
                        putchar('\n');
        }
        maybe_flush_or_die(stdout, "stdout");
-       finish_commit(commit);
+       finish_commit(commit, data);
 }
 
-static void finish_commit(struct commit *commit)
+static void finish_commit(struct commit *commit, void *data)
 {
        if (commit->parents) {
                free_commit_list(commit->parents);
@@ -168,27 +156,29 @@ static void finish_commit(struct commit *commit)
        commit->buffer = NULL;
 }
 
-static void finish_object(struct object_array_entry *p)
+static void finish_object(struct object *obj, const struct name_path *path, const char *name)
 {
-       if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
-               die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
+       if (obj->type == OBJ_BLOB && !has_sha1_file(obj->sha1))
+               die("missing blob object '%s'", sha1_to_hex(obj->sha1));
 }
 
-static void show_object(struct object_array_entry *p)
+static void show_object(struct object *obj, const struct name_path *path, const char *component)
 {
+       char *name = path_name(path, component);
        /* An object with name "foo\n0000000..." can be used to
         * confuse downstream "git pack-objects" very badly.
         */
-       const char *ep = strchr(p->name, '\n');
+       const char *ep = strchr(name, '\n');
 
-       finish_object(p);
+       finish_object(obj, path, name);
        if (ep) {
-               printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
-                      (int) (ep - p->name),
-                      p->name);
+               printf("%s %.*s\n", sha1_to_hex(obj->sha1),
+                      (int) (ep - name),
+                      name);
        }
        else
-               printf("%s %s\n", sha1_to_hex(p->item->sha1), p->name);
+               printf("%s %s\n", sha1_to_hex(obj->sha1), name);
+       free(name);
 }
 
 static void show_edge(struct commit *commit)
@@ -196,389 +186,122 @@ static void show_edge(struct commit *commit)
        printf("-%s\n", sha1_to_hex(commit->object.sha1));
 }
 
-/*
- * This is a truly stupid algorithm, but it's only
- * used for bisection, and we just don't care enough.
- *
- * We care just barely enough to avoid recursing for
- * non-merge entries.
- */
-static int count_distance(struct commit_list *entry)
-{
-       int nr = 0;
-
-       while (entry) {
-               struct commit *commit = entry->item;
-               struct commit_list *p;
-
-               if (commit->object.flags & (UNINTERESTING | COUNTED))
-                       break;
-               if (!(commit->object.flags & TREESAME))
-                       nr++;
-               commit->object.flags |= COUNTED;
-               p = commit->parents;
-               entry = p;
-               if (p) {
-                       p = p->next;
-                       while (p) {
-                               nr += count_distance(p);
-                               p = p->next;
-                       }
-               }
-       }
-
-       return nr;
-}
-
-static void clear_distance(struct commit_list *list)
+static inline int log2i(int n)
 {
-       while (list) {
-               struct commit *commit = list->item;
-               commit->object.flags &= ~COUNTED;
-               list = list->next;
-       }
-}
+       int log2 = 0;
 
-#define DEBUG_BISECT 0
+       for (; n > 1; n >>= 1)
+               log2++;
 
-static inline int weight(struct commit_list *elem)
-{
-       return *((int*)(elem->item->util));
+       return log2;
 }
 
-static inline void weight_set(struct commit_list *elem, int weight)
+static inline int exp2i(int n)
 {
-       *((int*)(elem->item->util)) = weight;
+       return 1 << n;
 }
 
-static int count_interesting_parents(struct commit *commit)
+/*
+ * Estimate the number of bisect steps left (after the current step)
+ *
+ * For any x between 0 included and 2^n excluded, the probability for
+ * n - 1 steps left looks like:
+ *
+ * P(2^n + x) == (2^n - x) / (2^n + x)
+ *
+ * and P(2^n + x) < 0.5 means 2^n < 3x
+ */
+static int estimate_bisect_steps(int all)
 {
-       struct commit_list *p;
-       int count;
+       int n, x, e;
 
-       for (count = 0, p = commit->parents; p; p = p->next) {
-               if (p->item->object.flags & UNINTERESTING)
-                       continue;
-               count++;
-       }
-       return count;
-}
-
-static inline int halfway(struct commit_list *p, int nr)
-{
-       /*
-        * Don't short-cut something we are not going to return!
-        */
-       if (p->item->object.flags & TREESAME)
+       if (all < 3)
                return 0;
-       if (DEBUG_BISECT)
-               return 0;
-       /*
-        * 2 and 3 are halfway of 5.
-        * 3 is halfway of 6 but 2 and 4 are not.
-        */
-       switch (2 * weight(p) - nr) {
-       case -1: case 0: case 1:
-               return 1;
-       default:
-               return 0;
-       }
-}
-
-#if !DEBUG_BISECT
-#define show_list(a,b,c,d) do { ; } while (0)
-#else
-static void show_list(const char *debug, int counted, int nr,
-                     struct commit_list *list)
-{
-       struct commit_list *p;
-
-       fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
-
-       for (p = list; p; p = p->next) {
-               struct commit_list *pp;
-               struct commit *commit = p->item;
-               unsigned flags = commit->object.flags;
-               enum object_type type;
-               unsigned long size;
-               char *buf = read_sha1_file(commit->object.sha1, &type, &size);
-               char *ep, *sp;
-
-               fprintf(stderr, "%c%c%c ",
-                       (flags & TREESAME) ? ' ' : 'T',
-                       (flags & UNINTERESTING) ? 'U' : ' ',
-                       (flags & COUNTED) ? 'C' : ' ');
-               if (commit->util)
-                       fprintf(stderr, "%3d", weight(p));
-               else
-                       fprintf(stderr, "---");
-               fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
-               for (pp = commit->parents; pp; pp = pp->next)
-                       fprintf(stderr, " %.*s", 8,
-                               sha1_to_hex(pp->item->object.sha1));
-
-               sp = strstr(buf, "\n\n");
-               if (sp) {
-                       sp += 2;
-                       for (ep = sp; *ep && *ep != '\n'; ep++)
-                               ;
-                       fprintf(stderr, " %.*s", (int)(ep - sp), sp);
-               }
-               fprintf(stderr, "\n");
-       }
-}
-#endif /* DEBUG_BISECT */
-
-static struct commit_list *best_bisection(struct commit_list *list, int nr)
-{
-       struct commit_list *p, *best;
-       int best_distance = -1;
-
-       best = list;
-       for (p = list; p; p = p->next) {
-               int distance;
-               unsigned flags = p->item->object.flags;
-
-               if (flags & TREESAME)
-                       continue;
-               distance = weight(p);
-               if (nr - distance < distance)
-                       distance = nr - distance;
-               if (distance > best_distance) {
-                       best = p;
-                       best_distance = distance;
-               }
-       }
-
-       return best;
-}
-
-struct commit_dist {
-       struct commit *commit;
-       int distance;
-};
 
-static int compare_commit_dist(const void *a_, const void *b_)
-{
-       struct commit_dist *a, *b;
+       n = log2i(all);
+       e = exp2i(n);
+       x = all - e;
 
-       a = (struct commit_dist *)a_;
-       b = (struct commit_dist *)b_;
-       if (a->distance != b->distance)
-               return b->distance - a->distance; /* desc sort */
-       return hashcmp(a->commit->object.sha1, b->commit->object.sha1);
+       return (e < 3 * x) ? n : n - 1;
 }
 
-static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
+static void show_tried_revs(struct commit_list *tried, int stringed)
 {
-       struct commit_list *p;
-       struct commit_dist *array = xcalloc(nr, sizeof(*array));
-       int cnt, i;
-
-       for (p = list, cnt = 0; p; p = p->next) {
-               int distance;
-               unsigned flags = p->item->object.flags;
-
-               if (flags & TREESAME)
-                       continue;
-               distance = weight(p);
-               if (nr - distance < distance)
-                       distance = nr - distance;
-               array[cnt].commit = p->item;
-               array[cnt].distance = distance;
-               cnt++;
-       }
-       qsort(array, cnt, sizeof(*array), compare_commit_dist);
-       for (p = list, i = 0; i < cnt; i++) {
-               struct name_decoration *r = xmalloc(sizeof(*r) + 100);
-               struct object *obj = &(array[i].commit->object);
-
-               sprintf(r->name, "dist=%d", array[i].distance);
-               r->next = add_decoration(&name_decoration, obj, r);
-               p->item = array[i].commit;
-               p = p->next;
+       printf("bisect_tried='");
+       for (;tried; tried = tried->next) {
+               char *format = tried->next ? "%s|" : "%s";
+               printf(format, sha1_to_hex(tried->item->object.sha1));
        }
-       if (p)
-               p->next = NULL;
-       free(array);
-       return list;
+       printf(stringed ? "' &&\n" : "'\n");
 }
 
-/*
- * zero or positive weight is the number of interesting commits it can
- * reach, including itself.  Especially, weight = 0 means it does not
- * reach any tree-changing commits (e.g. just above uninteresting one
- * but traversal is with pathspec).
- *
- * weight = -1 means it has one parent and its distance is yet to
- * be computed.
- *
- * weight = -2 means it has more than one parent and its distance is
- * unknown.  After running count_distance() first, they will get zero
- * or positive distance.
- */
-static struct commit_list *do_find_bisection(struct commit_list *list,
-                                            int nr, int *weights,
-                                            int find_all)
+int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
 {
-       int n, counted;
-       struct commit_list *p;
-
-       counted = 0;
-
-       for (n = 0, p = list; p; p = p->next) {
-               struct commit *commit = p->item;
-               unsigned flags = commit->object.flags;
-
-               p->item->util = &weights[n++];
-               switch (count_interesting_parents(commit)) {
-               case 0:
-                       if (!(flags & TREESAME)) {
-                               weight_set(p, 1);
-                               counted++;
-                               show_list("bisection 2 count one",
-                                         counted, nr, list);
-                       }
-                       /*
-                        * otherwise, it is known not to reach any
-                        * tree-changing commit and gets weight 0.
-                        */
-                       break;
-               case 1:
-                       weight_set(p, -1);
-                       break;
-               default:
-                       weight_set(p, -2);
-                       break;
-               }
-       }
+       int cnt, flags = info->bisect_show_flags;
+       char hex[41] = "", *format;
+       struct commit_list *tried;
+       struct rev_info *revs = info->revs;
 
-       show_list("bisection 2 initialize", counted, nr, list);
+       if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+               return 1;
+
+       revs->commits = filter_skipped(revs->commits, &tried, flags & BISECT_SHOW_ALL);
 
        /*
-        * If you have only one parent in the resulting set
-        * then you can reach one commit more than that parent
-        * can reach.  So we do not have to run the expensive
-        * count_distance() for single strand of pearls.
-        *
-        * However, if you have more than one parents, you cannot
-        * just add their distance and one for yourself, since
-        * they usually reach the same ancestor and you would
-        * end up counting them twice that way.
-        *
-        * So we will first count distance of merges the usual
-        * way, and then fill the blanks using cheaper algorithm.
+        * revs->commits can reach "reaches" commits among
+        * "all" commits.  If it is good, then there are
+        * (all-reaches) commits left to be bisected.
+        * On the other hand, if it is bad, then the set
+        * to bisect is "reaches".
+        * A bisect set of size N has (N-1) commits further
+        * to test, as we already know one bad one.
         */
-       for (p = list; p; p = p->next) {
-               if (p->item->object.flags & UNINTERESTING)
-                       continue;
-               if (weight(p) != -2)
-                       continue;
-               weight_set(p, count_distance(p));
-               clear_distance(list);
-
-               /* Does it happen to be at exactly half-way? */
-               if (!find_all && halfway(p, nr))
-                       return p;
-               counted++;
-       }
+       cnt = all - reaches;
+       if (cnt < reaches)
+               cnt = reaches;
 
-       show_list("bisection 2 count_distance", counted, nr, list);
+       if (revs->commits)
+               strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
 
-       while (counted < nr) {
-               for (p = list; p; p = p->next) {
-                       struct commit_list *q;
-                       unsigned flags = p->item->object.flags;
-
-                       if (0 <= weight(p))
-                               continue;
-                       for (q = p->item->parents; q; q = q->next) {
-                               if (q->item->object.flags & UNINTERESTING)
-                                       continue;
-                               if (0 <= weight(q))
-                                       break;
-                       }
-                       if (!q)
-                               continue;
-
-                       /*
-                        * weight for p is unknown but q is known.
-                        * add one for p itself if p is to be counted,
-                        * otherwise inherit it from q directly.
-                        */
-                       if (!(flags & TREESAME)) {
-                               weight_set(p, weight(q)+1);
-                               counted++;
-                               show_list("bisection 2 count one",
-                                         counted, nr, list);
-                       }
-                       else
-                               weight_set(p, weight(q));
-
-                       /* Does it happen to be at exactly half-way? */
-                       if (!find_all && halfway(p, nr))
-                               return p;
-               }
+       if (flags & BISECT_SHOW_ALL) {
+               traverse_commit_list(revs, show_commit, show_object, info);
+               printf("------\n");
        }
 
-       show_list("bisection 2 counted all", counted, nr, list);
-
-       if (!find_all)
-               return best_bisection(list, nr);
-       else
-               return best_bisection_sorted(list, nr);
-}
-
-static struct commit_list *find_bisection(struct commit_list *list,
-                                         int *reaches, int *all,
-                                         int find_all)
-{
-       int nr, on_list;
-       struct commit_list *p, *best, *next, *last;
-       int *weights;
-
-       show_list("bisection 2 entry", 0, 0, list);
+       if (flags & BISECT_SHOW_TRIED)
+               show_tried_revs(tried, flags & BISECT_SHOW_STRINGED);
+       format = (flags & BISECT_SHOW_STRINGED) ?
+               "bisect_rev=%s &&\n"
+               "bisect_nr=%d &&\n"
+               "bisect_good=%d &&\n"
+               "bisect_bad=%d &&\n"
+               "bisect_all=%d &&\n"
+               "bisect_steps=%d\n"
+               :
+               "bisect_rev=%s\n"
+               "bisect_nr=%d\n"
+               "bisect_good=%d\n"
+               "bisect_bad=%d\n"
+               "bisect_all=%d\n"
+               "bisect_steps=%d\n";
+       printf(format,
+              hex,
+              cnt - 1,
+              all - reaches - 1,
+              reaches - 1,
+              all,
+              estimate_bisect_steps(all));
 
-       /*
-        * Count the number of total and tree-changing items on the
-        * list, while reversing the list.
-        */
-       for (nr = on_list = 0, last = NULL, p = list;
-            p;
-            p = next) {
-               unsigned flags = p->item->object.flags;
-
-               next = p->next;
-               if (flags & UNINTERESTING)
-                       continue;
-               p->next = last;
-               last = p;
-               if (!(flags & TREESAME))
-                       nr++;
-               on_list++;
-       }
-       list = last;
-       show_list("bisection 2 sorted", 0, nr, list);
-
-       *all = nr;
-       weights = xcalloc(on_list, sizeof(*weights));
-
-       /* Do the real work of finding bisection commit. */
-       best = do_find_bisection(list, nr, weights, find_all);
-       if (best) {
-               if (!find_all)
-                       best->next = NULL;
-               *reaches = weight(best);
-       }
-       free(weights);
-       return best;
+       return 0;
 }
 
 int cmd_rev_list(int argc, const char **argv, const char *prefix)
 {
-       struct commit_list *list;
+       struct rev_info revs;
+       struct rev_list_info info;
        int i;
        int read_from_stdin = 0;
+       int bisect_list = 0;
        int bisect_show_vars = 0;
        int bisect_find_all = 0;
        int quiet = 0;
@@ -589,6 +312,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        revs.commit_format = CMIT_FMT_UNSPECIFIED;
        argc = setup_revisions(argc, argv, &revs, NULL);
 
+       memset(&info, 0, sizeof(info));
+       info.revs = &revs;
+
        quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
        for (i = 1 ; i < argc; i++) {
                const char *arg = argv[i];
@@ -598,7 +324,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp(arg, "--timestamp")) {
-                       show_timestamp = 1;
+                       info.show_timestamp = 1;
                        continue;
                }
                if (!strcmp(arg, "--bisect")) {
@@ -608,6 +334,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
                if (!strcmp(arg, "--bisect-all")) {
                        bisect_list = 1;
                        bisect_find_all = 1;
+                       info.bisect_show_flags = BISECT_SHOW_ALL;
                        revs.show_decorations = 1;
                        continue;
                }
@@ -627,19 +354,17 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
        }
        if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
                /* The command line has a --pretty  */
-               hdr_termination = '\n';
+               info.hdr_termination = '\n';
                if (revs.commit_format == CMIT_FMT_ONELINE)
-                       header_prefix = "";
+                       info.header_prefix = "";
                else
-                       header_prefix = "commit ";
+                       info.header_prefix = "commit ";
        }
        else if (revs.verbose_header)
                /* Only --header was specified */
                revs.commit_format = CMIT_FMT_RAW;
 
-       list = revs.commits;
-
-       if ((!list &&
+       if ((!revs.commits &&
             (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
              !revs.pending.nr)) ||
            revs.diff)
@@ -660,47 +385,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
 
                revs.commits = find_bisection(revs.commits, &reaches, &all,
                                              bisect_find_all);
-               if (bisect_show_vars) {
-                       int cnt;
-                       char hex[41];
-                       if (!revs.commits)
-                               return 1;
-                       /*
-                        * revs.commits can reach "reaches" commits among
-                        * "all" commits.  If it is good, then there are
-                        * (all-reaches) commits left to be bisected.
-                        * On the other hand, if it is bad, then the set
-                        * to bisect is "reaches".
-                        * A bisect set of size N has (N-1) commits further
-                        * to test, as we already know one bad one.
-                        */
-                       cnt = all - reaches;
-                       if (cnt < reaches)
-                               cnt = reaches;
-                       strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1));
-
-                       if (bisect_find_all) {
-                               traverse_commit_list(&revs, show_commit, show_object);
-                               printf("------\n");
-                       }
 
-                       printf("bisect_rev=%s\n"
-                              "bisect_nr=%d\n"
-                              "bisect_good=%d\n"
-                              "bisect_bad=%d\n"
-                              "bisect_all=%d\n",
-                              hex,
-                              cnt - 1,
-                              all - reaches - 1,
-                              reaches - 1,
-                              all);
-                       return 0;
-               }
+               if (bisect_show_vars)
+                       return show_bisect_vars(&info, reaches, all);
        }
 
        traverse_commit_list(&revs,
-               quiet ? finish_commit : show_commit,
-               quiet ? finish_object : show_object);
+                            quiet ? finish_commit : show_commit,
+                            quiet ? finish_object : show_object,
+                            &info);
 
        return 0;
 }
index 81d5a6ffc9ff1c149bfb68f976a9e66c307cae1d..22c6d6ad161f0de7dee88336a81a8f1f873c0bb0 100644 (file)
@@ -26,6 +26,8 @@ static int show_type = NORMAL;
 #define SHOW_SYMBOLIC_FULL 2
 static int symbolic;
 static int abbrev;
+static int abbrev_ref;
+static int abbrev_ref_strict;
 static int output_sq;
 
 /*
@@ -109,8 +111,8 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
                return;
        def = NULL;
 
-       if (symbolic && name) {
-               if (symbolic == SHOW_SYMBOLIC_FULL) {
+       if ((symbolic || abbrev_ref) && name) {
+               if (symbolic == SHOW_SYMBOLIC_FULL || abbrev_ref) {
                        unsigned char discard[20];
                        char *full;
 
@@ -125,6 +127,9 @@ static void show_rev(int type, const unsigned char *sha1, const char *name)
                                 */
                                break;
                        case 1: /* happy */
+                               if (abbrev_ref)
+                                       full = shorten_unambiguous_ref(full,
+                                               abbrev_ref_strict);
                                show_with_type(type, full);
                                break;
                        default: /* ambiguous */
@@ -506,6 +511,20 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                symbolic = SHOW_SYMBOLIC_FULL;
                                continue;
                        }
+                       if (!prefixcmp(arg, "--abbrev-ref") &&
+                           (!arg[12] || arg[12] == '=')) {
+                               abbrev_ref = 1;
+                               abbrev_ref_strict = warn_ambiguous_refs;
+                               if (arg[12] == '=') {
+                                       if (!strcmp(arg + 13, "strict"))
+                                               abbrev_ref_strict = 1;
+                                       else if (!strcmp(arg + 13, "loose"))
+                                               abbrev_ref_strict = 0;
+                                       else
+                                               die("unknown mode for %s", arg);
+                               }
+                               continue;
+                       }
                        if (!strcmp(arg, "--all")) {
                                for_each_ref(show_reference, NULL);
                                continue;
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 c11f45585825962e61bdee4bdd6df206f0a141c6..269d60890ac6732f05b835e88bdb60a10bb3d441 100644 (file)
@@ -59,8 +59,7 @@ static int check_local_mod(unsigned char *head, int index_only)
 
                if (lstat(ce->name, &st) < 0) {
                        if (errno != ENOENT)
-                               fprintf(stderr, "warning: '%s': %s",
-                                       ce->name, strerror(errno));
+                               warning("'%s': %s", ce->name, strerror(errno));
                        /* It already vanished from the working tree */
                        continue;
                }
index d65d01969252332eeee12b0419e4ba3a806952b1..473a3de40c4cc9aab5c1355d0602646234e7bc1c 100644 (file)
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "commit.h"
-#include "tag.h"
 #include "refs.h"
 #include "pkt-line.h"
 #include "run-command.h"
@@ -11,9 +10,7 @@ static const char send_pack_usage[] =
 "git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
 "  --all and explicit <ref> specification are mutually exclusive.";
 
-static struct send_pack_args args = {
-       /* .receivepack = */ "git-receive-pack",
-};
+static struct send_pack_args args;
 
 static int feed_object(const unsigned char *sha1, int fd, int negative)
 {
@@ -32,7 +29,7 @@ static int feed_object(const unsigned char *sha1, int fd, int negative)
 /*
  * Make a pack stream and spit it out into file descriptor fd
  */
-static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra)
+static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *extra, struct send_pack_args *args)
 {
        /*
         * The child becomes pack-objects --revs; we feed
@@ -46,12 +43,16 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
                "--stdout",
                NULL,
                NULL,
+               NULL,
        };
        struct child_process po;
        int i;
 
-       if (args.use_thin_pack)
-               argv[4] = "--thin";
+       i = 4;
+       if (args->use_thin_pack)
+               argv[i++] = "--thin";
+       if (args->use_ofs_delta)
+               argv[i++] = "--delta-base-offset";
        memset(&po, 0, sizeof(po));
        po.argv = argv;
        po.in = -1;
@@ -84,82 +85,6 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
        return 0;
 }
 
-static void unmark_and_free(struct commit_list *list, unsigned int mark)
-{
-       while (list) {
-               struct commit_list *temp = list;
-               temp->item->object.flags &= ~mark;
-               list = temp->next;
-               free(temp);
-       }
-}
-
-static int ref_newer(const unsigned char *new_sha1,
-                    const unsigned char *old_sha1)
-{
-       struct object *o;
-       struct commit *old, *new;
-       struct commit_list *list, *used;
-       int found = 0;
-
-       /* Both new and old must be commit-ish and new is descendant of
-        * old.  Otherwise we require --force.
-        */
-       o = deref_tag(parse_object(old_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       old = (struct commit *) o;
-
-       o = deref_tag(parse_object(new_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       new = (struct commit *) o;
-
-       if (parse_commit(new) < 0)
-               return 0;
-
-       used = list = NULL;
-       commit_list_insert(new, &list);
-       while (list) {
-               new = pop_most_recent_commit(&list, 1);
-               commit_list_insert(new, &used);
-               if (new == old) {
-                       found = 1;
-                       break;
-               }
-       }
-       unmark_and_free(list, 1);
-       unmark_and_free(used, 1);
-       return found;
-}
-
-static struct ref *local_refs, **local_tail;
-static struct ref *remote_refs, **remote_tail;
-
-static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct ref *ref;
-       int len;
-
-       /* we already know it starts with refs/ to get here */
-       if (check_ref_format(refname + 5))
-               return 0;
-
-       len = strlen(refname) + 1;
-       ref = xcalloc(1, sizeof(*ref) + len);
-       hashcpy(ref->new_sha1, sha1);
-       memcpy(ref->name, refname, len);
-       *local_tail = ref;
-       local_tail = &ref->next;
-       return 0;
-}
-
-static void get_local_heads(void)
-{
-       local_tail = &local_refs;
-       for_each_ref(one_local_ref, NULL);
-}
-
 static int receive_status(int in, struct ref *refs)
 {
        struct ref *hint;
@@ -247,16 +172,6 @@ static void update_tracking_ref(struct remote *remote, struct ref *ref)
        }
 }
 
-static const char *prettify_ref(const struct ref *ref)
-{
-       const char *name = ref->name;
-       return name + (
-               !prefixcmp(name, "refs/heads/") ? 11 :
-               !prefixcmp(name, "refs/tags/") ? 10 :
-               !prefixcmp(name, "refs/remotes/") ? 13 :
-               0);
-}
-
 #define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
 
 static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
@@ -385,47 +300,31 @@ static int refs_pushed(struct ref *ref)
        return 0;
 }
 
-static int do_send_pack(int in, int out, struct remote *remote, const char *dest, int nr_refspec, const char **refspec)
+int send_pack(struct send_pack_args *args,
+             int fd[], struct child_process *conn,
+             struct ref *remote_refs,
+             struct extra_have_objects *extra_have)
 {
+       int in = fd[0];
+       int out = fd[1];
        struct ref *ref;
        int new_refs;
        int ask_for_status_report = 0;
        int allow_deleting_refs = 0;
        int expect_status_report = 0;
-       int flags = MATCH_REFS_NONE;
        int ret;
-       struct extra_have_objects extra_have;
-
-       memset(&extra_have, 0, sizeof(extra_have));
-       if (args.send_all)
-               flags |= MATCH_REFS_ALL;
-       if (args.send_mirror)
-               flags |= MATCH_REFS_MIRROR;
-
-       /* No funny business with the matcher */
-       remote_tail = get_remote_heads(in, &remote_refs, 0, NULL, REF_NORMAL,
-                                      &extra_have);
-       get_local_heads();
 
        /* Does the other end support the reporting? */
        if (server_supports("report-status"))
                ask_for_status_report = 1;
        if (server_supports("delete-refs"))
                allow_deleting_refs = 1;
-
-       /* match them up */
-       if (!remote_tail)
-               remote_tail = &remote_refs;
-       if (match_refs(local_refs, remote_refs, &remote_tail,
-                      nr_refspec, refspec, flags)) {
-               close(out);
-               return -1;
-       }
+       if (server_supports("ofs-delta"))
+               args->use_ofs_delta = 1;
 
        if (!remote_refs) {
                fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
                        "Perhaps you should specify a branch such as 'master'.\n");
-               close(out);
                return 0;
        }
 
@@ -437,7 +336,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
 
                if (ref->peer_ref)
                        hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               else if (!args.send_mirror)
+               else if (!args->send_mirror)
                        continue;
 
                ref->deletion = is_null_sha1(ref->new_sha1);
@@ -476,7 +375,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
                    (!has_sha1_file(ref->old_sha1)
                      || !ref_newer(ref->new_sha1, ref->old_sha1));
 
-               if (ref->nonfastforward && !ref->force && !args.force_update) {
+               if (ref->nonfastforward && !ref->force && !args->force_update) {
                        ref->status = REF_STATUS_REJECT_NONFASTFORWARD;
                        continue;
                }
@@ -484,7 +383,7 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
                if (!ref->deletion)
                        new_refs++;
 
-               if (!args.dry_run) {
+               if (!args->dry_run) {
                        char *old_hex = sha1_to_hex(ref->old_sha1);
                        char *new_hex = sha1_to_hex(ref->new_sha1);
 
@@ -505,27 +404,19 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
        }
 
        packet_flush(out);
-       if (new_refs && !args.dry_run) {
-               if (pack_objects(out, remote_refs, &extra_have) < 0)
+       if (new_refs && !args->dry_run) {
+               if (pack_objects(out, remote_refs, extra_have, args) < 0) {
+                       for (ref = remote_refs; ref; ref = ref->next)
+                               ref->status = REF_STATUS_NONE;
                        return -1;
+               }
        }
-       else
-               close(out);
 
        if (expect_status_report)
                ret = receive_status(in, remote_refs);
        else
                ret = 0;
 
-       print_push_status(dest, remote_refs);
-
-       if (!args.dry_run && remote) {
-               for (ref = remote_refs; ref; ref = ref->next)
-                       update_tracking_ref(remote, ref);
-       }
-
-       if (!refs_pushed(remote_refs))
-               fprintf(stderr, "Everything up-to-date\n");
        if (ret < 0)
                return ret;
        for (ref = remote_refs; ref; ref = ref->next) {
@@ -574,11 +465,19 @@ static void verify_remote_names(int nr_heads, const char **heads)
 
 int cmd_send_pack(int argc, const char **argv, const char *prefix)
 {
-       int i, nr_heads = 0;
-       const char **heads = NULL;
+       int i, nr_refspecs = 0;
+       const char **refspecs = NULL;
        const char *remote_name = NULL;
        struct remote *remote = NULL;
        const char *dest = NULL;
+       int fd[2];
+       struct child_process *conn;
+       struct extra_have_objects extra_have;
+       struct ref *remote_refs, **remote_tail, *local_refs;
+       int ret;
+       int send_all = 0;
+       const char *receivepack = "git-receive-pack";
+       int flags;
 
        argv++;
        for (i = 1; i < argc; i++, argv++) {
@@ -586,11 +485,11 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
 
                if (*arg == '-') {
                        if (!prefixcmp(arg, "--receive-pack=")) {
-                               args.receivepack = arg + 15;
+                               receivepack = arg + 15;
                                continue;
                        }
                        if (!prefixcmp(arg, "--exec=")) {
-                               args.receivepack = arg + 7;
+                               receivepack = arg + 7;
                                continue;
                        }
                        if (!prefixcmp(arg, "--remote=")) {
@@ -598,7 +497,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                continue;
                        }
                        if (!strcmp(arg, "--all")) {
-                               args.send_all = 1;
+                               send_all = 1;
                                continue;
                        }
                        if (!strcmp(arg, "--dry-run")) {
@@ -627,8 +526,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                        dest = arg;
                        continue;
                }
-               heads = (const char **) argv;
-               nr_heads = argc - i;
+               refspecs = (const char **) argv;
+               nr_refspecs = argc - i;
                break;
        }
        if (!dest)
@@ -637,8 +536,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
         * --all and --mirror are incompatible; neither makes sense
         * with any refspecs.
         */
-       if ((heads && (args.send_all || args.send_mirror)) ||
-                                       (args.send_all && args.send_mirror))
+       if ((refspecs && (send_all || args.send_mirror)) ||
+           (send_all && args.send_mirror))
                usage(send_pack_usage);
 
        if (remote_name) {
@@ -649,24 +548,50 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                }
        }
 
-       return send_pack(&args, dest, remote, nr_heads, heads);
-}
+       conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0);
 
-int send_pack(struct send_pack_args *my_args,
-             const char *dest, struct remote *remote,
-             int nr_heads, const char **heads)
-{
-       int fd[2], ret;
-       struct child_process *conn;
+       memset(&extra_have, 0, sizeof(extra_have));
+
+       get_remote_heads(fd[0], &remote_refs, 0, NULL, REF_NORMAL,
+                        &extra_have);
 
-       memcpy(&args, my_args, sizeof(args));
+       verify_remote_names(nr_refspecs, refspecs);
 
-       verify_remote_names(nr_heads, heads);
+       local_refs = get_local_heads();
 
-       conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
-       ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
+       flags = MATCH_REFS_NONE;
+
+       if (send_all)
+               flags |= MATCH_REFS_ALL;
+       if (args.send_mirror)
+               flags |= MATCH_REFS_MIRROR;
+
+       /* match them up */
+       remote_tail = &remote_refs;
+       while (*remote_tail)
+               remote_tail = &((*remote_tail)->next);
+       if (match_refs(local_refs, remote_refs, &remote_tail,
+                      nr_refspecs, refspecs, flags)) {
+               return -1;
+       }
+
+       ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
+
+       close(fd[1]);
        close(fd[0]);
-       /* do_send_pack always closes fd[1] */
+
        ret |= finish_connect(conn);
-       return !!ret;
+
+       print_push_status(dest, remote_refs);
+
+       if (!args.dry_run && remote) {
+               struct ref *ref;
+               for (ref = remote_refs; ref; ref = ref->next)
+                       update_tracking_ref(remote, ref);
+       }
+
+       if (!ret && !refs_pushed(remote_refs))
+               fprintf(stderr, "Everything up-to-date\n");
+
+       return ret;
 }
index e49290687f2a0c55317398ef8b04ac319018c91b..b28091b4455db15e80841f2779ce0686d4f18826 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))
@@ -92,7 +101,6 @@ static void insert_one_record(struct shortlog *log,
        }
        while (*oneline && isspace(*oneline) && *oneline != '\n')
                oneline++;
-       len = eol - oneline;
        format_subject(&subject, oneline, " ");
        buffer = strbuf_detach(&subject, NULL);
 
@@ -107,16 +115,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 +231,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 +260,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 +326,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 306b850c720ecef7030bd7139f7c7d2758125ac4..c3afabbe914d699a8e0c80bdb5063ca51506167e 100644 (file)
@@ -365,8 +365,7 @@ static int append_ref(const char *refname, const unsigned char *sha1,
                                return 0;
        }
        if (MAX_REVS <= ref_name_cnt) {
-               fprintf(stderr, "warning: ignoring %s; "
-                       "cannot handle more than %d refs\n",
+               warning("ignoring %s; cannot handle more than %d refs",
                        refname, MAX_REVS);
                return 0;
        }
@@ -577,7 +576,7 @@ static void parse_reflog_param(const char *arg, int *cnt, const char **base)
        if (*ep == ',')
                *base = ep + 1;
        else if (*ep)
-               die("unrecognized reflog param '%s'", arg + 9);
+               die("unrecognized reflog param '%s'", arg);
        else
                *base = NULL;
        if (*cnt <= 0)
index 572b114119db15f5f42dd79e3bc15e6d219f71db..dc76c5090f2367426201b37c5b13c2a5cbf00de2 100644 (file)
@@ -140,7 +140,7 @@ static int exclude_existing(const char *match)
                                continue;
                }
                if (check_ref_format(ref)) {
-                       fprintf(stderr, "warning: ref '%s' ignored\n", ref);
+                       warning("ref '%s' ignored", ref);
                        continue;
                }
                if (!string_list_has_string(&existing_refs, ref)) {
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..e54443009420d98dc281285e997b32f2a263b996 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);
 
@@ -325,7 +338,7 @@ static void create_tag(const unsigned char *object, const char *tag,
                exit(128);
        }
        if (path) {
-               unlink(path);
+               unlink_or_warn(path);
                free(path);
        }
 }
@@ -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 0713bca778e7be18b58ec5d207dc7c8cd7e982ed..f88e7219367c309dbc3d3e7ce2db32666703a91d 100644 (file)
@@ -24,7 +24,7 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix)
         *      git archive --format-tar --prefix=basedir tree-ish
         */
        int i;
-       const char **nargv = xcalloc(sizeof(*nargv), argc + 2);
+       const char **nargv = xcalloc(sizeof(*nargv), argc + 3);
        char *basedir_arg;
        int nargc = 0;
 
@@ -36,6 +36,13 @@ int cmd_tar_tree(int argc, const char **argv, const char *prefix)
                argv++;
                argc--;
        }
+
+       /*
+        * Because it's just a compatibility wrapper, tar-tree supports only
+        * the old behaviour of reading attributes from the work tree.
+        */
+       nargv[nargc++] = "--worktree-attributes";
+
        switch (argc) {
        default:
                usage(tar_tree_usage);
index daca0f775e4b49f2576a7c444895146d05b7022c..92beaaf4b3ca5520a9af27bde7f15dda46d80197 100644 (file)
@@ -195,7 +195,7 @@ static int process_path(const char *path)
        struct stat st;
 
        len = strlen(path);
-       if (has_symlink_leading_path(len, path))
+       if (has_symlink_leading_path(path, len))
                return error("'%s' is beyond a symbolic link", path);
 
        /*
@@ -292,7 +292,7 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
        report("add '%s'", path);
  free_return:
        if (p < path || p > path + strlen(path))
-               free((char*)p);
+               free((char *)p);
 }
 
 static void read_index_info(int line_termination)
@@ -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);
@@ -509,7 +509,7 @@ static int do_unresolve(int ac, const char **av,
                const char *p = prefix_path(prefix, prefix_length, arg);
                err |= unresolve_one(p);
                if (p < arg || p > arg + strlen(arg))
-                       free((char*)p);
+                       free((char *)p);
        }
        return err;
 }
@@ -712,7 +712,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                if (set_executable_bit)
                        chmod_path(set_executable_bit, p);
                if (p < path || p > path + strlen(path))
-                       free((char*)p);
+                       free((char *)p);
        }
        if (read_from_stdin) {
                struct strbuf buf = STRBUF_INIT, nbuf = STRBUF_INIT;
index a9b02fa32f372a6810867c10560a20d58b5b2a91..0206b416cbf08ae4c1b0d753784fb0b61bac46a1 100644 (file)
@@ -35,7 +35,7 @@ static int run_upload_archive(int argc, const char **argv, const char *prefix)
        strcpy(buf, argv[1]); /* enter-repo smudges its argument */
 
        if (!enter_repo(buf, 0))
-               die("not a git archive");
+               die("'%s' does not appear to be a git repository", buf);
 
        /* put received options in sent_argv[] */
        sent_argc = 1;
index 25a29f11a4b9642c1bd367b81779d8f09ae04604..0ee0a9af60b0601fe0e6db98ec582e059f5e9064 100644 (file)
@@ -107,7 +107,7 @@ static int verify_one_pack(const char *path, int verbose)
        return err;
 }
 
-static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>...";
+static const char verify_pack_usage[] = "git verify-pack [-v] <pack>...";
 
 int cmd_verify_pack(int argc, const char **argv, const char *prefix)
 {
index 729a1593e61d87ad4596f07e7faedac81de64e81..7f7fda42f9b7ab272ff2b3fa4ad1984a79f11ce0 100644 (file)
@@ -55,7 +55,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
        close(gpg.in);
        ret = finish_command(&gpg);
 
-       unlink(path);
+       unlink_or_warn(path);
 
        return ret;
 }
index 1495cf6a20128ccffb981c3ac4d1da5469da1940..425ff8e89b361c34b3336cda58794682c66b57f3 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -25,6 +25,7 @@ extern int cmd_add(int argc, const char **argv, const char *prefix);
 extern int cmd_annotate(int argc, const char **argv, const char *prefix);
 extern int cmd_apply(int argc, const char **argv, const char *prefix);
 extern int cmd_archive(int argc, const char **argv, const char *prefix);
+extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);
 extern int cmd_blame(int argc, const char **argv, const char *prefix);
 extern int cmd_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_bundle(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
index 3d8f218a5f9e838b15e1d56113a4dd56904ee544..37bf35e636f0966662416f0693dbea5c1cc73311 100644 (file)
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "tree.h"
+#include "tree-walk.h"
 #include "cache-tree.h"
 
 #ifndef DEBUG
@@ -591,3 +592,36 @@ int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix)
 
        return 0;
 }
+
+static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+       int cnt;
+
+       hashcpy(it->sha1, tree->object.sha1);
+       init_tree_desc(&desc, tree->buffer, tree->size);
+       cnt = 0;
+       while (tree_entry(&desc, &entry)) {
+               if (!S_ISDIR(entry.mode))
+                       cnt++;
+               else {
+                       struct cache_tree_sub *sub;
+                       struct tree *subtree = lookup_tree(entry.sha1);
+                       if (!subtree->object.parsed)
+                               parse_tree(subtree);
+                       sub = cache_tree_sub(it, entry.path);
+                       sub->cache_tree = cache_tree();
+                       prime_cache_tree_rec(sub->cache_tree, subtree);
+                       cnt += sub->cache_tree->entry_count;
+               }
+       }
+       it->entry_count = cnt;
+}
+
+void prime_cache_tree(struct cache_tree **it, struct tree *tree)
+{
+       cache_tree_free(it);
+       *it = cache_tree();
+       prime_cache_tree_rec(*it, tree);
+}
index cf8b790874c4a4f5890b360c237ccdd4a5a03de4..e95883523633a51f833683e96af1738da2868238 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef CACHE_TREE_H
 #define CACHE_TREE_H
 
+#include "tree.h"
+
 struct cache_tree;
 struct cache_tree_sub {
        struct cache_tree *cache_tree;
@@ -33,4 +35,6 @@ int cache_tree_update(struct cache_tree *, struct cache_entry **, int, int, int)
 #define WRITE_TREE_PREFIX_ERROR (-3)
 
 int write_cache_as_tree(unsigned char *sha1, int missing_ok, const char *prefix);
+void prime_cache_tree(struct cache_tree **, struct tree *);
+
 #endif
diff --git a/cache.h b/cache.h
index 8bc6bf32505a0bdccee839971618a8b545832a42..b8503ad91c3b13ccaf87a6f596d13918f7ae114b 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -140,8 +140,8 @@ struct ondisk_cache_entry_extended {
 };
 
 struct cache_entry {
-       unsigned int ce_ctime;
-       unsigned int ce_mtime;
+       struct cache_time ce_ctime;
+       struct cache_time ce_mtime;
        unsigned int ce_dev;
        unsigned int ce_ino;
        unsigned int ce_mode;
@@ -282,7 +282,7 @@ struct index_state {
        struct cache_entry **cache;
        unsigned int cache_nr, cache_alloc, cache_changed;
        struct cache_tree *cache_tree;
-       time_t timestamp;
+       struct cache_time timestamp;
        void *alloc;
        unsigned name_hash_initialized : 1,
                 initialized : 1;
@@ -428,7 +428,7 @@ extern int read_index_preload(struct index_state *, const char **pathspec);
 extern int read_index_from(struct index_state *, const char *path);
 extern int is_index_unborn(struct index_state *);
 extern int read_index_unmerged(struct index_state *);
-extern int write_index(const struct index_state *, int newfd);
+extern int write_index(struct index_state *, int newfd);
 extern int discard_index(struct index_state *);
 extern int unmerged_index(const struct index_state *);
 extern int verify_path(const char *path);
@@ -443,6 +443,7 @@ extern int add_index_entry(struct index_state *, struct cache_entry *ce, int opt
 extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 extern void rename_index_entry_at(struct index_state *, int pos, const char *new_name);
 extern int remove_index_entry_at(struct index_state *, int pos);
+extern void remove_marked_cache_entries(struct index_state *istate);
 extern int remove_file_from_index(struct index_state *, const char *path);
 #define ADD_CACHE_VERBOSE 1
 #define ADD_CACHE_PRETEND 2
@@ -541,8 +542,24 @@ enum rebase_setup_type {
        AUTOREBASE_ALWAYS,
 };
 
+enum push_default_type {
+       PUSH_DEFAULT_UNSPECIFIED = -1,
+       PUSH_DEFAULT_NOTHING = 0,
+       PUSH_DEFAULT_MATCHING,
+       PUSH_DEFAULT_TRACKING,
+       PUSH_DEFAULT_CURRENT,
+};
+
 extern enum branch_track git_branch_track;
 extern enum rebase_setup_type autorebase;
+extern enum push_default_type push_default;
+
+enum object_creation_mode {
+       OBJECT_CREATION_USES_HARDLINKS = 0,
+       OBJECT_CREATION_USES_RENAMES = 1,
+};
+
+extern enum object_creation_mode object_creation_mode;
 
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
@@ -613,7 +630,8 @@ enum sharedrepo {
        PERM_EVERYBODY      = 0664,
 };
 int git_config_perm(const char *var, const char *value);
-int adjust_shared_perm(const char *path);
+int set_shared_perm(const char *path, int mode);
+#define adjust_shared_perm(path) set_shared_perm((path), 0)
 int safe_create_leading_directories(char *path);
 int safe_create_leading_directories_const(const char *path);
 char *enter_repo(char *path, int strict);
@@ -627,6 +645,7 @@ const char *make_nonrelative_path(const char *path);
 const char *make_relative_path(const char *abs, const char *base);
 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 +655,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 +687,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_branch_name(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 +714,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);
@@ -724,7 +742,13 @@ 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_leading_path(const char *name, int len);
+extern int has_symlink_or_noent_leading_path(const char *name, int len);
+extern int has_dirs_only_path(const char *name, int len, int prefix_len);
+extern void invalidate_lstat_cache(const char *name, int len);
+extern void clear_lstat_cache(void);
+extern void schedule_dir_for_removal(const char *name, int len);
+extern void remove_scheduled_dirs(void);
 
 extern struct alternate_object_database {
        struct alternate_object_database *next;
@@ -797,7 +821,7 @@ struct ref {
 #define REF_HEADS      (1u << 1)
 #define REF_TAGS       (1u << 2)
 
-extern struct ref *find_ref_by_name(struct ref *list, const char *name);
+extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 
 #define CONNECT_VERBOSE       (1u << 0)
 extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
@@ -822,7 +846,7 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
 
 extern void pack_report(void);
 extern int open_pack_index(struct packed_git *);
-extern unsigned charuse_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
+extern unsigned char *use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
 extern void close_pack_windows(struct packed_git *);
 extern void unuse_pack(struct pack_window **);
 extern void free_pack_by_name(const char *);
@@ -866,6 +890,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 *);
@@ -941,7 +966,6 @@ extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
 extern int ws_blank_line(const char *line, int len, unsigned ws_rule);
 
 /* ls-files */
-int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
 int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
 void overlay_tree_on_cache(const char *tree_name, const char *prefix);
 
diff --git a/color.c b/color.c
index fc0b72ad59b13e4bd86372e5e81b4f400c99d26e..62977f4808ae339fdfe797e16b4eb28dc6abb85d 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,8 +1,6 @@
 #include "cache.h"
 #include "color.h"
 
-#define COLOR_RESET "\033[m"
-
 int git_use_color_default = 0;
 
 static int parse_color(const char *name, int len)
@@ -40,30 +38,41 @@ static int parse_attr(const char *name, int len)
 }
 
 void color_parse(const char *value, const char *var, char *dst)
+{
+       color_parse_mem(value, strlen(value), var, dst);
+}
+
+void color_parse_mem(const char *value, int value_len, const char *var,
+               char *dst)
 {
        const char *ptr = value;
+       int len = value_len;
        int attr = -1;
        int fg = -2;
        int bg = -2;
 
-       if (!strcasecmp(value, "reset")) {
-               strcpy(dst, "\033[m");
+       if (!strncasecmp(value, "reset", len)) {
+               strcpy(dst, GIT_COLOR_RESET);
                return;
        }
 
        /* [fg [bg]] [attr] */
-       while (*ptr) {
+       while (len > 0) {
                const char *word = ptr;
-               int val, len = 0;
+               int val, wordlen = 0;
 
-               while (word[len] && !isspace(word[len]))
-                       len++;
+               while (len > 0 && !isspace(word[wordlen])) {
+                       wordlen++;
+                       len--;
+               }
 
-               ptr = word + len;
-               while (*ptr && isspace(*ptr))
+               ptr = word + wordlen;
+               while (len > 0 && isspace(*ptr)) {
                        ptr++;
+                       len--;
+               }
 
-               val = parse_color(word, len);
+               val = parse_color(word, wordlen);
                if (val >= -1) {
                        if (fg == -2) {
                                fg = val;
@@ -75,7 +84,7 @@ void color_parse(const char *value, const char *var, char *dst)
                        }
                        goto bad;
                }
-               val = parse_attr(word, len);
+               val = parse_attr(word, wordlen);
                if (val < 0 || attr != -1)
                        goto bad;
                attr = val;
@@ -115,7 +124,7 @@ void color_parse(const char *value, const char *var, char *dst)
        *dst = 0;
        return;
 bad:
-       die("bad config value '%s' for variable '%s'", value, var);
+       die("bad color value '%.*s' for variable '%s'", value_len, value, var);
 }
 
 int git_config_colorbool(const char *var, const char *value, int stdout_is_tty)
@@ -164,7 +173,7 @@ static int color_vfprintf(FILE *fp, const char *color, const char *fmt,
                r += fprintf(fp, "%s", color);
        r += vfprintf(fp, fmt, args);
        if (*color)
-               r += fprintf(fp, "%s", COLOR_RESET);
+               r += fprintf(fp, "%s", GIT_COLOR_RESET);
        if (trail)
                r += fprintf(fp, "%s", trail);
        return r;
@@ -191,3 +200,31 @@ int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
        va_end(args);
        return r;
 }
+
+/*
+ * This function splits the buffer by newlines and colors the lines individually.
+ *
+ * Returns 0 on success.
+ */
+int color_fwrite_lines(FILE *fp, const char *color,
+               size_t count, const char *buf)
+{
+       if (!*color)
+               return fwrite(buf, count, 1, fp) != 1;
+       while (count) {
+               char *p = memchr(buf, '\n', count);
+               if (p != buf && (fputs(color, fp) < 0 ||
+                               fwrite(buf, p ? p - buf : count, 1, fp) != 1 ||
+                               fputs(GIT_COLOR_RESET, fp) < 0))
+                       return -1;
+               if (!p)
+                       return 0;
+               if (fputc('\n', fp) < 0)
+                       return -1;
+               count -= p + 1 - buf;
+               buf = p + 1;
+       }
+       return 0;
+}
+
+
diff --git a/color.h b/color.h
index 6cf5c88aaf8d0e38e2853e6fd212e3cdd6c180cb..18abeb7c7dd47794d4f5fea4049b5719f03383fe 100644 (file)
--- a/color.h
+++ b/color.h
@@ -4,6 +4,17 @@
 /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
 #define COLOR_MAXLEN 24
 
+#define GIT_COLOR_NORMAL       ""
+#define GIT_COLOR_RESET                "\033[m"
+#define GIT_COLOR_BOLD         "\033[1m"
+#define GIT_COLOR_RED          "\033[31m"
+#define GIT_COLOR_GREEN                "\033[32m"
+#define GIT_COLOR_YELLOW       "\033[33m"
+#define GIT_COLOR_BLUE         "\033[34m"
+#define GIT_COLOR_MAGENTA      "\033[35m"
+#define GIT_COLOR_CYAN         "\033[36m"
+#define GIT_COLOR_BG_RED       "\033[41m"
+
 /*
  * This variable stores the value of color.ui
  */
@@ -16,8 +27,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 0b071b6e256fa655b51f9e484f0633a7f055dd25..60d03676bbd0dba7be79c7098d7cae553962ca98 100644 (file)
@@ -24,7 +24,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
                        path = q->queue[i]->two->path;
                        len = strlen(path);
                        p = xmalloc(combine_diff_path_size(num_parent, len));
-                       p->path = (char*) &(p->parent[num_parent]);
+                       p->path = (char *) &(p->parent[num_parent]);
                        memcpy(p->path, path, len);
                        p->path[len] = 0;
                        p->len = len;
@@ -534,7 +534,6 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                return; /* result deleted */
 
        while (1) {
-               struct sline *sl = &sline[lno];
                unsigned long hunk_end;
                unsigned long rlines;
                const char *hunk_comment = NULL;
@@ -600,7 +599,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                        struct lline *ll;
                        int j;
                        unsigned long p_mask;
-                       sl = &sline[lno++];
+                       struct sline *sl = &sline[lno++];
                        ll = (sl->flag & no_pre_delete) ? NULL : sl->lost_head;
                        while (ll) {
                                fputs(c_old, stdout);
@@ -1064,7 +1063,7 @@ void diff_tree_combined_merge(const unsigned char *sha1,
        for (parents = commit->parents, num_parent = 0;
             parents;
             parents = parents->next, num_parent++)
-               hashcpy((unsigned char*)(parent + num_parent),
+               hashcpy((unsigned char *)(parent + num_parent),
                        parents->item->object.sha1);
        diff_tree_combined(sha1, parent, num_parent, dense, rev);
 }
index 3583a33ee90647d8e6ded02643eb75753760d94f..fb03a2ebb5d51f46d00fad3b3f6b1794d4fdad2b 100644 (file)
@@ -33,6 +33,7 @@ git-diff                                mainporcelain common
 git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
 git-diff-tree                           plumbinginterrogators
+git-difftool                            ancillaryinterrogators
 git-fast-export                                ancillarymanipulators
 git-fast-import                                ancillarymanipulators
 git-fetch                               mainporcelain common
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 ebac1483929c798905e6558e0013e3de3d6abeb2..b4a51b958c5651d3b509013aaef0c4e30b68a816 100644 (file)
@@ -89,10 +89,10 @@ static int cygwin_stat(const char *path, struct stat *buf)
 /*
  * At start up, we are trying to determine whether Win32 API or cygwin stat
  * functions should be used. The choice is determined by core.ignorecygwinfstricks.
- * Reading this option is not always possible immediately as git_dir may be
+ * Reading this option is not always possible immediately as git_dir may
  * not be set yet. So until it is set, use cygwin lstat/stat functions.
  * However, if core.filemode is set, we must use the Cygwin posix
- * stat/lstat as the Windows stat fuctions do not determine posix filemode.
+ * stat/lstat as the Windows stat functions do not determine posix filemode.
  *
  * Note that git_cygwin_config() does NOT call git_default_config() and this
  * is deliberate.  Many commands read from config to establish initial
index 1f4ead5f981688ee9e29ae2ee281e3904c9131f6..14feac7fe179069908df6dbc084b2adcc78c9242 100644 (file)
@@ -39,7 +39,7 @@
 # include <stdlib.h>
 #endif
 
-/* For platform which support the ISO C amendement 1 functionality we
+/* For platforms which support the ISO C amendment 1 functionality we
    support user defined character classes.  */
 #if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
 /* Solaris 2.5 has a bug: <wchar.h> must be included before <wctype.h>.  */
@@ -90,7 +90,7 @@
 
 # if defined _LIBC || (defined HAVE_WCTYPE_H && defined HAVE_WCHAR_H)
 /* The GNU C library provides support for user-defined character classes
-   and the functions from ISO C amendement 1.  */
+   and the functions from ISO C amendment 1.  */
 #  ifdef CHARCLASS_NAME_MAX
 #   define CHAR_CLASS_MAX_LENGTH CHARCLASS_NAME_MAX
 #  else
index cd0d8773641f2fdc808d8b246a8dd2bcd0e5814d..56bcb4277f47c295993c853a84edfe45e6aa3911 100644 (file)
@@ -5,6 +5,8 @@ void *gitmemmem(const void *haystack, size_t haystack_len,
 {
        const char *begin = haystack;
        const char *last_possible = begin + haystack_len - needle_len;
+       const char *tail = needle;
+       char point;
 
        /*
         * The first occurrence of the empty string is deemed to occur at
@@ -20,8 +22,9 @@ void *gitmemmem(const void *haystack, size_t haystack_len,
        if (haystack_len < needle_len)
                return NULL;
 
+       point = *tail++;
        for (; begin <= last_possible; begin++) {
-               if (!memcmp(begin, needle, needle_len))
+               if (*begin == point && !memcmp(begin + 1, tail, needle_len - 1))
                        return (void *)begin;
        }
 
index 3dbe6a77ffa4675b19e7183fd49e11212cb2cda0..cdeda1d9859fd545950e0c39ee7de3c9dc09bb07 100644 (file)
@@ -4,6 +4,119 @@
 
 unsigned int _CRT_fmode = _O_BINARY;
 
+static int err_win_to_posix(DWORD winerr)
+{
+       int error = ENOSYS;
+       switch(winerr) {
+       case ERROR_ACCESS_DENIED: error = EACCES; break;
+       case ERROR_ACCOUNT_DISABLED: error = EACCES; break;
+       case ERROR_ACCOUNT_RESTRICTION: error = EACCES; break;
+       case ERROR_ALREADY_ASSIGNED: error = EBUSY; break;
+       case ERROR_ALREADY_EXISTS: error = EEXIST; break;
+       case ERROR_ARITHMETIC_OVERFLOW: error = ERANGE; break;
+       case ERROR_BAD_COMMAND: error = EIO; break;
+       case ERROR_BAD_DEVICE: error = ENODEV; break;
+       case ERROR_BAD_DRIVER_LEVEL: error = ENXIO; break;
+       case ERROR_BAD_EXE_FORMAT: error = ENOEXEC; break;
+       case ERROR_BAD_FORMAT: error = ENOEXEC; break;
+       case ERROR_BAD_LENGTH: error = EINVAL; break;
+       case ERROR_BAD_PATHNAME: error = ENOENT; break;
+       case ERROR_BAD_PIPE: error = EPIPE; break;
+       case ERROR_BAD_UNIT: error = ENODEV; break;
+       case ERROR_BAD_USERNAME: error = EINVAL; break;
+       case ERROR_BROKEN_PIPE: error = EPIPE; break;
+       case ERROR_BUFFER_OVERFLOW: error = ENAMETOOLONG; break;
+       case ERROR_BUSY: error = EBUSY; break;
+       case ERROR_BUSY_DRIVE: error = EBUSY; break;
+       case ERROR_CALL_NOT_IMPLEMENTED: error = ENOSYS; break;
+       case ERROR_CANNOT_MAKE: error = EACCES; break;
+       case ERROR_CANTOPEN: error = EIO; break;
+       case ERROR_CANTREAD: error = EIO; break;
+       case ERROR_CANTWRITE: error = EIO; break;
+       case ERROR_CRC: error = EIO; break;
+       case ERROR_CURRENT_DIRECTORY: error = EACCES; break;
+       case ERROR_DEVICE_IN_USE: error = EBUSY; break;
+       case ERROR_DEV_NOT_EXIST: error = ENODEV; break;
+       case ERROR_DIRECTORY: error = EINVAL; break;
+       case ERROR_DIR_NOT_EMPTY: error = ENOTEMPTY; break;
+       case ERROR_DISK_CHANGE: error = EIO; break;
+       case ERROR_DISK_FULL: error = ENOSPC; break;
+       case ERROR_DRIVE_LOCKED: error = EBUSY; break;
+       case ERROR_ENVVAR_NOT_FOUND: error = EINVAL; break;
+       case ERROR_EXE_MARKED_INVALID: error = ENOEXEC; break;
+       case ERROR_FILENAME_EXCED_RANGE: error = ENAMETOOLONG; break;
+       case ERROR_FILE_EXISTS: error = EEXIST; break;
+       case ERROR_FILE_INVALID: error = ENODEV; break;
+       case ERROR_FILE_NOT_FOUND: error = ENOENT; break;
+       case ERROR_GEN_FAILURE: error = EIO; break;
+       case ERROR_HANDLE_DISK_FULL: error = ENOSPC; break;
+       case ERROR_INSUFFICIENT_BUFFER: error = ENOMEM; break;
+       case ERROR_INVALID_ACCESS: error = EACCES; break;
+       case ERROR_INVALID_ADDRESS: error = EFAULT; break;
+       case ERROR_INVALID_BLOCK: error = EFAULT; break;
+       case ERROR_INVALID_DATA: error = EINVAL; break;
+       case ERROR_INVALID_DRIVE: error = ENODEV; break;
+       case ERROR_INVALID_EXE_SIGNATURE: error = ENOEXEC; break;
+       case ERROR_INVALID_FLAGS: error = EINVAL; break;
+       case ERROR_INVALID_FUNCTION: error = ENOSYS; break;
+       case ERROR_INVALID_HANDLE: error = EBADF; break;
+       case ERROR_INVALID_LOGON_HOURS: error = EACCES; break;
+       case ERROR_INVALID_NAME: error = EINVAL; break;
+       case ERROR_INVALID_OWNER: error = EINVAL; break;
+       case ERROR_INVALID_PARAMETER: error = EINVAL; break;
+       case ERROR_INVALID_PASSWORD: error = EPERM; break;
+       case ERROR_INVALID_PRIMARY_GROUP: error = EINVAL; break;
+       case ERROR_INVALID_SIGNAL_NUMBER: error = EINVAL; break;
+       case ERROR_INVALID_TARGET_HANDLE: error = EIO; break;
+       case ERROR_INVALID_WORKSTATION: error = EACCES; break;
+       case ERROR_IO_DEVICE: error = EIO; break;
+       case ERROR_IO_INCOMPLETE: error = EINTR; break;
+       case ERROR_LOCKED: error = EBUSY; break;
+       case ERROR_LOCK_VIOLATION: error = EACCES; break;
+       case ERROR_LOGON_FAILURE: error = EACCES; break;
+       case ERROR_MAPPED_ALIGNMENT: error = EINVAL; break;
+       case ERROR_META_EXPANSION_TOO_LONG: error = E2BIG; break;
+       case ERROR_MORE_DATA: error = EPIPE; break;
+       case ERROR_NEGATIVE_SEEK: error = ESPIPE; break;
+       case ERROR_NOACCESS: error = EFAULT; break;
+       case ERROR_NONE_MAPPED: error = EINVAL; break;
+       case ERROR_NOT_ENOUGH_MEMORY: error = ENOMEM; break;
+       case ERROR_NOT_READY: error = EAGAIN; break;
+       case ERROR_NOT_SAME_DEVICE: error = EXDEV; break;
+       case ERROR_NO_DATA: error = EPIPE; break;
+       case ERROR_NO_MORE_SEARCH_HANDLES: error = EIO; break;
+       case ERROR_NO_PROC_SLOTS: error = EAGAIN; break;
+       case ERROR_NO_SUCH_PRIVILEGE: error = EACCES; break;
+       case ERROR_OPEN_FAILED: error = EIO; break;
+       case ERROR_OPEN_FILES: error = EBUSY; break;
+       case ERROR_OPERATION_ABORTED: error = EINTR; break;
+       case ERROR_OUTOFMEMORY: error = ENOMEM; break;
+       case ERROR_PASSWORD_EXPIRED: error = EACCES; break;
+       case ERROR_PATH_BUSY: error = EBUSY; break;
+       case ERROR_PATH_NOT_FOUND: error = ENOENT; break;
+       case ERROR_PIPE_BUSY: error = EBUSY; break;
+       case ERROR_PIPE_CONNECTED: error = EPIPE; break;
+       case ERROR_PIPE_LISTENING: error = EPIPE; break;
+       case ERROR_PIPE_NOT_CONNECTED: error = EPIPE; break;
+       case ERROR_PRIVILEGE_NOT_HELD: error = EACCES; break;
+       case ERROR_READ_FAULT: error = EIO; break;
+       case ERROR_SEEK: error = EIO; break;
+       case ERROR_SEEK_ON_DEVICE: error = ESPIPE; break;
+       case ERROR_SHARING_BUFFER_EXCEEDED: error = ENFILE; break;
+       case ERROR_SHARING_VIOLATION: error = EACCES; break;
+       case ERROR_STACK_OVERFLOW: error = ENOMEM; break;
+       case ERROR_SWAPERROR: error = ENOENT; break;
+       case ERROR_TOO_MANY_MODULES: error = EMFILE; break;
+       case ERROR_TOO_MANY_OPEN_FILES: error = EMFILE; break;
+       case ERROR_UNRECOGNIZED_MEDIA: error = ENXIO; break;
+       case ERROR_UNRECOGNIZED_VOLUME: error = ENODEV; break;
+       case ERROR_WAIT_NO_CHILDREN: error = ECHILD; break;
+       case ERROR_WRITE_FAULT: error = EIO; break;
+       case ERROR_WRITE_PROTECT: error = EROFS; break;
+       }
+       return error;
+}
+
 #undef open
 int mingw_open (const char *filename, int oflags, ...)
 {
@@ -46,7 +159,8 @@ static int do_lstat(const char *file_name, struct stat *buf)
                buf->st_uid = 0;
                buf->st_nlink = 1;
                buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_size = fdata.nFileSizeLow |
+                       (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
@@ -101,7 +215,7 @@ int mingw_fstat(int fd, struct stat *buf)
        }
        /* direct non-file handles to MS's fstat() */
        if (GetFileType(fh) != FILE_TYPE_DISK)
-               return fstat(fd, buf);
+               return _fstati64(fd, buf);
 
        if (GetFileInformationByHandle(fh, &fdata)) {
                buf->st_ino = 0;
@@ -109,7 +223,8 @@ int mingw_fstat(int fd, struct stat *buf)
                buf->st_uid = 0;
                buf->st_nlink = 1;
                buf->st_mode = file_attr_to_st_mode(fdata.dwFileAttributes);
-               buf->st_size = fdata.nFileSizeLow; /* Can't use nFileSizeHigh, since it's not a stat64 */
+               buf->st_size = fdata.nFileSizeLow |
+                       (((off_t)fdata.nFileSizeHigh)<<32);
                buf->st_dev = buf->st_rdev = 0; /* not used by Git */
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
@@ -281,7 +396,7 @@ repeat:
                 * its own input data to become available. But since
                 * the process (pack-objects) is itself CPU intensive,
                 * it will happily pick up the time slice that we are
-                * relinguishing here.
+                * relinquishing here.
                 */
                Sleep(0);
                goto repeat;
@@ -342,7 +457,7 @@ static const char *quote_arg(const char *arg)
        const char *p = arg;
        if (!*p) force_quotes = 1;
        while (*p) {
-               if (isspace(*p) || *p == '*' || *p == '?' || *p == '{')
+               if (isspace(*p) || *p == '*' || *p == '?' || *p == '{' || *p == '\'')
                        force_quotes = 1;
                else if (*p == '"')
                        n++;
@@ -447,7 +562,7 @@ static char **get_path_split(void)
        if (!n)
                return NULL;
 
-       path = xmalloc((n+1)*sizeof(char*));
+       path = xmalloc((n+1)*sizeof(char *));
        p = envpath;
        i = 0;
        do {
@@ -819,7 +934,9 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz)
 #undef rename
 int mingw_rename(const char *pold, const char *pnew)
 {
-       DWORD attrs;
+       DWORD attrs, gle;
+       int tries = 0;
+       static const int delay[] = { 0, 1, 10, 20, 40 };
 
        /*
         * Try native rename() first to get errno right.
@@ -829,10 +946,12 @@ int mingw_rename(const char *pold, const char *pnew)
                return 0;
        if (errno != EEXIST)
                return -1;
+repeat:
        if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
                return 0;
        /* TODO: translate more errors */
-       if (GetLastError() == ERROR_ACCESS_DENIED &&
+       gle = GetLastError();
+       if (gle == ERROR_ACCESS_DENIED &&
            (attrs = GetFileAttributes(pnew)) != INVALID_FILE_ATTRIBUTES) {
                if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
                        errno = EISDIR;
@@ -842,10 +961,23 @@ int mingw_rename(const char *pold, const char *pnew)
                    SetFileAttributes(pnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
                        if (MoveFileEx(pold, pnew, MOVEFILE_REPLACE_EXISTING))
                                return 0;
+                       gle = GetLastError();
                        /* revert file attributes on failure */
                        SetFileAttributes(pnew, attrs);
                }
        }
+       if (tries < ARRAY_SIZE(delay) && gle == ERROR_ACCESS_DENIED) {
+               /*
+                * We assume that some other process had the source or
+                * destination file open at the wrong moment and retry.
+                * In order to give the other process a higher chance to
+                * complete its operation, we give up our time slice now.
+                * If we have to retry again, we do sleep a bit.
+                */
+               Sleep(delay[tries]);
+               tries++;
+               goto repeat;
+       }
        errno = EACCES;
        return -1;
 }
@@ -1003,3 +1135,24 @@ void mingw_open_html(const char *unixpath)
        printf("Launching default browser to display HTML ...\n");
        ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
 }
+
+int link(const char *oldpath, const char *newpath)
+{
+       typedef BOOL WINAPI (*T)(const char*, const char*, LPSECURITY_ATTRIBUTES);
+       static T create_hard_link = NULL;
+       if (!create_hard_link) {
+               create_hard_link = (T) GetProcAddress(
+                       GetModuleHandle("kernel32.dll"), "CreateHardLinkA");
+               if (!create_hard_link)
+                       create_hard_link = (T)-1;
+       }
+       if (create_hard_link == (T)-1) {
+               errno = ENOSYS;
+               return -1;
+       }
+       if (!create_hard_link(newpath, oldpath, NULL)) {
+               errno = err_win_to_posix(GetLastError());
+               return -1;
+       }
+       return 0;
+}
index 4f275cb8e6a67515292a9dfc60bd1343065067a9..762eb143a7654480d8831a6258a0767c0f33900b 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
@@ -67,8 +67,6 @@ static inline int readlink(const char *path, char *buf, size_t bufsiz)
 { errno = ENOSYS; return -1; }
 static inline int symlink(const char *oldpath, const char *newpath)
 { errno = ENOSYS; return -1; }
-static inline int link(const char *oldpath, const char *newpath)
-{ errno = ENOSYS; return -1; }
 static inline int fchmod(int fildes, mode_t mode)
 { errno = ENOSYS; return -1; }
 static inline int fork(void)
@@ -134,6 +132,7 @@ int getpagesize(void);      /* defined in MinGW's libgcc.a */
 struct passwd *getpwuid(int uid);
 int setitimer(int type, struct itimerval *in, struct itimerval *out);
 int sigaction(int sig, struct sigaction *in, struct sigaction *out);
+int link(const char *oldpath, const char *newpath);
 
 /*
  * replacements of existing functions
@@ -160,14 +159,22 @@ int mingw_connect(int sockfd, struct sockaddr *sa, size_t sz);
 int mingw_rename(const char*, const char*);
 #define rename mingw_rename
 
+#ifdef USE_WIN32_MMAP
+int mingw_getpagesize(void);
+#define getpagesize mingw_getpagesize
+#endif
+
 /* Use mingw_lstat() instead of lstat()/stat() and
  * mingw_fstat() instead of fstat() on Windows.
  */
+#define off_t off64_t
+#define stat _stati64
+#define lseek _lseeki64
 int mingw_lstat(const char *file_name, struct stat *buf);
 int mingw_fstat(int fd, struct stat *buf);
 #define fstat mingw_fstat
 #define lstat mingw_lstat
-#define stat(x,y) mingw_lstat(x,y)
+#define _stati64(x,y) mingw_lstat(x,y)
 
 int mingw_utime(const char *file_name, const struct utimbuf *times);
 #define utime mingw_utime
index 87b33e46697b9a5dd9a9e8391ca3607f7e2ff569..5ea007567d15c229685f571628d4afcce6783e8d 100644 (file)
@@ -1043,7 +1043,7 @@ regex_compile (pattern, size, syntax, bufp)
      they can be reliably used as array indices.  */
   register unsigned char c, c1;
 
-  /* A random tempory spot in PATTERN.  */
+  /* A random temporary spot in PATTERN.  */
   const char *p1;
 
   /* Points to the end of the buffer, where we should append.  */
@@ -1796,7 +1796,7 @@ regex_compile (pattern, size, syntax, bufp)
                    we're all done, the pattern will look like:
                      set_number_at <jump count> <upper bound>
                      set_number_at <succeed_n count> <lower bound>
-                     succeed_n <after jump addr> <succed_n count>
+                     succeed_n <after jump addr> <succeed_n count>
                      <body of loop>
                      jump_n <succeed_n addr> <jump count>
                    (The upper bound and `jump_n' are omitted if
diff --git a/compat/win32mmap.c b/compat/win32mmap.c
new file mode 100644 (file)
index 0000000..779d796
--- /dev/null
@@ -0,0 +1,53 @@
+#include "../git-compat-util.h"
+
+/*
+ * Note that this doesn't return the actual pagesize, but
+ * the allocation granularity. If future Windows specific git code
+ * needs the real getpagesize function, we need to find another solution.
+ */
+int mingw_getpagesize(void)
+{
+       SYSTEM_INFO si;
+       GetSystemInfo(&si);
+       return si.dwAllocationGranularity;
+}
+
+void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
+{
+       HANDLE hmap;
+       void *temp;
+       size_t len;
+       struct stat st;
+       uint64_t o = offset;
+       uint32_t l = o & 0xFFFFFFFF;
+       uint32_t h = (o >> 32) & 0xFFFFFFFF;
+
+       if (!fstat(fd, &st))
+               len = xsize_t(st.st_size);
+       else
+               die("mmap: could not determine filesize");
+
+       if ((length + offset) > len)
+               length = len - offset;
+
+       if (!(flags & MAP_PRIVATE))
+               die("Invalid usage of mmap when built with USE_WIN32_MMAP");
+
+       hmap = CreateFileMapping((HANDLE)_get_osfhandle(fd), 0, PAGE_WRITECOPY,
+               0, 0, 0);
+
+       if (!hmap)
+               return MAP_FAILED;
+
+       temp = MapViewOfFileEx(hmap, FILE_MAP_COPY, h, l, length, start);
+
+       if (!CloseHandle(hmap))
+               warning("unable to close file mapping handle\n");
+
+       return temp ? temp : MAP_FAILED;
+}
+
+int git_munmap(void *start, size_t length)
+{
+       return !UnmapViewOfFile(start);
+}
index e2d96dfe6f75213de567174261d9aeba3e663d9d..44dc293ad314379d7835d9d96a5c3fd12ad2b27f 100644 (file)
@@ -18,8 +18,6 @@
 
  This file is git-specific. Therefore, this file does not attempt
  to implement any codes that are not used by git.
-
- TODO: K
 */
 
 static HANDLE console;
@@ -79,6 +77,20 @@ static void set_console_attr(void)
        SetConsoleTextAttribute(console, attributes);
 }
 
+static void erase_in_line(void)
+{
+       CONSOLE_SCREEN_BUFFER_INFO sbi;
+
+       if (!console)
+               return;
+
+       GetConsoleScreenBufferInfo(console, &sbi);
+       FillConsoleOutputCharacterA(console, ' ',
+               sbi.dwSize.X - sbi.dwCursorPosition.X, sbi.dwCursorPosition,
+               NULL);
+}
+
+
 static const char *set_attr(const char *str)
 {
        const char *func;
@@ -218,7 +230,7 @@ static const char *set_attr(const char *str)
                set_console_attr();
                break;
        case 'K':
-               /* TODO */
+               erase_in_line();
                break;
        default:
                /* Unsupported code */
index 37e3c74861cb335d4bacab86c2f5d03cd95f43bf..1682273c12ab042d73fa32caf30d18fb13ef85e3 100644 (file)
--- a/config.c
+++ b/config.c
@@ -331,9 +331,9 @@ int git_config_bool_or_int(const char *name, const char *value, int *is_bool)
                return 1;
        if (!*value)
                return 0;
-       if (!strcasecmp(value, "true") || !strcasecmp(value, "yes"))
+       if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on"))
                return 1;
-       if (!strcasecmp(value, "false") || !strcasecmp(value, "no"))
+       if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off"))
                return 0;
        *is_bool = 0;
        return git_config_int(name, value);
@@ -495,6 +495,16 @@ static int git_default_core_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.createobject")) {
+               if (!strcmp(value, "rename"))
+                       object_creation_mode = OBJECT_CREATION_USES_RENAMES;
+               else if (!strcmp(value, "link"))
+                       object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;
+               else
+                       die("Invalid mode for object creation: %s", value);
+               return 0;
+       }
+
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
@@ -565,6 +575,40 @@ static int git_default_branch_config(const char *var, const char *value)
        return 0;
 }
 
+static int git_default_push_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "push.default")) {
+               if (!value)
+                       return config_error_nonbool(var);
+               else if (!strcmp(value, "nothing"))
+                       push_default = PUSH_DEFAULT_NOTHING;
+               else if (!strcmp(value, "matching"))
+                       push_default = PUSH_DEFAULT_MATCHING;
+               else if (!strcmp(value, "tracking"))
+                       push_default = PUSH_DEFAULT_TRACKING;
+               else if (!strcmp(value, "current"))
+                       push_default = PUSH_DEFAULT_CURRENT;
+               else {
+                       error("Malformed value for %s: %s", var, value);
+                       return error("Must be one of nothing, matching, "
+                                    "tracking or current.");
+               }
+               return 0;
+       }
+
+       /* Add other config variables here and to Documentation/config.txt. */
+       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 +623,12 @@ 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, "push."))
+               return git_default_push_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;
@@ -632,28 +682,37 @@ int git_config_global(void)
 
 int git_config(config_fn_t fn, void *data)
 {
-       int ret = 0;
+       int ret = 0, found = 0;
        char *repo_config = NULL;
        const char *home = NULL;
 
        /* Setting $GIT_CONFIG makes git read _only_ the given config file. */
        if (config_exclusive_filename)
                return git_config_from_file(fn, config_exclusive_filename, data);
-       if (git_config_system() && !access(git_etc_gitconfig(), R_OK))
+       if (git_config_system() && !access(git_etc_gitconfig(), R_OK)) {
                ret += git_config_from_file(fn, git_etc_gitconfig(),
                                            data);
+               found += 1;
+       }
 
        home = getenv("HOME");
        if (git_config_global() && home) {
                char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
-               if (!access(user_config, R_OK))
+               if (!access(user_config, R_OK)) {
                        ret += git_config_from_file(fn, user_config, data);
+                       found += 1;
+               }
                free(user_config);
        }
 
        repo_config = git_pathdup("config");
-       ret += git_config_from_file(fn, repo_config, data);
+       if (!access(repo_config, R_OK)) {
+               ret += git_config_from_file(fn, repo_config, data);
+               found += 1;
+       }
        free(repo_config);
+       if (found == 0)
+               return -1;
        return ret;
 }
 
@@ -665,16 +724,16 @@ int git_config(config_fn_t fn, void *data)
 
 static struct {
        int baselen;
-       charkey;
+       char *key;
        int do_not_match;
-       regex_tvalue_regex;
+       regex_t *value_regex;
        int multi_replace;
        size_t offset[MAX_MATCHES];
        enum { START, SECTION_SEEN, SECTION_END_SEEN, KEY_SEEN } state;
        int seen;
 } store;
 
-static int matches(const char* key, const char* value)
+static int matches(const char *key, const char *value)
 {
        return !strcmp(key, store.key) &&
                (store.value_regex == NULL ||
@@ -682,7 +741,7 @@ static int matches(const char* key, const char* value)
                  !regexec(store.value_regex, value, 0, NULL, 0)));
 }
 
-static int store_aux(const char* key, const char* value, void *cb)
+static int store_aux(const char *key, const char *value, void *cb)
 {
        const char *ep;
        size_t section_len;
@@ -751,7 +810,7 @@ static int write_error(const char *filename)
        return 4;
 }
 
-static int store_write_section(int fd, const charkey)
+static int store_write_section(int fd, const char *key)
 {
        const char *dot;
        int i, success;
@@ -776,7 +835,7 @@ static int store_write_section(int fd, const char* key)
        return success;
 }
 
-static int store_write_pair(int fd, const char* key, const char* value)
+static int store_write_pair(int fd, const char *key, const char *value)
 {
        int i, success;
        int length = strlen(key + store.baselen + 1);
@@ -824,8 +883,8 @@ static int store_write_pair(int fd, const char* key, const char* value)
        return success;
 }
 
-static ssize_t find_beginning_of_line(const charcontents, size_t size,
-       size_t offset_, intfound_bracket)
+static ssize_t find_beginning_of_line(const char *contents, size_t size,
+       size_t offset_, int *found_bracket)
 {
        size_t equal_offset = size, bracket_offset = size;
        ssize_t offset;
@@ -850,7 +909,7 @@ contline:
        return offset;
 }
 
-int git_config_set(const char* key, const char* value)
+int git_config_set(const char *key, const char *value)
 {
        return git_config_set_multivar(key, value, NULL, 0);
 }
@@ -878,15 +937,15 @@ int git_config_set(const char* key, const char* value)
  * - the config file is removed and the lock file rename()d to it.
  *
  */
-int git_config_set_multivar(const char* key, const char* value,
-       const charvalue_regex, int multi_replace)
+int git_config_set_multivar(const char *key, const char *value,
+       const char *value_regex, int multi_replace)
 {
        int i, dot;
        int fd = -1, in_fd;
        int ret;
-       charconfig_filename;
+       char *config_filename;
        struct lock_file *lock = NULL;
-       const charlast_dot = strrchr(key, '.');
+       const char *last_dot = strrchr(key, '.');
 
        if (config_exclusive_filename)
                config_filename = xstrdup(config_exclusive_filename);
@@ -942,7 +1001,7 @@ int git_config_set_multivar(const char* key, const char* value,
        lock = xcalloc(sizeof(struct lock_file), 1);
        fd = hold_lock_file_for_update(lock, config_filename, 0);
        if (fd < 0) {
-               error("could not lock config file %s", config_filename);
+               error("could not lock config file %s: %s", config_filename, strerror(errno));
                free(store.key);
                ret = -1;
                goto out_free;
@@ -967,13 +1026,13 @@ int git_config_set_multivar(const char* key, const char* value,
                        goto out_free;
                }
 
-               store.key = (char*)key;
+               store.key = (char *)key;
                if (!store_write_section(fd, key) ||
                    !store_write_pair(fd, key, value))
                        goto write_err_out;
        } else {
                struct stat st;
-               charcontents;
+               char *contents;
                size_t contents_sz, copy_begin, copy_end;
                int i, new_line = 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..4e728bca35f5f7b01188ef84df85b161266ce305 100644 (file)
@@ -42,6 +42,8 @@ else \
        if test "$withval" = "yes"; then \
                AC_MSG_WARN([You should provide path for --with-$1=PATH]); \
        else \
+               m4_toupper($1)_PATH=$withval; \
+               AC_MSG_NOTICE([Setting m4_toupper($1)_PATH to $withval]); \
                GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \
        fi; \
 fi; \
@@ -61,6 +63,8 @@ elif test "$withval" = "yes"; then \
        m4_toupper(NO_$1)=; \
 else \
        m4_toupper(NO_$1)=; \
+       m4_toupper($1)DIR=$withval; \
+       AC_MSG_NOTICE([Setting m4_toupper($1)DIR to $withval]); \
        GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \
 fi \
 ])# GIT_PARSE_WITH
@@ -76,6 +80,32 @@ AC_DEFUN([GIT_CHECK_FUNC],[AC_CHECK_FUNC([$1],[
   AC_SEARCH_LIBS([$1],,
   [$2],[$3])
 ],[$3])])
+
+dnl
+dnl GIT_STASH_FLAGS(BASEPATH_VAR)
+dnl -----------------------------
+dnl Allow for easy stashing of LDFLAGS and CPPFLAGS before running
+dnl tests that may want to take user settings into account.
+AC_DEFUN([GIT_STASH_FLAGS],[
+if test -n "$1"; then
+   old_CPPFLAGS="$CPPFLAGS"
+   old_LDFLAGS="$LDFLAGS"
+   CPPFLAGS="-I$1/include $CPPFLAGS"
+   LDFLAGS="-L$1/$lib $LDFLAGS"
+fi
+])
+
+dnl
+dnl GIT_UNSTASH_FLAGS(BASEPATH_VAR)
+dnl -----------------------------
+dnl Restore the stashed *FLAGS values.
+AC_DEFUN([GIT_UNSTASH_FLAGS],[
+if test -n "$1"; then
+   CPPFLAGS="$old_CPPFLAGS"
+   LDFLAGS="$old_LDFLAGS"
+fi
+])
+
 ## Site configuration related to programs (before tests)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
 #
@@ -86,9 +116,124 @@ AC_ARG_WITH([lib],
  [if test "$withval" = "no" || test "$withval" = "yes"; then \
        AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
 else \
+       lib=$withval; \
+       AC_MSG_NOTICE([Setting lib to '$lib']); \
        GIT_CONF_APPEND_LINE(lib=$withval); \
 fi; \
 ],[])
+
+if test -z "$lib"; then
+   AC_MSG_NOTICE([Setting lib to 'lib' (the default)])
+   lib=lib
+fi
+
+AC_ARG_ENABLE([pthreads],
+ [AS_HELP_STRING([--enable-pthreads=FLAGS],
+  [FLAGS is the value to pass to the compiler to enable POSIX Threads.]
+  [The default if FLAGS is not specified is to try first -pthread]
+  [and then -lpthread.]
+  [--without-pthreads will disable threading.])],
+[
+if test "x$enableval" = "xyes"; then
+   AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads])
+elif test "x$enableval" != "xno"; then
+   PTHREAD_CFLAGS=$enableval
+   AC_MSG_NOTICE([Setting '$PTHREAD_CFLAGS' as the FLAGS to enable POSIX Threads])
+else
+   AC_MSG_NOTICE([POSIX Threads will be disabled.])
+   NO_PTHREADS=YesPlease
+   USER_NOPTHREAD=1
+fi],
+[
+   AC_MSG_NOTICE([Will try -pthread then -lpthread to enable POSIX Threads.])
+])
+
+## Site configuration (override autodetection)
+## --with-PACKAGE[=ARG] and --without-PACKAGE
+AC_MSG_NOTICE([CHECKS for site configuration])
+#
+# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability
+# tests.  These tests take up a significant amount of the total test time
+# but are not needed unless you plan to talk to SVN repos.
+#
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
+# Define PPC_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for PowerPC.
+#
+# Define ARM_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine optimized for ARM.
+#
+# Define NO_OPENSSL environment variable if you do not have OpenSSL.
+# This also implies MOZILLA_SHA1.
+#
+# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+AC_ARG_WITH(openssl,
+AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
+AS_HELP_STRING([],              [ARG can be prefix for openssl library and headers]),\
+GIT_PARSE_WITH(openssl))
+#
+# Define NO_CURL if you do not have curl installed.  git-http-pull and
+# git-http-push are not built, and you cannot use http:// and https://
+# transports.
+#
+# Define CURLDIR=/foo/bar if your curl header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+AC_ARG_WITH(curl,
+AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)])
+AS_HELP_STRING([],           [ARG can be also prefix for curl library and headers]),
+GIT_PARSE_WITH(curl))
+#
+# 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.
+AC_ARG_WITH(expat,
+AS_HELP_STRING([--with-expat],
+[support git-push using http:// and https:// transports via WebDAV (default is YES)])
+AS_HELP_STRING([],            [ARG can be also prefix for expat library and headers]),
+GIT_PARSE_WITH(expat))
+#
+# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink
+# installed in /sw, but don't want GIT to link against any libraries
+# installed there.  If defined you may specify your own (or Fink's)
+# include directories and library directories by defining CFLAGS
+# and LDFLAGS appropriately.
+#
+# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X,
+# have DarwinPorts installed in /opt/local, but don't want GIT to
+# link against any libraries installed there.  If defined you may
+# specify your own (or DarwinPort's) include directories and
+# library directories by defining CFLAGS and LDFLAGS appropriately.
+#
+# Define NO_MMAP if you want to avoid mmap.
+#
+# Define NO_ICONV if your libc does not properly support iconv.
+AC_ARG_WITH(iconv,
+AS_HELP_STRING([--without-iconv],
+[if your architecture doesn't properly support iconv])
+AS_HELP_STRING([--with-iconv=PATH],
+[PATH is prefix for libiconv library and headers])
+AS_HELP_STRING([],
+[used only if you need linking with libiconv]),
+GIT_PARSE_WITH(iconv))
+
+## --enable-FEATURE[=ARG] and --disable-FEATURE
+#
+# Define USE_NSEC below if you want git to care about sub-second file mtimes
+# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
+# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
+# randomly break unless your underlying filesystem supports those sub-second
+# times (my ext3 doesn't).
+#
+# Define USE_STDEV below if you want git to care about the underlying device
+# change being considered an inode change from the update-index perspective.
+
 #
 # Define SHELL_PATH to provide path to shell.
 GIT_ARG_SET_PATH(shell)
@@ -114,31 +259,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])
@@ -167,7 +312,7 @@ fi
 AC_CHECK_PROGS(ASCIIDOC, [asciidoc])
 if test -n "$ASCIIDOC"; then
        AC_MSG_CHECKING([for asciidoc version])
-       asciidoc_version=`$ASCIIDOC --version 2>&1`
+       asciidoc_version=`$ASCIIDOC --version 2>/dev/null`
        case "${asciidoc_version}" in
        asciidoc' '8*)
                ASCIIDOC8=YesPlease
@@ -191,33 +336,57 @@ AC_MSG_NOTICE([CHECKS for libraries])
 #
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
+
+GIT_STASH_FLAGS($OPENSSLDIR)
+
 AC_CHECK_LIB([crypto], [SHA1_Init],
 [NEEDS_SSL_WITH_CRYPTO=],
 [AC_CHECK_LIB([ssl], [SHA1_Init],
  [NEEDS_SSL_WITH_CRYPTO=YesPlease
   NEEDS_SSL_WITH_CRYPTO=],
  [NO_OPENSSL=YesPlease])])
+
+GIT_UNSTASH_FLAGS($OPENSSLDIR)
+
 AC_SUBST(NEEDS_SSL_WITH_CRYPTO)
 AC_SUBST(NO_OPENSSL)
+
 #
 # Define NO_CURL if you do not have libcurl installed.  git-http-pull and
 # git-http-push are not built, and you cannot use http:// and https://
 # transports.
+
+GIT_STASH_FLAGS($CURLDIR)
+
 AC_CHECK_LIB([curl], [curl_global_init],
 [NO_CURL=],
 [NO_CURL=YesPlease])
+
+GIT_UNSTASH_FLAGS($CURLDIR)
+
 AC_SUBST(NO_CURL)
+
 #
 # 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.
+
+GIT_STASH_FLAGS($EXPATDIR)
+
 AC_CHECK_LIB([expat], [XML_ParserCreate],
 [NO_EXPAT=],
 [NO_EXPAT=YesPlease])
+
+GIT_UNSTASH_FLAGS($EXPATDIR)
+
 AC_SUBST(NO_EXPAT)
+
 #
 # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin and
 # some Solaris installations).
 # Define NO_ICONV if neither libc nor libiconv support iconv.
+
+GIT_STASH_FLAGS($ICONVDIR)
+
 AC_DEFUN([ICONVTEST_SRC], [
 #include <iconv.h>
 
@@ -227,25 +396,46 @@ int main(void)
        return 0;
 }
 ])
-AC_MSG_CHECKING([for iconv in -lc])
-AC_LINK_IFELSE(ICONVTEST_SRC,
+
+if test -n "$ICONVDIR"; then
+   lib_order="-liconv -lc"
+else
+   lib_order="-lc -liconv"
+fi
+
+NO_ICONV=YesPlease
+
+for l in $lib_order; do
+    if test "$l" = "-liconv"; then
+       NEEDS_LIBICONV=YesPlease
+    else
+       NEEDS_LIBICONV=
+    fi
+
+    old_LIBS="$LIBS"
+    LIBS="$LIBS $l"
+    AC_MSG_CHECKING([for iconv in $l])
+    AC_LINK_IFELSE(ICONVTEST_SRC,
        [AC_MSG_RESULT([yes])
-       NEEDS_LIBICONV=],
-       [AC_MSG_RESULT([no])
-       old_LIBS="$LIBS"
-       LIBS="$LIBS -liconv"
-       AC_MSG_CHECKING([for iconv in -liconv])
-       AC_LINK_IFELSE(ICONVTEST_SRC,
-               [AC_MSG_RESULT([yes])
-               NEEDS_LIBICONV=YesPlease],
-               [AC_MSG_RESULT([no])
-               NO_ICONV=YesPlease])
-       LIBS="$old_LIBS"])
+       NO_ICONV=
+       break],
+       [AC_MSG_RESULT([no])])
+    LIBS="$old_LIBS"
+done
+
+#in case of break
+LIBS="$old_LIBS"
+
+GIT_UNSTASH_FLAGS($ICONVDIR)
+
 AC_SUBST(NEEDS_LIBICONV)
 AC_SUBST(NO_ICONV)
-test -n "$NEEDS_LIBICONV" && LIBS="$LIBS -liconv"
+
 #
 # Define NO_DEFLATE_BOUND if deflateBound is missing from zlib.
+
+GIT_STASH_FLAGS($ZLIB_PATH)
+
 AC_DEFUN([ZLIBTEST_SRC], [
 #include <zlib.h>
 
@@ -263,7 +453,11 @@ AC_LINK_IFELSE(ZLIBTEST_SRC,
        [AC_MSG_RESULT([no])
        NO_DEFLATE_BOUND=yes])
 LIBS="$old_LIBS"
+
+GIT_UNSTASH_FLAGS($ZLIB_PATH)
+
 AC_SUBST(NO_DEFLATE_BOUND)
+
 #
 # Define NEEDS_SOCKET if linking with libc is not enough (SunOS,
 # Patrick Mauritz).
@@ -297,13 +491,18 @@ int main(void)
        return 0;
 }
 ]])
+
+GIT_STASH_FLAGS($ICONVDIR)
+
 AC_MSG_CHECKING([for old iconv()])
 AC_COMPILE_IFELSE(OLDICONVTEST_SRC,
        [AC_MSG_RESULT([no])],
        [AC_MSG_RESULT([yes])
        OLD_ICONV=UnfortunatelyYes])
-AC_SUBST(OLD_ICONV)
 
+GIT_UNSTASH_FLAGS($ICONVDIR)
+
+AC_SUBST(OLD_ICONV)
 
 ## Checks for typedefs, structures, and compiler characteristics.
 AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics])
@@ -492,111 +691,66 @@ AC_SUBST(NO_MKDTEMP)
 #
 # Define NO_PTHREADS if we do not have pthreads
 #
-# Define PTHREAD_LIBS to the linker flag used for Pthread support.
-AC_LANG_CONFTEST([AC_LANG_PROGRAM(
-  [[#include <pthread.h>]],
-  [[pthread_mutex_t test_mutex;]]
-)])
-${CC} -pthread conftest.c -o conftest.o > /dev/null 2>&1
-if test $? -eq 0;then
- PTHREAD_LIBS="-pthread"
+# Define PTHREAD_LIBS to the linker flag used for Pthread support and define
+# THREADED_DELTA_SEARCH if Pthreads are available.
+AC_DEFUN([PTHREADTEST_SRC], [
+#include <pthread.h>
+
+int main(void)
+{
+       pthread_mutex_t test_mutex;
+       return (0);
+}
+])
+
+dnl AC_LANG_CONFTEST([AC_LANG_PROGRAM(
+dnl   [[#include <pthread.h>]],
+dnl   [[pthread_mutex_t test_mutex;]]
+dnl )])
+
+NO_PTHREADS=UnfortunatelyYes
+THREADED_DELTA_SEARCH=
+PTHREAD_LIBS=
+
+if test -n "$USER_NOPTHREAD"; then
+   AC_MSG_NOTICE([Skipping POSIX Threads at user request.])
+# handle these separately since PTHREAD_CFLAGS could be '-lpthreads
+# -D_REENTRANT' or some such.
+elif test -z "$PTHREAD_CFLAGS"; then
+  for opt in -pthread -lpthread; do
+     old_CFLAGS="$CFLAGS"
+     CFLAGS="$opt $CFLAGS"
+     AC_MSG_CHECKING([Checking for POSIX Threads with '$opt'])
+     AC_LINK_IFELSE(PTHREADTEST_SRC,
+       [AC_MSG_RESULT([yes])
+               NO_PTHREADS=
+               PTHREAD_LIBS="$opt"
+               THREADED_DELTA_SEARCH=YesPlease
+               break
+       ],
+       [AC_MSG_RESULT([no])])
+      CFLAGS="$old_CFLAGS"
+  done
 else
- ${CC} -lpthread conftest.c -o conftest.o > /dev/null 2>&1
- if test $? -eq 0;then
-  PTHREAD_LIBS="-lpthread"
- else
-  NO_PTHREADS=UnfortunatelyYes
- fi
-fi
-AC_SUBST(PTHREAD_LIBS)
-AC_SUBST(NO_PTHREADS)
+  old_CFLAGS="$CFLAGS"
+  CFLAGS="$PTHREAD_CFLAGS $CFLAGS"
+  AC_MSG_CHECKING([Checking for POSIX Threads with '$PTHREAD_CFLAGS'])
+  AC_LINK_IFELSE(PTHREADTEST_SRC,
+       [AC_MSG_RESULT([yes])
+               NO_PTHREADS=
+               PTHREAD_LIBS="$PTHREAD_CFLAGS"
+               THREADED_DELTA_SEARCH=YesPlease
+       ],
+       [AC_MSG_RESULT([no])])
 
-## Site configuration (override autodetection)
-## --with-PACKAGE[=ARG] and --without-PACKAGE
-AC_MSG_NOTICE([CHECKS for site configuration])
-#
-# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability
-# tests.  These tests take up a significant amount of the total test time
-# but are not needed unless you plan to talk to SVN repos.
-#
-# Define MOZILLA_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
-# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
-# choice) has very fast version optimized for i586.
-#
-# Define PPC_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine optimized for PowerPC.
-#
-# Define ARM_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine optimized for ARM.
-#
-# Define NO_OPENSSL environment variable if you do not have OpenSSL.
-# This also implies MOZILLA_SHA1.
-#
-# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in
-# /foo/bar/include and /foo/bar/lib directories.
-AC_ARG_WITH(openssl,
-AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)])
-AS_HELP_STRING([],              [ARG can be prefix for openssl library and headers]),\
-GIT_PARSE_WITH(openssl))
-#
-# Define NO_CURL if you do not have curl installed.  git-http-pull and
-# git-http-push are not built, and you cannot use http:// and https://
-# transports.
-#
-# Define CURLDIR=/foo/bar if your curl header and library files are in
-# /foo/bar/include and /foo/bar/lib directories.
-AC_ARG_WITH(curl,
-AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)])
-AS_HELP_STRING([],           [ARG can be also prefix for curl library and headers]),
-GIT_PARSE_WITH(curl))
-#
-# 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.
-AC_ARG_WITH(expat,
-AS_HELP_STRING([--with-expat],
-[support git-push using http:// and https:// transports via WebDAV (default is YES)])
-AS_HELP_STRING([],            [ARG can be also prefix for expat library and headers]),
-GIT_PARSE_WITH(expat))
-#
-# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink
-# installed in /sw, but don't want GIT to link against any libraries
-# installed there.  If defined you may specify your own (or Fink's)
-# include directories and library directories by defining CFLAGS
-# and LDFLAGS appropriately.
-#
-# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X,
-# have DarwinPorts installed in /opt/local, but don't want GIT to
-# link against any libraries installed there.  If defined you may
-# specify your own (or DarwinPort's) include directories and
-# library directories by defining CFLAGS and LDFLAGS appropriately.
-#
-# Define NO_MMAP if you want to avoid mmap.
-#
-# Define NO_ICONV if your libc does not properly support iconv.
-AC_ARG_WITH(iconv,
-AS_HELP_STRING([--without-iconv],
-[if your architecture doesn't properly support iconv])
-AS_HELP_STRING([--with-iconv=PATH],
-[PATH is prefix for libiconv library and headers])
-AS_HELP_STRING([],
-[used only if you need linking with libiconv]),
-GIT_PARSE_WITH(iconv))
+  CFLAGS="$old_CFLAGS"
+fi
 
-## --enable-FEATURE[=ARG] and --disable-FEATURE
-#
-# Define USE_NSEC below if you want git to care about sub-second file mtimes
-# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
-# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
-# randomly break unless your underlying filesystem supports those sub-second
-# times (my ext3 doesn't).
-#
-# Define USE_STDEV below if you want git to care about the underlying device
-# change being considered an inode change from the update-index perspective.
+CFLAGS="$old_CFLAGS"
 
+AC_SUBST(PTHREAD_LIBS)
+AC_SUBST(NO_PTHREADS)
+AC_SUBST(THREADED_DELTA_SEARCH)
 
 ## Output files
 AC_CONFIG_FILES(["${config_file}":"${config_in}":"${config_append}"])
index 2f55ad2c256bc01b3062b99251af4386eef5af22..f6b8ba6fec16ed2ff2de4c66e2465e79d059a817 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -177,18 +177,11 @@ static enum protocol get_protocol(const char *name)
 
 static const char *ai_name(const struct addrinfo *ai)
 {
-       static char addr[INET_ADDRSTRLEN];
-       if ( AF_INET == ai->ai_family ) {
-               struct sockaddr_in *in;
-               in = (struct sockaddr_in *)ai->ai_addr;
-               inet_ntop(ai->ai_family, &in->sin_addr, addr, sizeof(addr));
-       } else if ( AF_INET6 == ai->ai_family ) {
-               struct sockaddr_in6 *in;
-               in = (struct sockaddr_in6 *)ai->ai_addr;
-               inet_ntop(ai->ai_family, &in->sin6_addr, addr, sizeof(addr));
-       } else {
+       static char addr[NI_MAXHOST];
+       if (getnameinfo(ai->ai_addr, ai->ai_addrlen, addr, sizeof(addr), NULL, 0,
+                       NI_NUMERICHOST) != 0)
                strcpy(addr, "(unknown)");
-       }
+
        return addr;
 }
 
@@ -315,7 +308,7 @@ static int git_tcp_connect_sock(char *host, int flags)
                /* Not numeric */
                struct servent *se = getservbyname(port,"tcp");
                if ( !se )
-                       die("Unknown port %s\n", port);
+                       die("Unknown port %s", port);
                nport = se->s_port;
        }
 
@@ -373,8 +366,6 @@ static void git_tcp_connect(int fd[2], char *host, int flags)
 
 
 static char *git_proxy_command;
-static const char *rhost_name;
-static int rhost_len;
 
 static int git_proxy_command_options(const char *var, const char *value,
                void *cb)
@@ -383,6 +374,8 @@ static int git_proxy_command_options(const char *var, const char *value,
                const char *for_pos;
                int matchlen = -1;
                int hostlen;
+               const char *rhost_name = cb;
+               int rhost_len = strlen(rhost_name);
 
                if (git_proxy_command)
                        return 0;
@@ -426,11 +419,8 @@ static int git_proxy_command_options(const char *var, const char *value,
 
 static int git_use_proxy(const char *host)
 {
-       rhost_name = host;
-       rhost_len = strlen(host);
        git_proxy_command = getenv("GIT_PROXY_COMMAND");
-       git_config(git_proxy_command_options, NULL);
-       rhost_name = NULL;
+       git_config(git_proxy_command_options, (void*)host);
        return (git_proxy_command && *git_proxy_command);
 }
 
@@ -507,7 +497,7 @@ struct child_process *git_connect(int fd[2], const char *url_orig,
                                  const char *prog, int flags)
 {
        char *url = xstrdup(url_orig);
-       char *host, *path = url;
+       char *host, *path;
        char *end;
        int c;
        struct child_process *conn;
index 3889cfb5aa20c93e6945784e48924dff3d73c391..f44152c4331bb6a2de764e6dcc98c1ac39e517e5 100755 (executable)
@@ -1,3 +1,4 @@
+#!bash
 #
 # bash completion support for core Git.
 #
 #       are currently in a git repository.  The %s token will be
 #       the name of the current branch.
 #
+#       In addition, if you set GIT_PS1_SHOWDIRTYSTATE to a nonempty
+#       value, unstaged (*) and staged (+) changes will be shown next
+#       to the branch name.  You can configure this per-repository
+#       with the bash.showDirtyState variable, which defaults to true
+#       once GIT_PS1_SHOWDIRTYSTATE is enabled.
+#
 # To submit patches:
 #
 #    *) Read Documentation/SubmittingPatches
@@ -50,10 +57,12 @@ case "$COMP_WORDBREAKS" in
 *)   COMP_WORDBREAKS="$COMP_WORDBREAKS:"
 esac
 
+# __gitdir accepts 0 or 1 arguments (i.e., location)
+# returns location of .git repo
 __gitdir ()
 {
-       if [ -z "$1" ]; then
-               if [ -n "$__git_dir" ]; then
+       if [ -z "${1-}" ]; then
+               if [ -n "${__git_dir-}" ]; then
                        echo "$__git_dir"
                elif [ -d .git ]; then
                        echo .git
@@ -67,58 +76,93 @@ __gitdir ()
        fi
 }
 
+# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
+# returns text to add to bash PS1 prompt (includes branch name)
 __git_ps1 ()
 {
-       local g="$(git rev-parse --git-dir 2>/dev/null)"
+       local g="$(__gitdir)"
        if [ -n "$g" ]; then
                local r
                local b
-               if [ -d "$g/rebase-apply" ]
-               then
-                       if test -f "$g/rebase-apply/rebasing"
-                       then
+               if [ -d "$g/rebase-apply" ]; then
+                       if [ -f "$g/rebase-apply/rebasing" ]; then
                                r="|REBASE"
-                       elif test -f "$g/rebase-apply/applying"
-                       then
+               elif [ -f "$g/rebase-apply/applying" ]; then
                                r="|AM"
                        else
                                r="|AM/REBASE"
                        fi
                        b="$(git symbolic-ref HEAD 2>/dev/null)"
-               elif [ -f "$g/rebase-merge/interactive" ]
-               then
+               elif [ -f "$g/rebase-merge/interactive" ]; then
                        r="|REBASE-i"
                        b="$(cat "$g/rebase-merge/head-name")"
-               elif [ -d "$g/rebase-merge" ]
-               then
+               elif [ -d "$g/rebase-merge" ]; then
                        r="|REBASE-m"
                        b="$(cat "$g/rebase-merge/head-name")"
-               elif [ -f "$g/MERGE_HEAD" ]
-               then
-                       r="|MERGING"
-                       b="$(git symbolic-ref HEAD 2>/dev/null)"
                else
-                       if [ -f "$g/BISECT_LOG" ]
-                       then
+                       if [ -f "$g/MERGE_HEAD" ]; then
+                               r="|MERGING"
+                       fi
+                       if [ -f "$g/BISECT_LOG" ]; then
                                r="|BISECTING"
                        fi
-                       if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
-                       then
-                               if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
-                               then
-                                       b="$(cut -c1-7 "$g/HEAD")..."
+
+                       b="$(git symbolic-ref HEAD 2>/dev/null)" || {
+
+                               b="$(
+                               case "${GIT_PS1_DESCRIBE_STYLE-}" in
+                               (contains)
+                                       git describe --contains HEAD ;;
+                               (branch)
+                                       git describe --contains --all HEAD ;;
+                               (describe)
+                                       git describe HEAD ;;
+                               (* | default)
+                                       git describe --exact-match HEAD ;;
+                               esac 2>/dev/null)" ||
+
+                               b="$(cut -c1-7 "$g/HEAD" 2>/dev/null)..." ||
+                               b="unknown"
+                               b="($b)"
+                       }
+               fi
+
+               local w
+               local i
+               local c
+
+               if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
+                       if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
+                               c="BARE:"
+                       else
+                               b="GIT_DIR!"
+                       fi
+               elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
+                       if [ -n "${GIT_PS1_SHOWDIRTYSTATE-}" ]; then
+                               if [ "$(git config --bool bash.showDirtyState)" != "false" ]; then
+                                       git diff --no-ext-diff --ignore-submodules \
+                                               --quiet --exit-code || w="*"
+                                       if git rev-parse --quiet --verify HEAD >/dev/null; then
+                                               git diff-index --cached --quiet \
+                                                       --ignore-submodules HEAD -- || i="+"
+                                       else
+                                               i="#"
+                                       fi
                                fi
                        fi
                fi
 
-               if [ -n "$1" ]; then
-                       printf "$1" "${b##refs/heads/}$r"
-               else
-                       printf " (%s)" "${b##refs/heads/}$r"
+               if [ -n "$b" ]; then
+                       if [ -n "${1-}" ]; then
+                               printf "$1" "$c${b##refs/heads/}$w$i$r"
+                       else
+                               printf " (%s)" "$c${b##refs/heads/}$w$i$r"
+                       fi
                fi
        fi
 }
 
+# __gitcomp_1 requires 2 arguments
 __gitcomp_1 ()
 {
        local c IFS=' '$'\t'$'\n'
@@ -131,6 +175,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 +189,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 +215,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 +234,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 +267,7 @@ __git_refs ()
        done
 }
 
+# __git_refs2 requires 1 argument (to pass to __git_refs)
 __git_refs2 ()
 {
        local i
@@ -226,6 +276,7 @@ __git_refs2 ()
        done
 }
 
+# __git_refs_remotes requires 1 argument (to pass to ls-remote)
 __git_refs_remotes ()
 {
        local cmd i is_hash=y
@@ -264,7 +315,7 @@ __git_remotes ()
 
 __git_merge_strategies ()
 {
-       if [ -n "$__git_merge_strategylist" ]; then
+       if [ -n "${__git_merge_strategylist-}" ]; then
                echo "$__git_merge_strategylist"
                return
        fi
@@ -348,9 +399,88 @@ __git_complete_revlist ()
        esac
 }
 
+__git_complete_remote_or_refspec ()
+{
+       local cmd="${COMP_WORDS[1]}"
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
+       while [ $c -lt $COMP_CWORD ]; do
+               i="${COMP_WORDS[c]}"
+               case "$i" in
+               --all|--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
+               -*) ;;
+               *) remote="$i"; break ;;
+               esac
+               c=$((++c))
+       done
+       if [ -z "$remote" ]; then
+               __gitcomp "$(__git_remotes)"
+               return
+       fi
+       if [ $no_complete_refspec = 1 ]; then
+               COMPREPLY=()
+               return
+       fi
+       [ "$remote" = "." ] && remote=
+       case "$cur" in
+       *:*)
+               case "$COMP_WORDBREAKS" in
+               *:*) : great ;;
+               *)   pfx="${cur%%:*}:" ;;
+               esac
+               cur="${cur#*:}"
+               lhs=0
+               ;;
+       +*)
+               pfx="+"
+               cur="${cur#+}"
+               ;;
+       esac
+       case "$cmd" in
+       fetch)
+               if [ $lhs = 1 ]; then
+                       __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur"
+               else
+                       __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               fi
+               ;;
+       pull)
+               if [ $lhs = 1 ]; then
+                       __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+               else
+                       __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               fi
+               ;;
+       push)
+               if [ $lhs = 1 ]; then
+                       __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               else
+                       __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+               fi
+               ;;
+       esac
+}
+
+__git_complete_strategy ()
+{
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -s|--strategy)
+               __gitcomp "$(__git_merge_strategies)"
+               return 0
+       esac
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --strategy=*)
+               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+               return 0
+               ;;
+       esac
+       return 1
+}
+
 __git_all_commands ()
 {
-       if [ -n "$__git_all_commandlist" ]; then
+       if [ -n "${__git_all_commandlist-}" ]; then
                echo "$__git_all_commandlist"
                return
        fi
@@ -368,7 +498,7 @@ __git_all_commandlist="$(__git_all_commands 2>/dev/null)"
 
 __git_porcelain_commands ()
 {
-       if [ -n "$__git_porcelain_commandlist" ]; then
+       if [ -n "${__git_porcelain_commandlist-}" ]; then
                echo "$__git_porcelain_commandlist"
                return
        fi
@@ -470,6 +600,7 @@ __git_aliases ()
        done
 }
 
+# __git_aliased_command requires 1 argument
 __git_aliased_command ()
 {
        local word cmdline=$(git --git-dir="$(__gitdir)" \
@@ -482,6 +613,7 @@ __git_aliased_command ()
        done
 }
 
+# __git_find_subcommand requires 1 argument
 __git_find_subcommand ()
 {
        local word subcommand c=1
@@ -526,7 +658,8 @@ _git_am ()
                ;;
        --*)
                __gitcomp "
-                       --signoff --utf8 --binary --3way --interactive
+                       --3way --committer-date-is-author-date --ignore-date
+                       --interactive --keep --no-utf8 --signoff --utf8
                        --whitespace=
                        "
                return
@@ -563,7 +696,7 @@ _git_add ()
        --*)
                __gitcomp "
                        --interactive --refresh --patch --update --dry-run
-                       --ignore-errors
+                       --ignore-errors --intent-to-add
                        "
                return
        esac
@@ -628,7 +761,6 @@ _git_branch ()
        done
 
        case "${COMP_WORDS[COMP_CWORD]}" in
-       --*=*)  COMPREPLY=() ;;
        --*)
                __gitcomp "
                        --color --no-color --verbose --abbrev= --no-abbrev
@@ -759,23 +891,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 --staged --pickaxe-all --pickaxe-regex
                        --base --ours --theirs
+                       $__git_diff_common_options
                        "
                return
                ;;
@@ -783,46 +922,68 @@ _git_diff ()
        __git_complete_file
 }
 
-_git_fetch ()
+__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
+                       tkdiff vimdiff gvimdiff xxdiff
+"
+
+_git_difftool ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --tool=*)
+               __gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}"
+               return
+               ;;
+       --*)
+               __gitcomp "--tool="
+               return
+               ;;
+       esac
+       COMPREPLY=()
+}
 
-       if [ "$COMP_CWORD" = 2 ]; then
-               __gitcomp "$(__git_remotes)"
-       else
-               case "$cur" in
-               *:*)
-                       local pfx=""
-                       case "$COMP_WORDBREAKS" in
-                       *:*) : great ;;
-                       *)   pfx="${cur%%:*}:" ;;
-                       esac
-                       __gitcomp "$(__git_refs)" "$pfx" "${cur#*:}"
-                       ;;
-               *)
-                       __gitcomp "$(__git_refs2 "${COMP_WORDS[2]}")"
-                       ;;
-               esac
-       fi
+__git_fetch_options="
+       --quiet --verbose --append --upload-pack --force --keep --depth=
+       --tags --no-tags
+"
+
+_git_fetch ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "$__git_fetch_options"
+               return
+               ;;
+       esac
+       __git_complete_remote_or_refspec
 }
 
 _git_format_patch ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
+       --thread=*)
+               __gitcomp "
+                       deep shallow
+                       " "" "${cur##--thread=}"
+               return
+               ;;
        --*)
                __gitcomp "
-                       --stdout --attach --thread
+                       --stdout --attach --no-attach --thread --thread=
                        --output-directory
                        --numbered --start-number
                        --numbered-files
                        --keep-subject
                        --signoff
-                       --in-reply-to=
+                       --in-reply-to= --cc=
                        --full-index --binary
                        --not --all
                        --cover-letter
                        --no-prefix --src-prefix= --dst-prefix=
+                       --inline --suffix= --ignore-if-in-upstream
+                       --subject-prefix=
                        "
                return
                ;;
@@ -830,6 +991,21 @@ _git_format_patch ()
        __git_complete_revlist
 }
 
+_git_fsck ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "
+                       --tags --root --unreachable --cache --no-reflogs --full
+                       --strict --verbose --lost-found
+                       "
+               return
+               ;;
+       esac
+       COMPREPLY=()
+}
+
 _git_gc ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
@@ -930,6 +1106,30 @@ _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_date_formats="relative iso8601 rfc2822 short local default raw"
+
 _git_log ()
 {
        __git_has_doubledash && return
@@ -942,36 +1142,37 @@ _git_log ()
        fi
        case "$cur" in
        --pretty=*)
-               __gitcomp "
-                       oneline short medium full fuller email raw
+               __gitcomp "$__git_log_pretty_formats
                        " "" "${cur##--pretty=}"
                return
                ;;
+       --format=*)
+               __gitcomp "$__git_log_pretty_formats
+                       " "" "${cur##--format=}"
+               return
+               ;;
        --date=*)
-               __gitcomp "
-                       relative iso8601 rfc2822 short local default
-               " "" "${cur##--date=}"
+               __gitcomp "$__git_log_date_formats" "" "${cur##--date=}"
                return
                ;;
        --*)
                __gitcomp "
-                       --max-count= --max-age= --since= --after=
-                       --min-age= --before= --until=
+                       $__git_log_common_options
+                       $__git_log_shortlog_options
+                       $__git_log_gitk_options
                        --root --topo-order --date-order --reverse
-                       --no-merges --follow
+                       --follow
                        --abbrev-commit --abbrev=
                        --relative-date --date=
-                       --author= --committer= --grep=
-                       --all-match
-                       --pretty= --name-status --name-only --raw
-                       --not --all
-                       --left-right --cherry-pick
+                       --pretty= --format= --oneline
+                       --cherry-pick
                        --graph
-                       --stat --numstat --shortstat
-                       --decorate --diff-filter=
-                       --color-words --walk-reflogs
-                       --parents --children --full-history
+                       --decorate
+                       --walk-reflogs
+                       --parents --children
                        $merge
+                       $__git_diff_common_options
+                       --pickaxe-all --pickaxe-regex
                        "
                return
                ;;
@@ -979,23 +1180,19 @@ _git_log ()
        __git_complete_revlist
 }
 
+__git_merge_options="
+       --no-commit --no-stat --log --no-log --squash --strategy
+       --commit --stat --no-squash --ff --no-ff
+"
+
 _git_merge ()
 {
+       __git_complete_strategy && return
+
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       case "${COMP_WORDS[COMP_CWORD-1]}" in
-       -s|--strategy)
-               __gitcomp "$(__git_merge_strategies)"
-               return
-       esac
        case "$cur" in
-       --strategy=*)
-               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
-               return
-               ;;
        --*)
-               __gitcomp "
-                       --no-commit --no-stat --log --no-log --squash --strategy
-                       "
+               __gitcomp "$__git_merge_options"
                return
        esac
        __gitcomp "$(__git_refs)"
@@ -1006,10 +1203,7 @@ _git_mergetool ()
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --tool=*)
-               __gitcomp "
-                       kdiff3 tkdiff meld xxdiff emerge
-                       vimdiff gvimdiff ecmerge opendiff
-                       " "" "${cur##--tool=}"
+               __gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}"
                return
                ;;
        --*)
@@ -1044,40 +1238,44 @@ _git_name_rev ()
 
 _git_pull ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
+       __git_complete_strategy && return
 
-       if [ "$COMP_CWORD" = 2 ]; then
-               __gitcomp "$(__git_remotes)"
-       else
-               __gitcomp "$(__git_refs "${COMP_WORDS[2]}")"
-       fi
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "
+                       --rebase --no-rebase
+                       $__git_merge_options
+                       $__git_fetch_options
+               "
+               return
+               ;;
+       esac
+       __git_complete_remote_or_refspec
 }
 
 _git_push ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-
-       if [ "$COMP_CWORD" = 2 ]; then
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       --repo)
                __gitcomp "$(__git_remotes)"
-       else
-               case "$cur" in
-               *:*)
-                       local pfx=""
-                       case "$COMP_WORDBREAKS" in
-                       *:*) : great ;;
-                       *)   pfx="${cur%%:*}:" ;;
-                       esac
-
-                       __gitcomp "$(__git_refs "${COMP_WORDS[2]}")" "$pfx" "${cur#*:}"
-                       ;;
-               +*)
-                       __gitcomp "$(__git_refs)" + "${cur#+}"
-                       ;;
-               *)
-                       __gitcomp "$(__git_refs)"
-                       ;;
-               esac
-       fi
+               return
+       esac
+       case "$cur" in
+       --repo=*)
+               __gitcomp "$(__git_remotes)" "" "${cur##--repo=}"
+               return
+               ;;
+       --*)
+               __gitcomp "
+                       --all --mirror --tags --dry-run --force --verbose
+                       --receive-pack= --repo=
+               "
+               return
+               ;;
+       esac
+       __git_complete_remote_or_refspec
 }
 
 _git_rebase ()
@@ -1087,16 +1285,8 @@ _git_rebase ()
                __gitcomp "--continue --skip --abort"
                return
        fi
-       case "${COMP_WORDS[COMP_CWORD-1]}" in
-       -s|--strategy)
-               __gitcomp "$(__git_merge_strategies)"
-               return
-       esac
+       __git_complete_strategy && return
        case "$cur" in
-       --strategy=*)
-               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
-               return
-               ;;
        --*)
                __gitcomp "--onto --merge --strategy --interactive"
                return
@@ -1104,18 +1294,39 @@ _git_rebase ()
        __gitcomp "$(__git_refs)"
 }
 
+__git_send_email_confirm_options="always never auto cc compose"
+__git_send_email_suppresscc_options="author self cc ccbody sob cccmd body all"
+
 _git_send_email ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
+       --confirm=*)
+               __gitcomp "
+                       $__git_send_email_confirm_options
+                       " "" "${cur##--confirm=}"
+               return
+               ;;
+       --suppress-cc=*)
+               __gitcomp "
+                       $__git_send_email_suppresscc_options
+                       " "" "${cur##--suppress-cc=}"
+
+               return
+               ;;
+       --smtp-encryption=*)
+               __gitcomp "ssl tls" "" "${cur##--smtp-encryption=}"
+               return
+               ;;
        --*)
-               __gitcomp "--bcc --cc --cc-cmd --chain-reply-to --compose
-                       --dry-run --envelope-sender --from --identity
+               __gitcomp "--annotate --bcc --cc --cc-cmd --chain-reply-to
+                       --compose --confirm= --dry-run --envelope-sender
+                       --from --identity
                        --in-reply-to --no-chain-reply-to --no-signed-off-by-cc
                        --no-suppress-from --no-thread --quiet
                        --signed-off-by-cc --smtp-pass --smtp-server
-                       --smtp-server-port --smtp-ssl --smtp-user --subject
-                       --suppress-cc --suppress-from --thread --to
+                       --smtp-server-port --smtp-encryption= --smtp-user
+                       --subject --suppress-cc= --suppress-from --thread --to
                        --validate --no-validate"
                return
                ;;
@@ -1154,10 +1365,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
@@ -1165,6 +1380,26 @@ _git_config ()
                        "
                return
                ;;
+       help.format)
+               __gitcomp "man info web html"
+               return
+               ;;
+       log.date)
+               __gitcomp "$__git_log_date_formats"
+               return
+               ;;
+       sendemail.aliasesfiletype)
+               __gitcomp "mutt mailrc pine elm gnus"
+               return
+               ;;
+       sendemail.confirm)
+               __gitcomp "$__git_send_email_confirm_options"
+               return
+               ;;
+       sendemail.suppresscc)
+               __gitcomp "$__git_send_email_suppresscc_options"
+               return
+               ;;
        *.*)
                COMPREPLY=()
                return
@@ -1193,6 +1428,39 @@ _git_config ()
                __gitcomp "$(__git_heads)" "$pfx" "$cur" "."
                return
                ;;
+       guitool.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               __gitcomp "
+                       argprompt cmd confirm needsfile noconsole norescan
+                       prompt revprompt revunmerged title
+                       " "$pfx" "$cur"
+               return
+               ;;
+       difftool.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               __gitcomp "cmd path" "$pfx" "$cur"
+               return
+               ;;
+       man.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               __gitcomp "cmd path" "$pfx" "$cur"
+               return
+               ;;
+       mergetool.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               __gitcomp "cmd path trustExitCode" "$pfx" "$cur"
+               return
+               ;;
+       pager.*)
+               local pfx="${cur%.*}."
+               cur="${cur#*.}"
+               __gitcomp "$(__git_all_commands)" "$pfx" "$cur"
+               return
+               ;;
        remote.*.*)
                local pfx="${cur%.*}."
                cur="${cur##*.}"
@@ -1208,8 +1476,15 @@ _git_config ()
                __gitcomp "$(__git_remotes)" "$pfx" "$cur" "."
                return
                ;;
+       url.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               __gitcomp "insteadof" "$pfx" "$cur"
+               return
+               ;;
        esac
        __gitcomp "
+               alias.
                apply.whitespace
                branch.autosetupmerge
                branch.autosetuprebase
@@ -1227,6 +1502,9 @@ _git_config ()
                color.diff.old
                color.diff.plain
                color.diff.whitespace
+               color.grep
+               color.grep.external
+               color.grep.match
                color.interactive
                color.interactive.header
                color.interactive.help
@@ -1244,6 +1522,7 @@ _git_config ()
                core.autocrlf
                core.bare
                core.compression
+               core.createObject
                core.deltaBaseCacheLimit
                core.editor
                core.excludesfile
@@ -1274,11 +1553,21 @@ _git_config ()
                diff.renameLimit
                diff.renameLimit.
                diff.renames
+               diff.suppressBlankEmpty
+               diff.tool
+               diff.wordRegex
+               difftool.
+               difftool.prompt
                fetch.unpackLimit
+               format.attach
+               format.cc
                format.headers
                format.numbered
                format.pretty
+               format.signoff
+               format.subjectprefix
                format.suffix
+               format.thread
                gc.aggressiveWindow
                gc.auto
                gc.autopacklimit
@@ -1289,6 +1578,7 @@ _git_config ()
                gc.rerereresolved
                gc.rerereunresolved
                gitcvs.allbinary
+               gitcvs.commitmsgannotation
                gitcvs.dbTableNamePrefix
                gitcvs.dbdriver
                gitcvs.dbname
@@ -1297,6 +1587,7 @@ _git_config ()
                gitcvs.enabled
                gitcvs.logfile
                gitcvs.usecrlfattr
+               guitool.
                gui.blamehistoryctx
                gui.commitmsgwidth
                gui.copyblamethreshold
@@ -1323,13 +1614,24 @@ _git_config ()
                http.sslVerify
                i18n.commitEncoding
                i18n.logOutputEncoding
+               imap.folder
+               imap.host
+               imap.pass
+               imap.port
+               imap.preformattedHTML
+               imap.sslverify
+               imap.tunnel
+               imap.user
                instaweb.browser
                instaweb.httpd
                instaweb.local
                instaweb.modulepath
                instaweb.port
+               interactive.singlekey
                log.date
                log.showroot
+               mailmap.file
+               man.
                man.viewer
                merge.conflictstyle
                merge.log
@@ -1337,7 +1639,9 @@ _git_config ()
                merge.stat
                merge.tool
                merge.verbosity
+               mergetool.
                mergetool.keepBackup
+               mergetool.prompt
                pack.compression
                pack.deltaCacheLimit
                pack.deltaCacheSize
@@ -1347,8 +1651,11 @@ _git_config ()
                pack.threads
                pack.window
                pack.windowMemory
+               pager.
                pull.octopus
                pull.twohead
+               push.default
+               rebase.stat
                receive.denyCurrentBranch
                receive.denyDeletes
                receive.denyNonFastForwards
@@ -1357,11 +1664,32 @@ _git_config ()
                repack.usedeltabaseoffset
                rerere.autoupdate
                rerere.enabled
+               sendemail.aliasesfile
+               sendemail.aliasesfiletype
+               sendemail.bcc
+               sendemail.cc
+               sendemail.cccmd
+               sendemail.chainreplyto
+               sendemail.confirm
+               sendemail.envelopesender
+               sendemail.multiedit
+               sendemail.signedoffbycc
+               sendemail.smtpencryption
+               sendemail.smtppass
+               sendemail.smtpserver
+               sendemail.smtpserverport
+               sendemail.smtpuser
+               sendemail.suppresscc
+               sendemail.suppressfrom
+               sendemail.thread
+               sendemail.to
+               sendemail.validate
                showbranch.default
                status.relativePaths
                status.showUntrackedFiles
                tar.umask
                transfer.unpackLimit
+               url.
                user.email
                user.name
                user.signingkey
@@ -1372,7 +1700,7 @@ _git_config ()
 
 _git_remote ()
 {
-       local subcommands="add rm show prune update"
+       local subcommands="add rename rm show prune update set-head"
        local subcommand="$(__git_find_subcommand "$subcommands")"
        if [ -z "$subcommand" ]; then
                __gitcomp "$subcommands"
@@ -1380,7 +1708,7 @@ _git_remote ()
        fi
 
        case "$subcommand" in
-       rm|show|prune)
+       rename|rm|show|prune)
                __gitcomp "$(__git_remotes)"
                ;;
        update)
@@ -1408,7 +1736,7 @@ _git_reset ()
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --*)
-               __gitcomp "--mixed --hard --soft"
+               __gitcomp "--merge --mixed --hard --soft"
                return
                ;;
        esac
@@ -1449,12 +1777,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
@@ -1470,13 +1794,19 @@ _git_show ()
        local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
        --pretty=*)
-               __gitcomp "
-                       oneline short medium full fuller email raw
+               __gitcomp "$__git_log_pretty_formats
                        " "" "${cur##--pretty=}"
                return
                ;;
+       --format=*)
+               __gitcomp "$__git_log_pretty_formats
+                       " "" "${cur##--format=}"
+               return
+               ;;
        --*)
-               __gitcomp "--pretty="
+               __gitcomp "--pretty= --format= --abbrev-commit --oneline
+                       $__git_diff_common_options
+                       "
                return
                ;;
        esac
@@ -1491,7 +1821,7 @@ _git_show_branch ()
                __gitcomp "
                        --all --remotes --topo-order --current --more=
                        --list --independent --merge-base --no-name
-                       --sha1-name --topics --reflog
+                       --sha1-name --sparse --topics --reflog
                        "
                return
                ;;
@@ -1552,7 +1882,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
@@ -1563,13 +1894,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=
@@ -1589,7 +1922,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,--*)
@@ -1603,13 +1937,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,--*)
@@ -1618,6 +1952,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=()
                        ;;
@@ -1677,7 +2026,6 @@ _git ()
 
        if [ -z "$command" ]; then
                case "${COMP_WORDS[COMP_CWORD]}" in
-               --*=*) COMPREPLY=() ;;
                --*)   __gitcomp "
                        --paginate
                        --no-pager
@@ -1685,6 +2033,7 @@ _git ()
                        --bare
                        --version
                        --exec-path
+                       --html-path
                        --work-tree=
                        --help
                        "
@@ -1714,8 +2063,10 @@ _git ()
        config)      _git_config ;;
        describe)    _git_describe ;;
        diff)        _git_diff ;;
+       difftool)    _git_difftool ;;
        fetch)       _git_fetch ;;
        format-patch) _git_format_patch ;;
+       fsck)        _git_fsck ;;
        gc)          _git_gc ;;
        grep)        _git_grep ;;
        help)        _git_help ;;
@@ -1741,6 +2092,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 ;;
@@ -1754,27 +2106,34 @@ _gitk ()
        __git_has_doubledash && return
 
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       local g="$(git rev-parse --git-dir 2>/dev/null)"
+       local g="$(__gitdir)"
        local merge=""
        if [ -f "$g/MERGE_HEAD" ]; then
                merge="--merge"
        fi
        case "$cur" in
        --*)
-               __gitcomp "--not --all $merge"
+               __gitcomp "
+                       $__git_log_common_options
+                       $__git_log_gitk_options
+                       $merge
+                       "
                return
                ;;
        esac
        __git_complete_revlist
 }
 
-complete -o default -o nospace -F _git git
-complete -o default -o nospace -F _gitk gitk
+complete -o bashdefault -o default -o nospace -F _git git 2>/dev/null \
+       || complete -o default -o nospace -F _git git
+complete -o bashdefault -o default -o nospace -F _gitk gitk 2>/dev/null \
+       || complete -o default -o nospace -F _gitk gitk
 
 # The following are necessary only for Cygwin, and only are needed
 # when the user has tab-completed the executable name and consequently
 # included the '.exe' suffix.
 #
 if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
-complete -o default -o nospace -F _git git.exe
+complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
+       || complete -o default -o nospace -F _git git.exe
 fi
index 90e7900e6d7aff2fadf9ba04f8d982733493411c..f3b57bf1d21e8256bf084076e50cbc9ff84a1ee8 100644 (file)
@@ -59,7 +59,7 @@ static void convert_ascii_sha1(void *buffer)
        struct entry *entry;
 
        if (get_sha1_hex(buffer, sha1))
-               die("expected sha1, got '%s'", (char*) buffer);
+               die("expected sha1, got '%s'", (char *) buffer);
        entry = convert_entry(sha1);
        memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
 }
@@ -100,7 +100,7 @@ static int write_subdirectory(void *buffer, unsigned long size, const char *base
                if (!slash) {
                        newlen += sprintf(new + newlen, "%o %s", mode, path);
                        new[newlen++] = '\0';
-                       hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20);
+                       hashcpy((unsigned char *)new + newlen, (unsigned char *) buffer + len - 20);
                        newlen += 20;
 
                        used += len;
@@ -271,7 +271,7 @@ static void convert_commit(void *buffer, unsigned long size, unsigned char *resu
        unsigned long orig_size = size;
 
        if (memcmp(buffer, "tree ", 5))
-               die("Bad commit '%s'", (char*) buffer);
+               die("Bad commit '%s'", (char *) buffer);
        convert_ascii_sha1((char *) buffer + 5);
        buffer = (char *) buffer + 46;    /* "tree " + "hex sha1" + "\n" */
        while (!memcmp(buffer, "parent ", 7)) {
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..342529db309821f461e8f77d05bc5e01c76802ec 100755 (executable)
@@ -442,13 +442,14 @@ def p4ChangesForPaths(depotPaths, changeRange):
     output = p4_read_pipe_lines("changes " + ' '.join (["%s...%s" % (p, changeRange)
                                                         for p in depotPaths]))
 
-    changes = []
+    changes = {}
     for line in output:
-        changeNum = line.split(" ")[1]
-        changes.append(int(changeNum))
+       changeNum = int(line.split(" ")[1])
+       changes[changeNum] = True
 
-    changes.sort()
-    return changes
+    changelist = changes.keys()
+    changelist.sort()
+    return changelist
 
 class Command:
     def __init__(self):
@@ -1141,7 +1142,7 @@ class P4Sync(Command):
 
         s = ''
         for (key, val) in self.users.items():
-            s += "%s\t%s\n" % (key, val)
+           s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
 
         open(self.getUserCacheFilename(), "wb").write(s)
         self.userMapFromPerforceServer = True
index 23aeb257b9557cb146586868084ce1b20d7e7ac8..6309d146e74a428520d09cbff4dc8d9a429ec001 100755 (executable)
@@ -14,13 +14,18 @@ die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV;
 
 my $branch_name = 'import-tars';
 my $branch_ref = "refs/heads/$branch_name";
-my $committer_name = 'T Ar Creator';
-my $committer_email = 'tar@example.com';
+my $author_name = $ENV{'GIT_AUTHOR_NAME'} || 'T Ar Creator';
+my $author_email = $ENV{'GIT_AUTHOR_EMAIL'} || 'tar@example.com';
+my $committer_name = $ENV{'GIT_COMMITTER_NAME'} || `git config --get user.name`;
+my $committer_email = $ENV{'GIT_COMMITTER_EMAIL'} || `git config --get user.email`;
+
+chomp($committer_name, $committer_email);
 
 open(FI, '|-', 'git', 'fast-import', '--quiet')
        or die "Unable to start git fast-import: $!\n";
 foreach my $tar_file (@ARGV)
 {
+       my $commit_time = time;
        $tar_file =~ m,([^/]+)$,;
        my $tar_name = $1;
 
@@ -39,7 +44,7 @@ foreach my $tar_file (@ARGV)
                die "Unrecognized compression format: $tar_file\n";
        }
 
-       my $commit_time = 0;
+       my $author_time = 0;
        my $next_mark = 1;
        my $have_top_dir = 1;
        my ($top_dir, %files);
@@ -92,7 +97,7 @@ foreach my $tar_file (@ARGV)
                }
                $files{$path} = [$next_mark++, $mode];
 
-               $commit_time = $mtime if $mtime > $commit_time;
+               $author_time = $mtime if $mtime > $author_time;
                $path =~ m,^([^/]+)/,;
                $top_dir = $1 unless $top_dir;
                $have_top_dir = 0 if $top_dir ne $1;
@@ -100,6 +105,7 @@ foreach my $tar_file (@ARGV)
 
        print FI <<EOF;
 commit $branch_ref
+author $author_name <$author_email> $author_time +0000
 committer $committer_name <$committer_email> $commit_time +0000
 data <<END_OF_COMMIT_MESSAGE
 Imported from $tar_file.
@@ -119,7 +125,7 @@ EOF
        print FI <<EOF;
 tag $tar_name
 from $branch_ref
-tagger $committer_name <$committer_email> $commit_time +0000
+tagger $author_name <$author_email> $author_time +0000
 data <<END_OF_TAG_MESSAGE
 Package $tar_name
 END_OF_TAG_MESSAGE
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 39f96aa115e0a20024d2f41138db6b2b8c6d5320..000147bbe4a00525d68efb1358c013812e10dcca 100644 (file)
@@ -1,12 +1,12 @@
 appp.sh is a script that is supposed to be used together with ExternalEditor
-for Mozilla Thundebird. It will let you include patches inline in e-mails
+for Mozilla Thunderbird. It will let you include patches inline in e-mails
 in an easy way.
 
 Usage:
 - Generate the patch with git format-patch.
 - Start writing a new e-mail in Thunderbird.
 - Press the external editor button (or Ctrl-E) to run appp.sh
-- Select the previosly generated patch file.
+- Select the previously generated patch file.
 - Finish editing the e-mail.
 
 Any text that is entered into the message editor before appp.sh is called
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..7ee64c7d77dd4a5665f70d80ffba1bcdecb9a408 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..daa4c8e8c95924c6fb446884b3ed44c454425b54 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -229,7 +229,7 @@ static char *path_ok(char *directory)
        }
 
        if (!path) {
-               logerror("'%s': unable to chdir or not a git archive", dir);
+               logerror("'%s' does not appear to be a git repository", dir);
                return NULL;
        }
 
@@ -444,27 +444,27 @@ static void parse_extra_args(char *extra_args, int buflen)
        if (hostname) {
 #ifndef NO_IPV6
                struct addrinfo hints;
-               struct addrinfo *ai, *ai0;
+               struct addrinfo *ai;
                int gai;
                static char addrbuf[HOST_NAME_MAX + 1];
 
                memset(&hints, 0, sizeof(hints));
                hints.ai_flags = AI_CANONNAME;
 
-               gai = getaddrinfo(hostname, 0, &hints, &ai0);
+               gai = getaddrinfo(hostname, 0, &hints, &ai);
                if (!gai) {
-                       for (ai = ai0; ai; ai = ai->ai_next) {
-                               struct sockaddr_in *sin_addr = (void *)ai->ai_addr;
-
-                               inet_ntop(AF_INET, &sin_addr->sin_addr,
-                                         addrbuf, sizeof(addrbuf));
-                               free(canon_hostname);
-                               canon_hostname = xstrdup(ai->ai_canonname);
-                               free(ip_address);
-                               ip_address = xstrdup(addrbuf);
-                               break;
-                       }
-                       freeaddrinfo(ai0);
+                       struct sockaddr_in *sin_addr = (void *)ai->ai_addr;
+
+                       inet_ntop(AF_INET, &sin_addr->sin_addr,
+                                 addrbuf, sizeof(addrbuf));
+                       free(ip_address);
+                       ip_address = xstrdup(addrbuf);
+
+                       free(canon_hostname);
+                       canon_hostname = xstrdup(ai->ai_canonname ?
+                                                ai->ai_canonname : ip_address);
+
+                       freeaddrinfo(ai);
                }
 #else
                struct hostent *hent;
@@ -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..409a17d46424083b62f21e7e278393cee7bfa080 100644 (file)
--- a/date.c
+++ b/date.c
@@ -89,6 +89,11 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        struct tm *tm;
        static char timebuf[200];
 
+       if (mode == DATE_RAW) {
+               snprintf(timebuf, sizeof(timebuf), "%lu %+05d", time, tz);
+               return timebuf;
+       }
+
        if (mode == DATE_RELATIVE) {
                unsigned long diff;
                struct timeval now;
@@ -128,7 +133,25 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
                        snprintf(timebuf, sizeof(timebuf), "%lu months ago", (diff + 15) / 30);
                        return timebuf;
                }
-               /* Else fall back on absolute format.. */
+               /* Give years and months for 5 years or so */
+               if (diff < 1825) {
+                       unsigned long years = (diff + 183) / 365;
+                       unsigned long months = (diff % 365 + 15) / 30;
+                       int n;
+                       n = snprintf(timebuf, sizeof(timebuf), "%lu year%s",
+                                       years, (years > 1 ? "s" : ""));
+                       if (months)
+                               snprintf(timebuf + n, sizeof(timebuf) - n,
+                                       ", %lu month%s ago",
+                                       months, (months > 1 ? "s" : ""));
+                       else
+                               snprintf(timebuf + n, sizeof(timebuf) - n,
+                                       " ago");
+                       return timebuf;
+               }
+               /* Otherwise, just years. Centuries is probably overkill. */
+               snprintf(timebuf, sizeof(timebuf), "%lu years ago", (diff + 183) / 365);
+               return timebuf;
        }
 
        if (mode == DATE_LOCAL)
@@ -615,6 +638,8 @@ enum date_mode parse_date_format(const char *format)
                return DATE_LOCAL;
        else if (!strcmp(format, "default"))
                return DATE_NORMAL;
+       else if (!strcmp(format, "raw"))
+               return DATE_RAW;
        else
                die("unknown date format %s", format);
 }
@@ -846,13 +871,15 @@ unsigned long approxidate(const char *date)
        int number = 0;
        struct tm tm, now;
        struct timeval tv;
+       time_t time_sec;
        char buffer[50];
 
        if (parse_date(date, buffer, sizeof(buffer)) > 0)
                return strtoul(buffer, NULL, 10);
 
        gettimeofday(&tv, NULL);
-       localtime_r(&tv.tv_sec, &tm);
+       time_sec = tv.tv_sec;
+       localtime_r(&time_sec, &tm);
        now = tm;
        for (;;) {
                unsigned char c = *date;
index 82d9e221eabab53acc418d0db6327e480836a5ed..e6fd8a7441a7ac6753d93e7156b9f71fe248262d 100644 (file)
@@ -8,7 +8,9 @@
 
 static unsigned int hash_obj(const struct object *obj, unsigned int n)
 {
-       unsigned int hash = *(unsigned int *)obj->sha1;
+       unsigned int hash;
+
+       memcpy(&hash, obj->sha1, sizeof(unsigned int));
        return hash % n;
 }
 
index d230efc146a73ae84cfd11c829c8404345d77273..0aba6cda3c01e17b07bb7235b0395ba50256aedd 100644 (file)
@@ -31,7 +31,7 @@ static int check_removed(const struct cache_entry *ce, struct stat *st)
                        return -1;
                return 1;
        }
-       if (has_symlink_leading_path(ce_namelen(ce), ce->name))
+       if (has_symlink_leading_path(ce->name, ce_namelen(ce)))
                return 1;
        if (S_ISDIR(st->st_mode)) {
                unsigned char sub[20];
@@ -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 a3e47a76e401a1b98891b15a406b5806a294af3d..4ebc1dbd87d9b90ad2aaa5c7d4601761e2d1d724 100644 (file)
@@ -38,9 +38,13 @@ static int get_mode(const char *path, int *mode)
 
        if (!path || !strcmp(path, "/dev/null"))
                *mode = 0;
+#ifdef _WIN32
+       else if (!strcasecmp(path, "nul"))
+               *mode = 0;
+#endif
        else if (!strcmp(path, "-"))
                *mode = create_ce_mode(0666);
-       else if (stat(path, &st))
+       else if (lstat(path, &st))
                return error("Could not access '%s'", path);
        else
                *mode = st.st_mode;
@@ -229,7 +233,7 @@ void diff_no_index(struct rev_info *revs,
        if (prefix) {
                int len = strlen(prefix);
 
-               revs->diffopt.paths = xcalloc(2, sizeof(char*));
+               revs->diffopt.paths = xcalloc(2, sizeof(char *));
                for (i = 0; i < 2; i++) {
                        const char *p = argv[argc - 2 + i];
                        /*
diff --git a/diff.c b/diff.c
index 387d19fdedef650516ea9fd7679f48e3776a7616..d24bff1e465d5b5e486ab461bd0548387736f109 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -12,6 +12,7 @@
 #include "run-command.h"
 #include "utf8.h"
 #include "userdiff.h"
+#include "sigchain.h"
 
 #ifdef NO_FAST_WORKING_DIRECTORY
 #define FAST_WORKING_DIRECTORY 0
@@ -23,19 +24,20 @@ static int diff_detect_rename_default;
 static int diff_rename_limit_default = 200;
 static int diff_suppress_blank_empty;
 int diff_use_color_default = -1;
+static const char *diff_word_regex_cfg;
 static const char *external_diff_cmd_cfg;
 int diff_auto_refresh_index = 1;
 static int diff_mnemonic_prefix;
 
 static char diff_colors[][COLOR_MAXLEN] = {
-       "\033[m",       /* reset */
-       "",             /* PLAIN (normal) */
-       "\033[1m",      /* METAINFO (bold) */
-       "\033[36m",     /* FRAGINFO (cyan) */
-       "\033[31m",     /* OLD (red) */
-       "\033[32m",     /* NEW (green) */
-       "\033[33m",     /* COMMIT (yellow) */
-       "\033[41m",     /* WHITESPACE (red background) */
+       GIT_COLOR_RESET,
+       GIT_COLOR_NORMAL,       /* PLAIN */
+       GIT_COLOR_BOLD,         /* METAINFO */
+       GIT_COLOR_CYAN,         /* FRAGINFO */
+       GIT_COLOR_RED,          /* OLD */
+       GIT_COLOR_GREEN,        /* NEW */
+       GIT_COLOR_YELLOW,       /* COMMIT */
+       GIT_COLOR_BG_RED,       /* WHITESPACE */
 };
 
 static void diff_filespec_load_driver(struct diff_filespec *one);
@@ -60,6 +62,15 @@ static int parse_diff_color_slot(const char *var, int ofs)
        die("bad config variable '%s'", var);
 }
 
+static int git_config_rename(const char *var, const char *value)
+{
+       if (!value)
+               return DIFF_DETECT_RENAME;
+       if (!strcasecmp(value, "copies") || !strcasecmp(value, "copy"))
+               return  DIFF_DETECT_COPY;
+       return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0;
+}
+
 /*
  * These are to give UI layer defaults.
  * The core-level commands such as git-diff-files should
@@ -73,13 +84,7 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
                return 0;
        }
        if (!strcmp(var, "diff.renames")) {
-               if (!value)
-                       diff_detect_rename_default = DIFF_DETECT_RENAME;
-               else if (!strcasecmp(value, "copies") ||
-                        !strcasecmp(value, "copy"))
-                       diff_detect_rename_default = DIFF_DETECT_COPY;
-               else if (git_config_bool(var,value))
-                       diff_detect_rename_default = DIFF_DETECT_RENAME;
+               diff_detect_rename_default = git_config_rename(var, value);
                return 0;
        }
        if (!strcmp(var, "diff.autorefreshindex")) {
@@ -92,6 +97,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 +174,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_or_warn(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 +355,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 +497,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 +550,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;
        }
@@ -785,9 +876,9 @@ static void fill_print_name(struct diffstat_file *file)
        file->print_name = pname;
 }
 
-static void show_stats(struct diffstat_tdata, struct diff_options *options)
+static void show_stats(struct diffstat_t *data, struct diff_options *options)
 {
-       int i, len, add, del, total, adds = 0, dels = 0;
+       int i, len, add, del, adds = 0, dels = 0;
        int max_change = 0, max_len = 0;
        int total_files = data->nr;
        int width, name_width;
@@ -890,14 +981,12 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
                 */
                add = added;
                del = deleted;
-               total = add + del;
                adds += add;
                dels += del;
 
                if (width <= max_change) {
                        add = scale_linear(add, width, max_change);
                        del = scale_linear(del, width, max_change);
-                       total = add + del;
                }
                show_name(options->file, prefix, name, len, reset, set);
                fprintf(options->file, "%5d%s", added + deleted,
@@ -936,7 +1025,7 @@ static void show_shortstats(struct diffstat_t* data, struct diff_options *option
               total_files, adds, dels);
 }
 
-static void show_numstat(struct diffstat_tdata, struct diff_options *options)
+static void show_numstat(struct diffstat_t *data, struct diff_options *options)
 {
        int i;
 
@@ -1325,6 +1414,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 +1566,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 +1580,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);
@@ -1846,17 +1957,23 @@ void diff_free_filespec_data(struct diff_filespec *s)
        s->cnt_data = NULL;
 }
 
-static void prep_temp_blob(struct diff_tempfile *temp,
+static void prep_temp_blob(const char *path, struct diff_tempfile *temp,
                           void *blob,
                           unsigned long size,
                           const unsigned char *sha1,
                           int mode)
 {
        int fd;
+       struct strbuf buf = STRBUF_INIT;
 
        fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX");
        if (fd < 0)
                die("unable to create temp-file: %s", strerror(errno));
+       if (convert_to_working_tree(path,
+                       (const char *)blob, (size_t)size, &buf)) {
+               blob = buf.buf;
+               size = buf.len;
+       }
        if (write_in_full(fd, blob, size) != size)
                die("unable to write temp-file");
        close(fd);
@@ -1864,12 +1981,14 @@ static void prep_temp_blob(struct diff_tempfile *temp,
        strcpy(temp->hex, sha1_to_hex(sha1));
        temp->hex[40] = 0;
        sprintf(temp->mode, "%06o", mode);
+       strbuf_release(&buf);
 }
 
-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
@@ -1878,7 +1997,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 ||
@@ -1897,7 +2022,7 @@ static void prepare_temp_file(const char *name,
                                die("readlink(%s)", name);
                        if (ret == sizeof(buf))
                                die("symlink too long: %s", name);
-                       prep_temp_blob(temp, buf, ret,
+                       prep_temp_blob(name, temp, buf, ret,
                                       (one->sha1_valid ?
                                        one->sha1 : null_sha1),
                                       (one->sha1_valid ?
@@ -1918,32 +2043,15 @@ static void prepare_temp_file(const char *name,
                         */
                        sprintf(temp->mode, "%06o", one->mode);
                }
-               return;
+               return temp;
        }
        else {
                if (diff_populate_filespec(one, 0))
                        die("cannot read data blob for %s", one->path);
-               prep_temp_blob(temp, one->data, one->size,
+               prep_temp_blob(name, 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:
@@ -1961,34 +2069,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;
@@ -2119,7 +2215,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
@@ -2246,15 +2342,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) {
@@ -2490,11 +2583,13 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 
        /* xdiff options */
        else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE);
        else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE_CHANGE);
        else if (!strcmp(arg, "--ignore-space-at-eol"))
-               options->xdl_opts |= XDF_IGNORE_WHITESPACE_AT_EOL;
+               DIFF_XDL_SET(options, IGNORE_WHITESPACE_AT_EOL);
+       else if (!strcmp(arg, "--patience"))
+               DIFF_XDL_SET(options, PATIENCE_DIFF);
 
        /* flags options */
        else if (!strcmp(arg, "--binary")) {
@@ -2515,8 +2610,15 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                DIFF_OPT_SET(options, COLOR_DIFF);
        else if (!strcmp(arg, "--no-color"))
                DIFF_OPT_CLR(options, COLOR_DIFF);
-       else if (!strcmp(arg, "--color-words"))
-               options->flags |= DIFF_OPT_COLOR_DIFF | DIFF_OPT_COLOR_DIFF_WORDS;
+       else if (!strcmp(arg, "--color-words")) {
+               DIFF_OPT_SET(options, COLOR_DIFF);
+               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+       }
+       else if (!prefixcmp(arg, "--color-words=")) {
+               DIFF_OPT_SET(options, COLOR_DIFF);
+               DIFF_OPT_SET(options, COLOR_DIFF_WORDS);
+               options->word_regex = arg + 14;
+       }
        else if (!strcmp(arg, "--exit-code"))
                DIFF_OPT_SET(options, EXIT_WITH_STATUS);
        else if (!strcmp(arg, "--quiet"))
@@ -2562,6 +2664,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;
@@ -3468,15 +3573,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));
@@ -3485,13 +3590,11 @@ static char *run_textconv(const char *pgm, struct diff_filespec *spec,
        if (start_command(&child) != 0 ||
            strbuf_read(&buf, child.out, 0) < 0 ||
            finish_command(&child) != 0) {
-               if (temp.name == temp.tmp_path)
-                       unlink(temp.name);
+               remove_tempfile();
                error("error running textconv command '%s'", pgm);
                return NULL;
        }
-       if (temp.name == temp.tmp_path)
-               unlink(temp.name);
+       remove_tempfile();
 
        return strbuf_detach(&buf, outsize);
 }
diff --git a/diff.h b/diff.h
index 42582edee68a4a4717ae5debebf37e6b9610fc8f..6616877ee5d15a8101cbdea0271cc18f8837d2f1 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -69,6 +69,9 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_OPT_TST(opts, flag)    ((opts)->flags & DIFF_OPT_##flag)
 #define DIFF_OPT_SET(opts, flag)    ((opts)->flags |= DIFF_OPT_##flag)
 #define DIFF_OPT_CLR(opts, flag)    ((opts)->flags &= ~DIFF_OPT_##flag)
+#define DIFF_XDL_TST(opts, flag)    ((opts)->xdl_opts & XDF_##flag)
+#define DIFF_XDL_SET(opts, flag)    ((opts)->xdl_opts |= XDF_##flag)
+#define DIFF_XDL_CLR(opts, flag)    ((opts)->xdl_opts &= ~XDF_##flag)
 
 struct diff_options {
        const char *filter;
@@ -78,6 +81,7 @@ struct diff_options {
        const char *a_prefix, *b_prefix;
        unsigned flags;
        int context;
+       int interhunkcontext;
        int break_opt;
        int detect_rename;
        int skip_stat_unmatch;
@@ -97,6 +101,7 @@ struct diff_options {
 
        int stat_width;
        int stat_name_width;
+       const char *word_regex;
 
        /* this is set by diffcore for DIFF_FORMAT_PATCH */
        int found_changes;
@@ -263,4 +268,6 @@ extern int diff_result_code(struct diff_options *, int);
 
 extern void diff_no_index(struct rev_info *, int, const char **, int, const char *);
 
+extern int index_differs_from(const char *def, int diff_flags);
+
 #endif /* DIFF_H */
index 31cdcfe8bcdae7df65b0387071846299a14bb7be..d7097bb576cef8900e8e0218ba82e1cc7a87a567 100644 (file)
@@ -45,7 +45,7 @@ static int should_break(struct diff_filespec *src,
         * The value we return is 1 if we want the pair to be broken,
         * or 0 if we do not.
         */
-       unsigned long delta_size, base_size, max_size;
+       unsigned long delta_size, max_size;
        unsigned long src_copied, literal_added, src_removed;
 
        *merge_score_p = 0; /* assume no deletion --- "do not break"
@@ -64,7 +64,6 @@ static int should_break(struct diff_filespec *src,
        if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
                return 0; /* error but caught downstream */
 
-       base_size = ((src->size < dst->size) ? src->size : dst->size);
        max_size = ((src->size > dst->size) ? src->size : dst->size);
        if (max_size < MINIMUM_BREAK_SIZE)
                return 0; /* we do not break too small filepair */
index af9fffe6e8e145b066157da8791c749257e7c8e9..d0ef8397008824fb5139680856e3229ecf2c4eb1 100644 (file)
@@ -10,7 +10,7 @@ static unsigned int contains(struct diff_filespec *one,
                             regex_t *regexp)
 {
        unsigned int cnt;
-       unsigned long offset, sz;
+       unsigned long sz;
        const char *data;
        if (diff_populate_filespec(one, 0))
                return 0;
@@ -25,23 +25,23 @@ static unsigned int contains(struct diff_filespec *one,
                regmatch_t regmatch;
                int flags = 0;
 
+               assert(data[sz] == '\0');
                while (*data && !regexec(regexp, data, 1, &regmatch, flags)) {
                        flags |= REG_NOTBOL;
-                       data += regmatch.rm_so;
-                       if (*data) data++;
+                       data += regmatch.rm_eo;
+                       if (*data && regmatch.rm_so == regmatch.rm_eo)
+                               data++;
                        cnt++;
                }
 
        } else { /* Classic exact string match */
-               /* Yes, I've heard of strstr(), but the thing is *data may
-                * not be NUL terminated.  Sue me.
-                */
-               for (offset = 0; offset + len <= sz; offset++) {
-                       /* we count non-overlapping occurrences of needle */
-                       if (!memcmp(needle, data + offset, len)) {
-                               offset += len - 1;
-                               cnt++;
-                       }
+               while (sz) {
+                       const char *found = memmem(data, sz, needle, len);
+                       if (!found)
+                               break;
+                       sz -= found - data + len;
+                       data = found + len;
+                       cnt++;
                }
        }
        diff_free_filespec_data(one);
index 0b0d6b8c8c2ab8833bb5d929ef0d3cb7891ec582..63ac998bfaf64da807bec0ae0bd57c391fb651fe 100644 (file)
@@ -267,7 +267,7 @@ static int find_identical_files(struct file_similarity *src,
                        int score;
                        struct diff_filespec *source = p->filespec;
 
-                       /* False hash collission? */
+                       /* False hash collision? */
                        if (hashcmp(source->sha1, target->sha1))
                                continue;
                        /* Non-regular files? If so, the modes must match! */
diff --git a/dir.c b/dir.c
index 53ad921a0b93a70c7614dcdab25c6ef3e9a0c8b6..0e6b752cd5833bfb970619983b4efa880aaaf134 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;
@@ -153,7 +156,7 @@ void add_exclude(const char *string, const char *base,
        if (len && string[len - 1] == '/') {
                char *s;
                x = xmalloc(sizeof(*x) + len);
-               s = (char*)(x+1);
+               s = (char *)(x+1);
                memcpy(s, string, len - 1);
                s[len - 1] = '\0';
                string = s;
@@ -484,14 +487,14 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
                return recurse_into_directory;
 
        case index_gitdir:
-               if (dir->show_other_directories)
+               if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
                        return ignore_directory;
                return show_directory;
 
        case index_nonexistent:
-               if (dir->show_other_directories)
+               if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
                        break;
-               if (!dir->no_gitlinks) {
+               if (!(dir->flags & DIR_NO_GITLINKS)) {
                        unsigned char sha1[20];
                        if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
                                return show_directory;
@@ -500,7 +503,7 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
        }
 
        /* This is the "show_other_directories" case */
-       if (!dir->hide_empty_directories)
+       if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
                return show_directory;
        if (!read_directory_recursive(dir, dirname, dirname, len, 1, simplify))
                return ignore_directory;
@@ -573,7 +576,7 @@ static int get_dtype(struct dirent *de, const char *path)
  */
 static int read_directory_recursive(struct dir_struct *dir, const char *path, const char *base, int baselen, int check_only, const struct path_simplify *simplify)
 {
-       DIR *fdir = opendir(path);
+       DIR *fdir = opendir(*path ? path : ".");
        int contents = 0;
 
        if (fdir) {
@@ -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! */
@@ -600,7 +601,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
 
                        dtype = DTYPE(de);
                        exclude = excluded(dir, fullname, &dtype);
-                       if (exclude && dir->collect_ignored
+                       if (exclude && (dir->flags & DIR_COLLECT_IGNORED)
                            && in_pathspec(fullname, baselen + len, simplify))
                                dir_add_ignored(dir, fullname, baselen + len);
 
@@ -608,7 +609,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                         * Excluded? If we don't explicitly want to show
                         * ignored files, ignore it
                         */
-                       if (exclude && !dir->show_ignored)
+                       if (exclude && !(dir->flags & DIR_SHOW_IGNORED))
                                continue;
 
                        if (dtype == DT_UNKNOWN)
@@ -620,7 +621,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                         * even if we don't ignore them, since the
                         * directory may contain files that we do..
                         */
-                       if (!exclude && dir->show_ignored) {
+                       if (!exclude && (dir->flags & DIR_SHOW_IGNORED)) {
                                if (dtype != DT_DIR)
                                        continue;
                        }
@@ -633,7 +634,8 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                                len++;
                                switch (treat_directory(dir, fullname, baselen + len, simplify)) {
                                case show_directory:
-                                       if (exclude != dir->show_ignored)
+                                       if (exclude != !!(dir->flags
+                                                       & DIR_SHOW_IGNORED))
                                                continue;
                                        break;
                                case recurse_into_directory:
@@ -680,7 +682,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;
        }
 }
@@ -719,7 +721,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
 {
        struct path_simplify *simplify;
 
-       if (has_symlink_leading_path(strlen(path), path))
+       if (has_symlink_leading_path(path, strlen(path)))
                return dir->nr;
 
        simplify = create_simplify(pathspec);
@@ -779,6 +781,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 +814,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..541286ad1daeb66a9963288d275534ee963bd5cd 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -34,11 +34,13 @@ struct exclude_stack {
 struct dir_struct {
        int nr, alloc;
        int ignored_nr, ignored_alloc;
-       unsigned int show_ignored:1,
-                    show_other_directories:1,
-                    hide_empty_directories:1,
-                    no_gitlinks:1,
-                    collect_ignored:1;
+       enum {
+               DIR_SHOW_IGNORED = 1<<0,
+               DIR_SHOW_OTHER_DIRECTORIES = 1<<1,
+               DIR_HIDE_EMPTY_DIRECTORIES = 1<<2,
+               DIR_NO_GITLINKS = 1<<3,
+               DIR_COLLECT_IGNORED = 1<<4
+       } flags;
        struct dir_entry **entries;
        struct dir_entry **ignored;
 
@@ -77,6 +79,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..cc841edf9051460b3a382ea25c0097f245ec8884 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,46 +1,41 @@
 #include "cache.h"
 #include "blob.h"
+#include "dir.h"
 
-static void create_directories(const char *path, const struct checkout *state)
+static void create_directories(const char *path, int path_len,
+                              const struct checkout *state)
 {
-       int len = strlen(path);
-       char *buf = xmalloc(len + 1);
-       const char *slash = path;
-
-       while ((slash = strchr(slash+1, '/')) != NULL) {
-               struct stat st;
-               int stat_status;
-
-               len = slash - path;
-               memcpy(buf, path, len);
+       char *buf = xmalloc(path_len + 1);
+       int len = 0;
+
+       while (len < path_len) {
+               do {
+                       buf[len] = path[len];
+                       len++;
+               } while (len < path_len && path[len] != '/');
+               if (len >= path_len)
+                       break;
                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(buf, len, 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 &&
-                           !unlink(buf) && !mkdir(buf, 0777))
+                           !unlink_or_warn(buf) && !mkdir(buf, 0777))
                                continue;
                        die("cannot create directory at %s", buf);
                }
@@ -62,9 +57,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))
@@ -85,7 +78,7 @@ static int create_file(const char *path, unsigned int mode)
        return open(path, O_WRONLY | O_CREAT | O_EXCL, mode);
 }
 
-static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned long *size)
+static void *read_blob_entry(struct cache_entry *ce, unsigned long *size)
 {
        enum object_type type;
        void *new = read_sha1_file(ce->sha1, &type, size);
@@ -100,36 +93,52 @@ static void *read_blob_entry(struct cache_entry *ce, const char *path, unsigned
 
 static int write_entry(struct cache_entry *ce, char *path, const struct checkout *state, int to_tempfile)
 {
-       int fd;
-       long wrote;
-
-       switch (ce->ce_mode & S_IFMT) {
-               char *new;
-               struct strbuf buf;
-               unsigned long size;
+       unsigned int ce_mode_s_ifmt = ce->ce_mode & S_IFMT;
+       int fd, ret, fstat_done = 0;
+       char *new;
+       struct strbuf buf = STRBUF_INIT;
+       unsigned long size;
+       size_t wrote, newsize = 0;
+       struct stat st;
 
+       switch (ce_mode_s_ifmt) {
        case S_IFREG:
-               new = read_blob_entry(ce, path, &size);
+       case S_IFLNK:
+               new = read_blob_entry(ce, &size);
                if (!new)
                        return error("git checkout-index: unable to read sha1 file of %s (%s)",
                                path, sha1_to_hex(ce->sha1));
 
+               if (ce_mode_s_ifmt == S_IFLNK && has_symlinks && !to_tempfile) {
+                       ret = symlink(new, path);
+                       free(new);
+                       if (ret)
+                               return error("git checkout-index: unable to create symlink %s (%s)",
+                                            path, strerror(errno));
+                       break;
+               }
+
                /*
                 * Convert from git internal format to working tree format
                 */
-               strbuf_init(&buf, 0);
-               if (convert_to_working_tree(ce->name, new, size, &buf)) {
-                       size_t newsize = 0;
+               if (ce_mode_s_ifmt == S_IFREG &&
+                   convert_to_working_tree(ce->name, new, size, &buf)) {
                        free(new);
                        new = strbuf_detach(&buf, &newsize);
                        size = newsize;
                }
 
                if (to_tempfile) {
-                       strcpy(path, ".merge_file_XXXXXX");
+                       if (ce_mode_s_ifmt == S_IFREG)
+                               strcpy(path, ".merge_file_XXXXXX");
+                       else
+                               strcpy(path, ".merge_link_XXXXXX");
                        fd = mkstemp(path);
-               } else
+               } else if (ce_mode_s_ifmt == S_IFREG) {
                        fd = create_file(path, ce->ce_mode);
+               } else {
+                       fd = create_file(path, 0666);
+               }
                if (fd < 0) {
                        free(new);
                        return error("git checkout-index: unable to create file %s (%s)",
@@ -137,41 +146,17 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                }
 
                wrote = write_in_full(fd, new, size);
+               /* use fstat() only when path == ce->name */
+               if (fstat_is_reliable() &&
+                   state->refresh_cache && !to_tempfile && !state->base_dir_len) {
+                       fstat(fd, &st);
+                       fstat_done = 1;
+               }
                close(fd);
                free(new);
                if (wrote != size)
                        return error("git checkout-index: unable to write file %s", path);
                break;
-       case S_IFLNK:
-               new = read_blob_entry(ce, path, &size);
-               if (!new)
-                       return error("git checkout-index: unable to read sha1 file of %s (%s)",
-                               path, sha1_to_hex(ce->sha1));
-               if (to_tempfile || !has_symlinks) {
-                       if (to_tempfile) {
-                               strcpy(path, ".merge_link_XXXXXX");
-                               fd = mkstemp(path);
-                       } else
-                               fd = create_file(path, 0666);
-                       if (fd < 0) {
-                               free(new);
-                               return error("git checkout-index: unable to create "
-                                                "file %s (%s)", path, strerror(errno));
-                       }
-                       wrote = write_in_full(fd, new, size);
-                       close(fd);
-                       free(new);
-                       if (wrote != size)
-                               return error("git checkout-index: unable to write file %s",
-                                       path);
-               } else {
-                       wrote = symlink(new, path);
-                       free(new);
-                       if (wrote)
-                               return error("git checkout-index: unable to create "
-                                                "symlink %s (%s)", path, strerror(errno));
-               }
-               break;
        case S_IFGITLINK:
                if (to_tempfile)
                        return error("git checkout-index: cannot create temporary subproject %s", path);
@@ -183,8 +168,8 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
        }
 
        if (state->refresh_cache) {
-               struct stat st;
-               lstat(ce->name, &st);
+               if (!fstat_done)
+                       lstat(ce->name, &st);
                fill_stat_cache_info(ce, &st);
        }
        return 0;
@@ -201,6 +186,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
 
        memcpy(path, state->base_dir, len);
        strcpy(path + len, ce->name);
+       len += ce_namelen(ce);
 
        if (!lstat(path, &st)) {
                unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
@@ -229,6 +215,6 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
                        return error("unable to unlink old '%s' (%s)", path, strerror(errno));
        } else if (state->not_new)
                return 0;
-       create_directories(path, state);
+       create_directories(path, len, state);
        return write_entry(ce, path, state, 0);
 }
index e278bce0ea5f1ddda2dab9012663c6d1d4c6bd89..801a005ef1b23ef13cfa9ece676c550fe35dedc0 100644 (file)
@@ -42,6 +42,11 @@ enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
 unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
 enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
 enum rebase_setup_type autorebase = AUTOREBASE_NEVER;
+enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;
+#ifndef OBJECT_CREATION_MODE
+#define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS
+#endif
+enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;
 
 /* Parallel index stat data preload? */
 int core_preload_index = 0;
index 351fec2e9e374eacaeba88d2a7dddf11f5e331ce..408e4e55e1c58931444c772d35d23b505bf3e2ea 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)
@@ -65,9 +101,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 f246d5347b4131f274edad5c7c687fb7ddbbd99e..a2a24588a99957d5af759ca6d8858366430ab3a3 100644 (file)
@@ -1,4 +1,5 @@
 /*
+(See Documentation/git-fast-import.txt for maintained documentation.)
 Format of STDIN stream:
 
   stream ::= cmd*;
@@ -18,8 +19,8 @@ Format of STDIN stream:
 
   new_commit ::= 'commit' sp ref_str lf
     mark?
-    ('author' sp name '<' email '>' when lf)?
-    'committer' sp name '<' email '>' when lf
+    ('author' sp name sp '<' email '>' sp when lf)?
+    'committer' sp name sp '<' email '>' sp when lf
     commit_msg
     ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
     ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)*
@@ -43,7 +44,7 @@ Format of STDIN stream:
 
   new_tag ::= 'tag' sp tag_str lf
     'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf
-    ('tagger' sp name '<' email '>' when lf)?
+    ('tagger' sp name sp '<' email '>' sp when lf)?
     tag_msg;
   tag_msg ::= data;
 
@@ -75,7 +76,7 @@ Format of STDIN stream:
     delim lf;
 
      # note: declen indicates the length of binary_data in bytes.
-     # declen does not include the lf preceeding the binary data.
+     # declen does not include the lf preceding the binary data.
      #
   exact_data ::= 'data' sp declen lf
     binary_data;
@@ -132,8 +133,8 @@ Format of STDIN stream:
      # always escapes the related input from comment processing.
      #
      # In case it is not clear, the '#' that starts the comment
-     # must be the first character on that the line (an lf have
-     # preceeded it).
+     # must be the first character on that line (an lf
+     # preceded it).
      #
   comment ::= '#' not_lf* lf;
   not_lf  ::= # Any byte that is not ASCII newline (LF);
@@ -150,6 +151,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)
@@ -210,7 +212,7 @@ struct tree_content;
 struct tree_entry
 {
        struct tree_content *tree;
-       struct atom_strname;
+       struct atom_str *name;
        struct tree_entry_ms
        {
                uint16_t mode;
@@ -311,7 +313,7 @@ static unsigned int object_entry_alloc = 5000;
 static struct object_entry_pool *blocks;
 static struct object_entry *object_table[1 << 16];
 static struct mark_set *marks;
-static const charmark_file;
+static const char *mark_file;
 
 /* Our last blob */
 static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 };
@@ -670,7 +672,7 @@ static struct branch *lookup_branch(const char *name)
 static struct branch *new_branch(const char *name)
 {
        unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz;
-       struct branchb = lookup_branch(name);
+       struct branch *b = lookup_branch(name);
 
        if (b)
                die("Invalid attempt to create duplicate branch: %s", name);
@@ -867,7 +869,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;
@@ -901,9 +903,6 @@ static char *keep_pack(char *curr_index_name)
        static const char *keep_msg = "fast-import";
        int keep_fd;
 
-       chmod(pack_data->pack_name, 0444);
-       chmod(curr_index_name, 0444);
-
        keep_fd = odb_pack_keep(name, sizeof(name), pack_data->sha1);
        if (keep_fd < 0)
                die("cannot create keep file");
@@ -932,7 +931,7 @@ static void unkeep_all_packs(void)
                struct packed_git *p = all_packs[k];
                snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
                         get_object_directory(), sha1_to_hex(p->sha1));
-               unlink(name);
+               unlink_or_warn(name);
        }
 }
 
@@ -954,7 +953,7 @@ static void end_packfile(void)
                close(pack_data->pack_fd);
                idx_name = keep_pack(create_index());
 
-               /* Register the packfile with core git's machinary. */
+               /* Register the packfile with core git's machinery. */
                new_p = add_packed_git(idx_name, strlen(idx_name), 1);
                if (!new_p)
                        die("core git rejected index %s", idx_name);
@@ -982,7 +981,7 @@ static void end_packfile(void)
        }
        else {
                close(old_p->pack_fd);
-               unlink(old_p->pack_name);
+               unlink_or_warn(old_p->pack_name);
        }
        free(old_p);
 
@@ -1036,7 +1035,7 @@ static int store_object(
        git_SHA_CTX c;
        z_stream s;
 
-       hdrlen = sprintf((char*)hdr,"%s %lu", typename(type),
+       hdrlen = sprintf((char *)hdr,"%s %lu", typename(type),
                (unsigned long)dat->len) + 1;
        git_SHA1_Init(&c);
        git_SHA1_Update(&c, hdr, hdrlen);
@@ -1218,7 +1217,7 @@ static const char *get_mode(const char *str, uint16_t *modep)
 
 static void load_tree(struct tree_entry *root)
 {
-       unsigned charsha1 = root->versions[1].sha1;
+       unsigned char *sha1 = root->versions[1].sha1;
        struct object_entry *myoe;
        struct tree_content *t;
        unsigned long size;
@@ -1259,8 +1258,8 @@ static void load_tree(struct tree_entry *root)
                e->versions[0].mode = e->versions[1].mode;
                e->name = to_atom(c, strlen(c));
                c += e->name->str_len + 1;
-               hashcpy(e->versions[0].sha1, (unsigned char*)c);
-               hashcpy(e->versions[1].sha1, (unsigned char*)c);
+               hashcpy(e->versions[0].sha1, (unsigned char *)c);
+               hashcpy(e->versions[1].sha1, (unsigned char *)c);
                c += 20;
        }
        free(buf);
@@ -1744,21 +1743,19 @@ static void parse_data(struct strbuf *sb)
 static int validate_raw_date(const char *src, char *result, int maxlen)
 {
        const char *orig_src = src;
-       char *endp, sign;
-       unsigned long date;
+       char *endp;
 
        errno = 0;
 
-       date = strtoul(src, &endp, 10);
+       strtoul(src, &endp, 10);
        if (errno || endp == src || *endp != ' ')
                return -1;
 
        src = endp + 1;
        if (*src != '-' && *src != '+')
                return -1;
-       sign = *src;
 
-       date = strtoul(src + 1, &endp, 10);
+       strtoul(src + 1, &endp, 10);
        if (errno || endp == src || *endp || (endp - orig_src) >= maxlen)
                return -1;
 
@@ -2403,6 +2400,8 @@ int main(int argc, const char **argv)
 {
        unsigned int i, show_stats = 1;
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
        git_config(git_pack_config, NULL);
        if (!pack_compression_seen && core_compression_seen)
diff --git a/fsck.c b/fsck.c
index 97f76c58155249412d7c59e965b564dfc0f75181..511b82cba9977c14d6dcba5efbd6d38591d3aacc 100644 (file)
--- a/fsck.c
+++ b/fsck.c
@@ -148,20 +148,17 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
        struct tree_desc desc;
        unsigned o_mode;
        const char *o_name;
-       const unsigned char *o_sha1;
 
        init_tree_desc(&desc, item->buffer, item->size);
 
        o_mode = 0;
        o_name = NULL;
-       o_sha1 = NULL;
 
        while (desc.size) {
                unsigned mode;
                const char *name;
-               const unsigned char *sha1;
 
-               sha1 = tree_entry_extract(&desc, &name, &mode);
+               tree_entry_extract(&desc, &name, &mode);
 
                if (strchr(name, '/'))
                        has_full_path = 1;
@@ -207,7 +204,6 @@ static int fsck_tree(struct tree *item, int strict, fsck_error error_func)
 
                o_mode = mode;
                o_name = name;
-               o_sha1 = sha1;
        }
 
        retval = 0;
diff --git a/fsck.h b/fsck.h
index 990ee02335a2e2693e32baa82b259c23843f2aa0..008456b675b6e5c8a5a07e0b8628a41e3acc733a 100644 (file)
--- a/fsck.h
+++ b/fsck.h
@@ -23,7 +23,7 @@ int fsck_error_function(struct object *obj, int type, const char *fmt, ...);
  * the return value is:
  *    -1       error in processing the object
  *    <0       return value of the callback, which lead to an abort
- *    >0       return value of the first sigaled error >0 (in the case of no other errors)
+ *    >0       return value of the first signaled error >0 (in the case of no other errors)
  *    0                everything OK
  */
 int fsck_walk(struct object *obj, fsck_walk_func walk, void *data);
index b0223c3419301132032fb67519a275e57707df22..f6e536ece314316ebb281721ed27d7519577202f 100755 (executable)
@@ -3,6 +3,8 @@
 use strict;
 use Git;
 
+binmode(STDOUT, ":raw");
+
 my $repo = Git->repository();
 
 my $menu_use_color = $repo->get_colorbool('color.interactive');
@@ -12,6 +14,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 +42,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("", @_);
@@ -73,6 +93,47 @@ if (!defined $GIT_DIR) {
 }
 chomp($GIT_DIR);
 
+my %cquote_map = (
+ "b" => chr(8),
+ "t" => chr(9),
+ "n" => chr(10),
+ "v" => chr(11),
+ "f" => chr(12),
+ "r" => chr(13),
+ "\\" => "\\",
+ "\042" => "\042",
+);
+
+sub unquote_path {
+       local ($_) = @_;
+       my ($retval, $remainder);
+       if (!/^\042(.*)\042$/) {
+               return $_;
+       }
+       ($_, $retval) = ($1, "");
+       while (/^([^\\]*)\\(.*)$/) {
+               $remainder = $2;
+               $retval .= $1;
+               for ($remainder) {
+                       if (/^([0-3][0-7][0-7])(.*)$/) {
+                               $retval .= chr(oct($1));
+                               $_ = $2;
+                               last;
+                       }
+                       if (/^([\\\042btnvfr])(.*)$/) {
+                               $retval .= $cquote_map{$1};
+                               $_ = $2;
+                               last;
+                       }
+                       # This is malformed -- just return it as-is for now.
+                       return $_[0];
+               }
+               $_ = $remainder;
+       }
+       $retval .= $_;
+       return $retval;
+}
+
 sub refresh {
        my $fh;
        open $fh, 'git update-index --refresh |'
@@ -86,7 +147,7 @@ sub refresh {
 sub list_untracked {
        map {
                chomp $_;
-               $_;
+               unquote_path($_);
        }
        run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
 }
@@ -123,7 +184,8 @@ sub list_modified {
 
        if (@ARGV) {
                @tracked = map {
-                       chomp $_; $_;
+                       chomp $_;
+                       unquote_path($_);
                } run_cmd_pipe(qw(git ls-files --exclude-standard --), @ARGV);
                return if (!@tracked);
        }
@@ -135,6 +197,7 @@ sub list_modified {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
                        my ($change, $bin);
+                       $file = unquote_path($file);
                        if ($add eq '-' && $del eq '-') {
                                $change = 'binary';
                                $bin = 1;
@@ -150,6 +213,7 @@ sub list_modified {
                }
                elsif (($adddel, $file) =
                       /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $file = unquote_path($file);
                        $data{$file}{INDEX_ADDDEL} = $adddel;
                }
        }
@@ -157,6 +221,7 @@ sub list_modified {
        for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       $file = unquote_path($file);
                        if (!exists $data{$file}) {
                                $data{$file} = +{
                                        INDEX => 'unchanged',
@@ -178,6 +243,7 @@ sub list_modified {
                }
                elsif (($adddel, $file) =
                       /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $file = unquote_path($file);
                        $data{$file}{FILE_ADDDEL} = $adddel;
                }
        }
@@ -284,7 +350,8 @@ sub find_unique_prefixes {
                        }
                        %search = %{$search{$letter}};
                }
-               if ($soft_limit && $j + 1 > $soft_limit) {
+               if (ord($letters[0]) > 127 ||
+                   ($soft_limit && $j + 1 > $soft_limit)) {
                        $prefix = undef;
                        $remainder = $ret;
                }
@@ -325,6 +392,10 @@ sub highlight_prefix {
        return "$prompt_color$prefix$normal_color$remainder";
 }
 
+sub error_msg {
+       print STDERR colored $error_color, @_;
+}
+
 sub list_and_choose {
        my ($opts, @stuff) = @_;
        my (@chosen, @return);
@@ -420,12 +491,12 @@ sub list_and_choose {
                        else {
                                $bottom = $top = find_unique($choice, @stuff);
                                if (!defined $bottom) {
-                                       print "Huh ($choice)?\n";
+                                       error_msg "Huh ($choice)?\n";
                                        next TOPLOOP;
                                }
                        }
                        if ($opts->{SINGLETON} && $bottom != $top) {
-                               print "Huh ($choice)?\n";
+                               error_msg "Huh ($choice)?\n";
                                next TOPLOOP;
                        }
                        for ($i = $bottom-1; $i <= $top-1; $i++) {
@@ -549,11 +620,12 @@ sub parse_diff {
        if ($diff_use_color) {
                @colored = run_cmd_pipe(qw(git diff-files -p --color --), $path);
        }
-       my (@hunk) = { TEXT => [], DISPLAY => [] };
+       my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
 
        for (my $i = 0; $i < @diff; $i++) {
                if ($diff[$i] =~ /^@@ /) {
-                       push @hunk, { TEXT => [], DISPLAY => [] };
+                       push @hunk, { TEXT => [], DISPLAY => [],
+                               TYPE => 'hunk' };
                }
                push @{$hunk[-1]{TEXT}}, $diff[$i];
                push @{$hunk[-1]{DISPLAY}},
@@ -565,8 +637,8 @@ sub parse_diff {
 sub parse_diff_header {
        my $src = shift;
 
-       my $head = { TEXT => [], DISPLAY => [] };
-       my $mode = { TEXT => [], DISPLAY => [] };
+       my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
+       my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
 
        for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
                my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ?
@@ -613,6 +685,7 @@ sub split_hunk {
                my $this = +{
                        TEXT => [],
                        DISPLAY => [],
+                       TYPE => 'hunk',
                        OLD => $o_ofs,
                        NEW => $n_ofs,
                        OCNT => 0,
@@ -731,6 +804,10 @@ EOF
                || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
        system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
 
+       if ($? != 0) {
+               return undef;
+       }
+
        open $fh, '<', $hunkfile
                or die "failed to open hunk edit file for reading: " . $!;
        my @newtext = grep { !/^#/ } <$fh>;
@@ -758,11 +835,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;
        }
@@ -777,7 +875,11 @@ sub edit_hunk_loop {
                if (!defined $text) {
                        return undef;
                }
-               my $newhunk = { TEXT => $text, USE => 1 };
+               my $newhunk = {
+                       TEXT => $text,
+                       TYPE => $hunk->[$ix]->{TYPE},
+                       USE => 1
+               };
                if (diff_applies($head,
                                 @{$hunk}[0..$ix-1],
                                 $newhunk,
@@ -798,8 +900,11 @@ sub help_patch_cmd {
        print colored $help_color, <<\EOF ;
 y - stage this hunk
 n - do not stage this hunk
+q - quit, do not stage this hunk nor any of the remaining ones
 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
@@ -832,11 +937,53 @@ sub patch_update_cmd {
                                        @mods);
        }
        for (@them) {
-               patch_update_file($_->{VALUE});
+               return 0 if patch_update_file($_->{VALUE});
+       }
+}
+
+# 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 $quit = 0;
        my ($ix, $num);
        my $path = shift;
        my ($head, @hunk) = parse_diff($path);
@@ -846,32 +993,7 @@ sub patch_update_file {
        }
 
        if (@{$mode->{TEXT}}) {
-               while (1) {
-                       print @{$mode->{DISPLAY}};
-                       print colored $prompt_color,
-                               "Stage mode change [y/n/a/d/?]? ";
-                       my $line = <STDIN>;
-                       if ($line =~ /^y/i) {
-                               $mode->{USE} = 1;
-                               last;
-                       }
-                       elsif ($line =~ /^n/i) {
-                               $mode->{USE} = 0;
-                               last;
-                       }
-                       elsif ($line =~ /^a/i) {
-                               $_->{USE} = 1 foreach ($mode, @hunk);
-                               last;
-                       }
-                       elsif ($line =~ /^d/i) {
-                               $_->{USE} = 0 foreach ($mode, @hunk);
-                               last;
-                       }
-                       else {
-                               help_patch_cmd('');
-                               next;
-                       }
-               }
+               unshift @hunk, $mode;
        }
 
        $num = scalar @hunk;
@@ -887,22 +1009,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}) {
@@ -912,15 +1037,20 @@ sub patch_update_file {
                }
                last if (!$undecided);
 
-               if (hunk_splittable($hunk[$ix]{TEXT})) {
-                       $other .= '/s';
+               if ($hunk[$ix]{TYPE} eq 'hunk' &&
+                   hunk_splittable($hunk[$ix]{TEXT})) {
+                       $other .= ',s';
+               }
+               if ($hunk[$ix]{TYPE} eq 'hunk') {
+                       $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 ',
+                 ($hunk[$ix]{TYPE} eq 'mode' ? 'mode change' : 'this hunk'),
+                 " [y,n,q,a,d,/$other,?]? ";
+               my $line = prompt_single_character;
                if ($line) {
                        if ($line =~ /^y/i) {
                                $hunk[$ix]{USE} = 1;
@@ -937,6 +1067,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 +1101,86 @@ sub patch_update_file {
                                }
                                next;
                        }
-                       elsif ($other =~ /K/ && $line =~ /^K/) {
-                               $ix--;
+                       elsif ($line =~ /^q/i) {
+                               while ($ix < $num) {
+                                       if (!defined $hunk[$ix]{USE}) {
+                                               $hunk[$ix]{USE} = 0;
+                                       }
+                                       $ix++;
+                               }
+                               $quit = 1;
                                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) {
@@ -980,7 +1191,7 @@ sub patch_update_file {
                                $num = scalar @hunk;
                                next;
                        }
-                       elsif ($line =~ /^e/) {
+                       elsif ($other =~ /e/ && $line =~ /^e/) {
                                my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
                                if (defined $newhunk) {
                                        splice @hunk, $ix, 1, $newhunk;
@@ -1001,9 +1212,6 @@ sub patch_update_file {
 
        my $n_lofs = 0;
        my @result = ();
-       if ($mode->{USE}) {
-               push @result, @{$mode->{TEXT}};
-       }
        for (@hunk) {
                if ($_->{USE}) {
                        push @result, @{$_->{TEXT}};
@@ -1026,6 +1234,7 @@ sub patch_update_file {
        }
 
        print "\n";
+       return $quit;
 }
 
 sub diff_cmd {
index 4b157fe5d536fdbdbf85e0e06c419b5927a90867..6d1848b6cce89e4953a3ca6e1b2e6e1611277a4a 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,21 @@ cd_to_toplevel
 git var GIT_COMMITTER_IDENT >/dev/null ||
        die "You need to set your committer info first"
 
+if git rev-parse --verify -q HEAD >/dev/null
+then
+       HAS_HEAD=yes
+else
+       HAS_HEAD=
+fi
+
+sq () {
+       for sqarg
+       do
+               printf "%s" "$sqarg" |
+               sed -e 's/'\''/'\''\\'\'''\''/g' -e 's/.*/ '\''&'\''/'
+       done
+}
+
 stop_here () {
     echo "$1" >"$dotest/next"
     exit 1
@@ -124,6 +142,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 +175,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 +218,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 +228,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 +239,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" = "" ||
@@ -261,16 +297,27 @@ else
                : >"$dotest/rebasing"
        else
                : >"$dotest/applying"
-               git update-ref ORIG_HEAD HEAD
+               if test -n "$HAS_HEAD"
+               then
+                       git update-ref ORIG_HEAD HEAD
+               else
+                       git update-ref -d ORIG_HEAD >/dev/null 2>&1
+               fi
        fi
 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
+       case "$HAS_HEAD" in
+       '')
+               files=$(git ls-files) ;;
+       ?*)
+               files=$(git diff-index --cached --name-only HEAD --) ;;
+       esac || exit
+       if test "$files"
+       then
+               test -n "$HAS_HEAD" && : >"$dotest/dirtyindex"
+               die "Dirty index: cannot apply patches (dirty: $files)"
        fi
 esac
 
@@ -459,7 +506,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 +548,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
 
@@ -511,8 +558,21 @@ do
        fi
 
        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
+               parent=$(git rev-parse --verify -q HEAD) ||
+               echo >&2 "applying to an empty history"
+
+               if test -n "$committer_date_is_author_date"
+               then
+                       GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
+                       export GIT_COMMITTER_DATE
+               fi &&
+               git commit-tree $tree ${parent:+-p} $parent <"$dotest/final-commit"
+       ) &&
        git update-ref -m "$GIT_REFLOG_ACTION: $FIRSTLINE" HEAD $commit $parent ||
        stop_here $this
 
index 10ad340920efb7177df53cb3a209d1a3edd5a039..24712ff304af89317793fa4c54d39f4c579bb345 100755 (executable)
@@ -77,7 +77,7 @@ bisect_start() {
        then
                # Reset to the rev from where we started.
                start_head=$(cat "$GIT_DIR/BISECT_START")
-               git checkout "$start_head" || exit
+               git checkout "$start_head" -- || exit
        else
                # Get rev from where we start.
                case "$head" in
@@ -279,87 +279,14 @@ bisect_auto_next() {
        bisect_next_check && bisect_next || :
 }
 
-filter_skipped() {
-       _eval="$1"
-       _skip="$2"
-
-       if [ -z "$_skip" ]; then
-               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" | {
-               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*)
-                               # We had nothing to search.
-                               echo "bisect_rev= &&"
-                               VARS=1
-                               ;;
-                       ,,*,bisect_rev*)
-                               # 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.
-                               TRIED="${TRIED:+$TRIED|}$hash"
-                               case "$_skip" in
-                               *$hash*) ;;
-                               *)
-                                       echo "bisect_rev=$hash &&"
-                                       echo "bisect_tried='$TRIED' &&"
-                                       FOUND=1
-                                       ;;
-                               esac
-                               ;;
-                       ,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 () {
        _tried=$1
-       if expr "$_tried" : ".*[|].*" > /dev/null ; then
+       _bad=$2
+       if test -n "$_tried" ; then
                echo "There are only 'skip'ped commit left to test."
                echo "The first bad commit could be any of:"
                echo "$_tried" | tr '[|]' '[\012]'
+               test -n "$_bad" && echo "$_bad"
                echo "We cannot bisect more!"
                exit 2
        fi
@@ -370,7 +297,7 @@ bisect_checkout() {
        _msg="$2"
        echo "Bisecting: $_msg"
        mark_expected_rev "$_rev"
-       git checkout -q "$_rev" || exit
+       git checkout -q "$_rev" -- || exit
        git show-branch "$_rev"
 }
 
@@ -490,29 +417,24 @@ bisect_next() {
        test "$?" -eq "1" && return
 
        # Get bisection information
-       BISECT_OPT=''
-       test -n "$skip" && BISECT_OPT='--bisect-all'
-       eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
-       eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
-       eval=$(filter_skipped "$eval" "$skip") &&
+       eval=$(eval "git bisect--helper --next-vars") &&
        eval "$eval" || exit
 
        if [ -z "$bisect_rev" ]; then
+               # We should exit here only if the "bad"
+               # commit is also a "skip" commit (see above).
+               exit_if_skipped_commits "$bisect_tried"
                echo "$bad was both good and bad"
                exit 1
        fi
        if [ "$bisect_rev" = "$bad" ]; then
-               exit_if_skipped_commits "$bisect_tried"
+               exit_if_skipped_commits "$bisect_tried" "$bad"
                echo "$bisect_rev is first bad commit"
                git diff-tree --pretty $bisect_rev
                exit 0
        fi
 
-       # We should exit here only if the "bad"
-       # commit is also a "skip" commit (see above).
-       exit_if_skipped_commits "$bisect_rev"
-
-       bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this"
+       bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this (roughly $bisect_steps steps)"
 }
 
 bisect_visualize() {
@@ -549,7 +471,7 @@ bisect_reset() {
        *)
            usage ;;
        esac
-       git checkout "$branch" && bisect_clean_state
+       git checkout "$branch" -- && bisect_clean_state
 }
 
 bisect_clean_state() {
index 124bb94b153f13e4be5e17814b67115ff31d1587..c7cf2d5d9cdbfd4ed20c8b8ea49a36af7c138a4e 100644 (file)
@@ -46,6 +46,7 @@
 #define _ALL_SOURCE 1
 #define _GNU_SOURCE 1
 #define _BSD_SOURCE 1
+#define _NETBSD_SOURCE 1
 
 #include <unistd.h>
 #include <stdio.h>
@@ -166,7 +167,7 @@ static inline const char *skip_prefix(const char *str, const char *prefix)
        return strncmp(str, prefix, len) ? NULL : str + len;
 }
 
-#ifdef NO_MMAP
+#if defined(NO_MMAP) || defined(USE_WIN32_MMAP)
 
 #ifndef PROT_READ
 #define PROT_READ 1
@@ -180,13 +181,19 @@ static inline const char *skip_prefix(const char *str, const char *prefix)
 extern void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
 extern int git_munmap(void *start, size_t length);
 
+#else /* NO_MMAP || USE_WIN32_MMAP */
+
+#include <sys/mman.h>
+
+#endif /* NO_MMAP || USE_WIN32_MMAP */
+
+#ifdef NO_MMAP
+
 /* This value must be multiple of (pagesize * 2) */
 #define DEFAULT_PACKED_GIT_WINDOW_SIZE (1 * 1024 * 1024)
 
 #else /* NO_MMAP */
 
-#include <sys/mman.h>
-
 /* This value must be multiple of (pagesize * 2) */
 #define DEFAULT_PACKED_GIT_WINDOW_SIZE \
        (sizeof(void*) >= 8 \
@@ -319,6 +326,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
@@ -329,13 +337,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)
 
@@ -384,4 +395,30 @@ void git_qsort(void *base, size_t nmemb, size_t size,
 # define FORCE_DIR_SET_GID 0
 #endif
 
+#ifdef NO_NSEC
+#undef USE_NSEC
+#define ST_CTIME_NSEC(st) 0
+#define ST_MTIME_NSEC(st) 0
+#else
+#ifdef USE_ST_TIMESPEC
+#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctimespec.tv_nsec))
+#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtimespec.tv_nsec))
+#else
+#define ST_CTIME_NSEC(st) ((unsigned int)((st).st_ctim.tv_nsec))
+#define ST_MTIME_NSEC(st) ((unsigned int)((st).st_mtim.tv_nsec))
+#endif
+#endif
+
+#ifdef UNRELIABLE_FSTAT
+#define fstat_is_reliable() 0
+#else
+#define fstat_is_reliable() 1
+#endif
+
+/*
+ * Preserves errno, prints a message, but gives no warning for ENOENT.
+ * Always returns the return value of unlink(2).
+ */
+int unlink_or_warn(const char *path);
+
 #endif
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;
 }
diff --git a/git-difftool--helper.sh b/git-difftool--helper.sh
new file mode 100755 (executable)
index 0000000..57e8e32
--- /dev/null
@@ -0,0 +1,59 @@
+#!/bin/sh
+# git-difftool--helper is a GIT_EXTERNAL_DIFF-compatible diff tool launcher.
+# This script is typically launched by using the 'git difftool'
+# convenience command.
+#
+# Copyright (c) 2009 David Aguilar
+
+# Load common functions from git-mergetool--lib
+TOOL_MODE=diff
+. git-mergetool--lib
+
+# difftool.prompt controls the default prompt/no-prompt behavior
+# and is overridden with $GIT_DIFFTOOL*_PROMPT.
+should_prompt () {
+       prompt=$(git config --bool difftool.prompt || echo true)
+       if test "$prompt" = true; then
+               test -z "$GIT_DIFFTOOL_NO_PROMPT"
+       else
+               test -n "$GIT_DIFFTOOL_PROMPT"
+       fi
+}
+
+# Sets up shell variables and runs a 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"
+
+       # $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
+       run_merge_tool "$merge_tool"
+}
+
+# Allow GIT_DIFF_TOOL and GIT_MERGE_TOOL to provide default values
+test -n "$GIT_MERGE_TOOL" && merge_tool="$GIT_MERGE_TOOL"
+test -n "$GIT_DIFF_TOOL" && merge_tool="$GIT_DIFF_TOOL"
+
+if test -z "$merge_tool"; then
+       merge_tool="$(get_merge_tool)" || exit
+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/git-difftool.perl b/git-difftool.perl
new file mode 100755 (executable)
index 0000000..ba5e60a
--- /dev/null
@@ -0,0 +1,92 @@
+#!/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.
+# GIT_DIFFTOOL_NO_PROMPT, GIT_DIFFTOOL_PROMPT, and GIT_DIFF_TOOL
+# are exported 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>] [-y|--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;
+       if ($^O eq 'MSWin32' || $^O eq 'msys') {
+               return "$exe.exe";
+       }
+       return $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' || $arg eq '--tool') {
+                       usage() if $#ARGV <= $idx;
+                       $ENV{GIT_DIFF_TOOL} = $ARGV[$idx + 1];
+                       $skip_next = 1;
+                       next;
+               }
+               if ($arg =~ /^--tool=/) {
+                       $ENV{GIT_DIFF_TOOL} = substr($arg, 7);
+                       next;
+               }
+               if ($arg eq '-y' || $arg eq '--no-prompt') {
+                       $ENV{GIT_DIFFTOOL_NO_PROMPT} = 'true';
+                       delete $ENV{GIT_DIFFTOOL_PROMPT};
+                       next;
+               }
+               if ($arg eq '--prompt') {
+                       $ENV{GIT_DIFFTOOL_PROMPT} = 'true';
+                       delete $ENV{GIT_DIFFTOOL_NO_PROMPT};
+                       next;
+               }
+               if ($arg eq '-h' || $arg eq '--help') {
+                       usage();
+               }
+               push @command, $arg;
+       }
+       return @command
+}
+
+setup_environment();
+
+# ActiveState Perl for Win32 does not implement POSIX semantics of
+# exec* system call. It just spawns the given executable and finishes
+# the starting program, exiting with code 0.
+# system will at least catch the errors returned by git diff,
+# allowing the caller of git difftool better handling of failures.
+my $rc = system(generate_command());
+exit($rc | ($rc >> 8));
index 0897b5971a770755527817d4cb3766d87ce5bebd..37e044db40bb89470bb64a4108c86d5173e03b5a 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
 
@@ -109,11 +119,12 @@ filter_tree=
 filter_index=
 filter_parent=
 filter_msg=cat
-filter_commit='git commit-tree "$@"'
+filter_commit=
 filter_tag_name=
 filter_subdir=
 orig_namespace=refs/original/
 force=
+prune_empty=
 while :
 do
        case "$1" in
@@ -126,6 +137,11 @@ do
                force=t
                continue
                ;;
+       --prune-empty)
+               shift
+               prune_empty=t
+               continue
+               ;;
        -*)
                ;;
        *)
@@ -176,6 +192,17 @@ do
        esac
 done
 
+case "$prune_empty,$filter_commit" in
+,)
+       filter_commit='git commit-tree "$@"';;
+t,)
+       filter_commit="$functions;"' git_commit_non_empty_tree "$@"';;
+,*)
+       ;;
+*)
+       die "Cannot set --prune-empty and --filter-commit at the same time"
+esac
+
 case "$force" in
 t)
        rm -rf "$tempdir"
@@ -193,13 +220,21 @@ die ""
 # Remove tempdir on exit
 trap 'cd ../..; rm -rf "$tempdir"' 0
 
+ORIG_GIT_DIR="$GIT_DIR"
+ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
+ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
+GIT_WORK_TREE=.
+export GIT_DIR GIT_WORK_TREE
+
 # Make sure refs/original is empty
-git for-each-ref > "$tempdir"/backup-refs
+git for-each-ref > "$tempdir"/backup-refs || exit
 while read sha1 type name
 do
        case "$force,$name" in
        ,$orig_namespace*)
-               die "Namespace $orig_namespace not empty"
+               die "Cannot create a new backup.
+A previous backup already exists in $orig_namespace
+Force overwriting the backup with -f"
        ;;
        t,$orig_namespace*)
                git update-ref -d "$name" $sha1
@@ -207,15 +242,10 @@ do
        esac
 done < "$tempdir"/backup-refs
 
-ORIG_GIT_DIR="$GIT_DIR"
-ORIG_GIT_WORK_TREE="$GIT_WORK_TREE"
-ORIG_GIT_INDEX_FILE="$GIT_INDEX_FILE"
-GIT_WORK_TREE=.
-export GIT_DIR GIT_WORK_TREE
-
 # The refs should be updated if their heads were rewritten
-git rev-parse --no-flags --revs-only --symbolic-full-name --default HEAD "$@" |
-sed -e '/^^/d' >"$tempdir"/heads
+git rev-parse --no-flags --revs-only --symbolic-full-name \
+       --default HEAD "$@" > "$tempdir"/raw-heads || exit
+sed -e '/^^/d' "$tempdir"/raw-heads >"$tempdir"/heads
 
 test -s "$tempdir"/heads ||
        die "Which ref do you want to rewrite?"
@@ -224,8 +254,6 @@ GIT_INDEX_FILE="$(pwd)/../index"
 export GIT_INDEX_FILE
 git read-tree || die "Could not seed the index"
 
-ret=0
-
 # map old->new commit ids for rewriting parents
 mkdir ../map || die "Could not create map/ directory"
 
@@ -244,10 +272,10 @@ test $commits -eq 0 && die "Found nothing to rewrite"
 
 # Rewrite the commits
 
-i=0
+git_filter_branch__commit_count=0
 while read commit parents; do
-       i=$(($i+1))
-       printf "\rRewrite $commit ($i/$commits)"
+       git_filter_branch__commit_count=$(($git_filter_branch__commit_count+1))
+       printf "\rRewrite $commit ($git_filter_branch__commit_count/$commits)"
 
        case "$filter_subdir" in
        "")
@@ -288,10 +316,11 @@ while read commit parents; do
                        die "tree filter failed: $filter_tree"
 
                (
-                       git diff-index -r --name-only $commit
+                       git diff-index -r --name-only $commit &&
                        git ls-files --others
-               ) |
-               git update-index --add --replace --remove --stdin
+               ) > "$tempdir"/tree-state || exit
+               git update-index --add --replace --remove --stdin \
+                       < "$tempdir"/tree-state || exit
        fi
 
        eval "$filter_index" < /dev/null ||
@@ -312,7 +341,8 @@ while read commit parents; do
                eval "$filter_msg" > ../message ||
                        die "msg filter failed: $filter_msg"
        @SHELL_PATH@ -c "$filter_commit" "git commit-tree" \
-               $(git write-tree) $parentstr < ../message > ../map/$commit
+               $(git write-tree) $parentstr < ../message > ../map/$commit ||
+                       die "could not write rewritten commit"
 done <../revs
 
 # In case of a subdirectory filter, it is possible that a specified head
@@ -380,7 +410,8 @@ do
                        die "Could not rewrite $ref"
        ;;
        esac
-       git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
+       git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1 ||
+                exit
 done < "$tempdir"/heads
 
 # TODO: This should possibly go, with the semantics that all positive given
@@ -399,7 +430,7 @@ if [ "$filter_tag_name" ]; then
                if [ "$type" = "tag" ]; then
                        # Dereference to a commit
                        sha1t="$sha1"
-                       sha1="$(git rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
+                       sha1="$(git rev-parse -q "$sha1"^{commit})" || continue
                fi
 
                [ -f "../map/$sha1" ] || continue
@@ -456,7 +487,7 @@ test -z "$ORIG_GIT_INDEX_FILE" || {
 }
 
 if [ "$(is_bare_repository)" = false ]; then
-       git read-tree -u -m HEAD
+       git read-tree -u -m HEAD || exit
 fi
 
-exit $ret
+exit 0
index 3ad8a21b30128ce21b6b6c28094d9af5e5866faf..b3580e9e48b6ac5d1a7fbd010f032445702f140f 100644 (file)
@@ -105,8 +105,11 @@ endif
 
 ifeq ($(uname_S),Darwin)
        TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app
-       ifeq ($(shell expr "$(uname_R)" : '9\.'),2)
-               TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish\ Shell.app
+       ifeq ($(shell echo "$(uname_R)" | awk -F. '{if ($$1 >= 9) print "y"}')_$(shell test -d $(TKFRAMEWORK) || echo n),y_n)
+               TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish.app
+               ifeq ($(shell test -d $(TKFRAMEWORK) || echo n),n)
+                       TKFRAMEWORK = /System/Library/Frameworks/Tk.framework/Resources/Wish\ Shell.app
+               endif
        endif
        TKEXECUTABLE = $(shell basename "$(TKFRAMEWORK)" .app)
 endif
index e018e076f8a1b927ac07f612cf097992643a5db7..14b92ba786554f4e714c89191e5584a825964ab2 100755 (executable)
@@ -122,6 +122,7 @@ unset oguimsg
 set _appname {Git Gui}
 set _gitdir {}
 set _gitexec {}
+set _githtmldir {}
 set _reponame {}
 set _iscygwin {}
 set _search_path {}
@@ -168,6 +169,28 @@ proc gitexec {args} {
        return [eval [list file join $_gitexec] $args]
 }
 
+proc githtmldir {args} {
+       global _githtmldir
+       if {$_githtmldir eq {}} {
+               if {[catch {set _githtmldir [git --html-path]}]} {
+                       # Git not installed or option not yet supported
+                       return {}
+               }
+               if {[is_Cygwin]} {
+                       set _githtmldir [exec cygpath \
+                               --windows \
+                               --absolute \
+                               $_githtmldir]
+               } else {
+                       set _githtmldir [file normalize $_githtmldir]
+               }
+       }
+       if {$args eq {}} {
+               return $_githtmldir
+       }
+       return [eval [list file join $_githtmldir] $args]
+}
+
 proc reponame {} {
        return $::_reponame
 }
@@ -640,10 +663,13 @@ font create font_diffbold
 font create font_diffitalic
 
 foreach class {Button Checkbutton Entry Label
-               Labelframe Listbox Menu Message
+               Labelframe Listbox Message
                Radiobutton Spinbox Text} {
        option add *$class.font font_ui
 }
+if {![is_MacOSX]} {
+       option add *Menu.font font_ui
+}
 unset class
 
 if {[is_Windows] || [is_MacOSX]} {
@@ -699,7 +725,7 @@ proc apply_config {} {
 
 set default_config(branch.autosetupmerge) true
 set default_config(merge.tool) {}
-set default_config(merge.keepbackup) true
+set default_config(mergetool.keepbackup) true
 set default_config(merge.diffstat) true
 set default_config(merge.summary) false
 set default_config(merge.verbosity) 2
@@ -769,9 +795,9 @@ if {![regsub {^git version } $_git_version {} _git_version]} {
 set _real_git_version $_git_version
 regsub -- {[\-\.]dirty$} $_git_version {} _git_version
 regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
-regsub {\.rc[0-9]+$} $_git_version {} _git_version
+regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
 regsub {\.GIT$} $_git_version {} _git_version
-regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version
+regsub {\.[a-zA-Z]+\.?[0-9]+$} $_git_version {} _git_version
 
 if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
        catch {wm withdraw .}
@@ -1108,6 +1134,7 @@ set current_diff_path {}
 set is_3way_diff 0
 set is_conflict_diff 0
 set selected_commit_type new
+set diff_empty_count 0
 
 set nullid "0000000000000000000000000000000000000000"
 set nullid2 "0000000000000000000000000000000000000001"
@@ -1924,7 +1951,7 @@ proc do_explore {} {
                # freedesktop.org-conforming system is our best shot
                set explorer "xdg-open"
        }
-       eval exec $explorer [file dirname [gitdir]] &
+       eval exec $explorer [list [file nativename [file dirname [gitdir]]]] &
 }
 
 set is_quitting 0
@@ -2277,6 +2304,12 @@ set ui_comm {}
 # -- Menu Bar
 #
 menu .mbar -tearoff 0
+if {[is_MacOSX]} {
+       # -- Apple Menu (Mac OS X only)
+       #
+       .mbar add cascade -label Apple -menu .mbar.apple
+       menu .mbar.apple
+}
 .mbar add cascade -label [mc Repository] -menu .mbar.repository
 .mbar add cascade -label [mc Edit] -menu .mbar.edit
 if {[is_enabled branch]} {
@@ -2292,7 +2325,6 @@ if {[is_enabled transport]} {
 if {[is_enabled multicommit] || [is_enabled singlecommit]} {
        .mbar add cascade -label [mc Tools] -menu .mbar.tools
 }
-. configure -menu .mbar
 
 # -- Repository Menu
 #
@@ -2545,19 +2577,7 @@ if {[is_enabled transport]} {
 }
 
 if {[is_MacOSX]} {
-       # -- Apple Menu (Mac OS X only)
-       #
-       .mbar add cascade -label Apple -menu .mbar.apple
-       menu .mbar.apple
-
-       .mbar.apple add command -label [mc "About %s" [appname]] \
-               -command do_about
-       .mbar.apple add separator
-       .mbar.apple add command \
-               -label [mc "Preferences..."] \
-               -command do_options \
-               -accelerator $M1T-,
-       bind . <$M1B-,> do_options
+       proc ::tk::mac::ShowPreferences {} {do_options}
 } else {
        # -- Edit Menu
        #
@@ -2585,17 +2605,23 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 .mbar add cascade -label [mc Help] -menu .mbar.help
 menu .mbar.help
 
-if {![is_MacOSX]} {
+if {[is_MacOSX]} {
+       .mbar.apple add command -label [mc "About %s" [appname]] \
+               -command do_about
+       .mbar.apple add separator
+} else {
        .mbar.help add command -label [mc "About %s" [appname]] \
                -command do_about
 }
+. configure -menu .mbar
 
+set doc_path [githtmldir]
+if {$doc_path ne {}} {
+       set doc_path [file join $doc_path index.html]
 
-set doc_path [file dirname [gitexec]]
-set doc_path [file join $doc_path Documentation index.html]
-
-if {[is_Cygwin]} {
-       set doc_path [exec cygpath --mixed $doc_path]
+       if {[is_Cygwin]} {
+               set doc_path [exec cygpath --mixed $doc_path]
+       }
 }
 
 if {[file isfile $doc_path]} {
@@ -2944,7 +2970,7 @@ $ctxm add command \
        -command {tk_textPaste $ui_comm}
 $ctxm add command \
        -label [mc Delete] \
-       -command {$ui_comm delete sel.first sel.last}
+       -command {catch {$ui_comm delete sel.first sel.last}}
 $ctxm add separator
 $ctxm add command \
        -label [mc "Select All"] \
index ef1930b4911591566be4561b8c17c24e1cfbfaad..20d5e42307c70a976e56f922f787610cde5bac9f 100644 (file)
@@ -51,7 +51,7 @@ constructor dialog {} {
                $w.check \
                [mc "Delete Only If Merged Into"] \
                ]
-       $w_check none [mc "Always (Do not perform merge test.)"]
+       $w_check none [mc "Always (Do not perform merge checks)"]
        pack $w.check -anchor nw -fill x -pady 5 -padx 5
 
        foreach h [load_all_heads] {
@@ -112,7 +112,7 @@ method _delete {} {
        }
        if {$to_delete eq {}} return
        if {$check_cmt eq {}} {
-               set msg [mc "Recovering deleted branches is difficult. \n\n Delete the selected branches?"]
+               set msg [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]
                if {[tk_messageBox \
                        -icon warning \
                        -type yesno \
index caca88831b7066c573917542d24a52e832dcff34..9e7412c446f715588b9cfe21654a3447e8680e12 100644 (file)
@@ -9,6 +9,7 @@ field w_cons   {}; # embedded console window object
 field new_expr   ; # expression the user saw/thinks this is
 field new_hash   ; # commit SHA-1 we are switching to
 field new_ref    ; # ref we are updating/creating
+field old_hash   ; # commit SHA-1 that was checked out when we started
 
 field parent_w      .; # window that started us
 field merge_type none; # type of merge to apply to existing branch
@@ -280,11 +281,11 @@ method _start_checkout {} {
 
        # -- Our in memory state should match the repository.
        #
-       repository_state curType curHEAD curMERGE_HEAD
+       repository_state curType old_hash curMERGE_HEAD
        if {[string match amend* $commit_type]
                && $curType eq {normal}
-               && $curHEAD eq $HEAD} {
-       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
+               && $old_hash eq $HEAD} {
+       } elseif {$commit_type ne $curType || $HEAD ne $old_hash} {
                info_popup [mc "Last scanned state does not match repository state.
 
 Another Git program has modified this repository since the last scan.  A rescan must be performed before the current branch can be changed.
@@ -297,7 +298,7 @@ The rescan will be automatically started now.
                return
        }
 
-       if {$curHEAD eq $new_hash} {
+       if {$old_hash eq $new_hash} {
                _after_readtree $this
        } elseif {[is_config_true gui.trustmtime]} {
                _readtree $this
@@ -453,13 +454,47 @@ method _after_readtree {} {
 If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."]
        }
 
+       # -- Run the post-checkout hook.
+       #
+       set fd_ph [githook_read post-checkout $old_hash $new_hash 1]
+       if {$fd_ph ne {}} {
+               global pch_error
+               set pch_error {}
+               fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
+               fileevent $fd_ph readable [cb _postcheckout_wait $fd_ph]
+       } else {
+               _update_repo_state $this
+       }
+}
+
+method _postcheckout_wait {fd_ph} {
+       global pch_error
+
+       append pch_error [read $fd_ph]
+       fconfigure $fd_ph -blocking 1
+       if {[eof $fd_ph]} {
+               if {[catch {close $fd_ph}]} {
+                       hook_failed_popup post-checkout $pch_error 0
+               }
+               unset pch_error
+               _update_repo_state $this
+               return
+       }
+       fconfigure $fd_ph -blocking 0
+}
+
+method _update_repo_state {} {
        # -- Update our repository state.  If we were previously in
        #    amend mode we need to toss the current buffer and do a
        #    full rescan to update our file lists.  If we weren't in
        #    amend mode our file lists are accurate and we can avoid
        #    the rescan.
        #
+       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+       global ui_comm
+
        unlock_index
+       set name [_name $this]
        set selected_commit_type new
        if {[string match amend* $commit_type]} {
                $ui_comm delete 0.0 end
index f9ff62a3b22fcc481c88dc35268d06d2fc4a5795..633cc572bbd076ff957c0b8194b6645e899e53ad 100644 (file)
@@ -398,6 +398,8 @@ method _do_new {} {
        grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew
        pack $w_body.where -fill x
 
+       grid columnconfigure $w_body.where 1 -weight 1
+
        trace add variable @local_path write [cb _write_local_path]
        bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
        update
@@ -964,7 +966,34 @@ method _readtree_wait {fd} {
                return
        }
 
-       set done 1
+       # -- Run the post-checkout hook.
+       #
+       set fd_ph [githook_read post-checkout [string repeat 0 40] \
+               [git rev-parse HEAD] 1]
+       if {$fd_ph ne {}} {
+               global pch_error
+               set pch_error {}
+               fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
+               fileevent $fd_ph readable [cb _postcheckout_wait $fd_ph]
+       } else {
+               set done 1
+       }
+}
+
+method _postcheckout_wait {fd_ph} {
+       global pch_error
+
+       append pch_error [read $fd_ph]
+       fconfigure $fd_ph -blocking 1
+       if {[eof $fd_ph]} {
+               if {[catch {close $fd_ph}]} {
+                       hook_failed_popup post-checkout $pch_error 0
+               }
+               unset pch_error
+               set done 1
+               return
+       }
+       fconfigure $fd_ph -blocking 0
 }
 
 ######################################################################
@@ -998,6 +1027,8 @@ method _do_open {} {
        grid $w_body.where.l $w_body.where.t $w_body.where.b -sticky ew
        pack $w_body.where -fill x
 
+       grid columnconfigure $w_body.where 1 -weight 1
+
        trace add variable @local_path write [cb _write_local_path]
        bind $w_body.h <Destroy> [list trace remove variable @local_path write [cb _write_local_path]]
        update
index 9cc84105952d02f0171be0e23f0a82373bae0545..7f459cd5647f690a5e48ed7c29fc0d75eef89d0c 100644 (file)
@@ -115,6 +115,23 @@ proc create_new_commit {} {
        rescan ui_ready
 }
 
+proc setup_commit_encoding {msg_wt {quiet 0}} {
+       global repo_config
+
+       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+               set enc utf-8
+       }
+       set use_enc [tcl_encoding $enc]
+       if {$use_enc ne {}} {
+               fconfigure $msg_wt -encoding $use_enc
+       } else {
+               if {!$quiet} {
+                       error_popup [mc "warning: Tcl does not support encoding '%s'." $enc]
+               }
+               fconfigure $msg_wt -encoding utf-8
+       }
+}
+
 proc commit_tree {} {
        global HEAD commit_type file_states ui_comm repo_config
        global pch_error
@@ -200,16 +217,7 @@ A good commit message has the following format:
        set msg_p [gitdir GITGUI_EDITMSG]
        set msg_wt [open $msg_p w]
        fconfigure $msg_wt -translation lf
-       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
-               set enc utf-8
-       }
-       set use_enc [tcl_encoding $enc]
-       if {$use_enc ne {}} {
-               fconfigure $msg_wt -encoding $use_enc
-       } else {
-               error_popup [mc "warning: Tcl does not support encoding '%s'." $enc]
-               fconfigure $msg_wt -encoding utf-8
-       }
+       setup_commit_encoding $msg_wt
        puts $msg_wt $msg
        close $msg_wt
 
@@ -362,6 +370,7 @@ A rescan will be automatically started now.
                append reflogm " ($commit_type)"
        }
        set msg_fd [open $msg_p r]
+       setup_commit_encoding $msg_fd 1
        gets $msg_fd subject
        close $msg_fd
        append reflogm {: } $subject
@@ -398,8 +407,8 @@ A rescan will be automatically started now.
        #
        set fd_ph [githook_read post-commit]
        if {$fd_ph ne {}} {
-               upvar #0 pch_error$cmt_id pc_err
-               set pc_err {}
+               global pch_error
+               set pch_error {}
                fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
                fileevent $fd_ph readable \
                        [list commit_postcommit_wait $fd_ph $cmt_id]
@@ -461,7 +470,7 @@ A rescan will be automatically started now.
 }
 
 proc commit_postcommit_wait {fd_ph cmt_id} {
-       upvar #0 pch_error$cmt_id pch_error
+       global pch_error
 
        append pch_error [read $fd_ph]
        fconfigure $fd_ph -blocking 1
index bbbf15c875f437f486f652d204bcebf1e5f1a8e4..925b3f56c1c23cc5e0c2b270b9f7e160013c9009 100644 (file)
@@ -51,11 +51,16 @@ proc force_diff_encoding {enc} {
 
 proc handle_empty_diff {} {
        global current_diff_path file_states file_lists
+       global diff_empty_count
 
        set path $current_diff_path
        set s $file_states($path)
        if {[lindex $s 0] ne {_M}} return
 
+       # Prevent infinite rescan loops
+       incr diff_empty_count
+       if {$diff_empty_count > 1} return
+
        info_popup [mc "No differences detected.
 
 %s has no changes.
@@ -310,6 +315,7 @@ proc read_diff {fd cont_info} {
        global ui_diff diff_active
        global is_3way_diff is_conflict_diff current_diff_header
        global current_diff_queue
+       global diff_empty_count
 
        $ui_diff conf -state normal
        while {[gets $fd line] >= 0} {
@@ -415,7 +421,10 @@ proc read_diff {fd cont_info} {
 
                if {[$ui_diff index end] eq {2.0}} {
                        handle_empty_diff
+               } else {
+                       set diff_empty_count 0
                }
+
                set callback [lindex $cont_info 1]
                if {$callback ne {}} {
                        eval $callback
index eb2b4b56a4db4c20727432c7a71d5192d580ce9e..3fe90e697002baaa1c5fa8df4c3d3eae199b062d 100644 (file)
@@ -88,7 +88,7 @@ proc merge_load_stages {path cont} {
        set merge_stages(3) {}
        set merge_stages_buf {}
 
-       set merge_stages_fd [eval git_read ls-files -u -z -- $path]
+       set merge_stages_fd [eval git_read ls-files -u -z -- {$path}]
 
        fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary
        fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont]
@@ -382,7 +382,7 @@ proc merge_tool_finish {fd} {
                delete_temp_files $mtool_tmpfiles
                ui_status [mc "Merge tool failed."]
        } else {
-               if {[is_config_true merge.keepbackup]} {
+               if {[is_config_true mergetool.keepbackup]} {
                        file rename -force -- $backup "$mtool_target.orig"
                }
 
index 89eb0f70f289e48e2b875e2cd49eb026a266ca0e..4e02fc0d393ff9fe8d2983fec99a03db7f36273a 100644 (file)
@@ -213,9 +213,7 @@ method _delete {} {
                -type yesno \
                -title [wm title $w] \
                -parent $w \
-               -message [mc "Recovering deleted branches is difficult.
-
-Delete the selected branches?"]] ne yes} {
+               -message [mc "Recovering deleted branches is difficult.\n\nDelete the selected branches?"]] ne yes} {
                return
        }
 
index 38c3151b05c732d919943e44629bfc0a8c9fb617..2f20eb39c0e25d68e4e6b46fe3b14a46b84ae83e 100644 (file)
@@ -54,7 +54,7 @@ proc do_cygwin_shortcut {} {
                                        $argv0]
                                win32_create_lnk $fn [list \
                                        $sh -c \
-                                       "CHERE_INVOKING=1 source /etc/profile;[sq $me]" \
+                                       "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \
                                        ] \
                                        [file dirname [file normalize [gitdir]]]
                        } err]} {
index 6ae63b6c7c61cb8f773ba5b9367c948820a0873a..95e6e5553ea86482d0fe9be77b07e805e01e3393 100644 (file)
@@ -146,7 +146,7 @@ proc tools_complete {fullname w {ok 1}} {
        }
 
        if {$ok} {
-               set msg [mc "Tool completed succesfully: %s" $fullname]
+               set msg [mc "Tool completed successfully: %s" $fullname]
        } else {
                set msg [mc "Tool failed: %s" $fullname]
        }
index a6f730b4eb38365b5156b715e9bb2fd8772c672c..51abb50bb627d05d702de2d794c47b874c9667ca 100644 (file)
@@ -773,16 +773,6 @@ msgstr "Immer (ohne Zusammenführungstest)"
 msgid "The following branches are not completely merged into %s:"
 msgstr "Folgende Zweige sind noch nicht mit »%s« zusammengeführt:"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"Gelöschte Zweige können nur mit größerem Aufwand wiederhergestellt werden.\n"
-"\n"
-"Gewählte Zweige jetzt löschen?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -2506,7 +2496,7 @@ msgstr "Starten: %s"
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr "Werkzeug erfolgreich abgeschlossen: %s"
 
 #: lib/tools.tcl:151
index 45773ab3d89f84757cf1f7dc0a7d0626ce88f1ec..a944ace6ced4c68b55b7b25023d546521a8ecd68 100644 (file)
@@ -62,7 +62,7 @@ msgstr ""
 "\n"
 "%s nécessite au moins Git 1.5.0.\n"
 "\n"
-"Peut'on considérer que '%s' est en version 1.5.0 ?\n"
+"Peut-on considérer que '%s' est en version 1.5.0 ?\n"
 
 #: git-gui.sh:1062
 msgid "Git directory not found:"
@@ -82,7 +82,7 @@ msgstr "Aucun répertoire de travail"
 
 #: git-gui.sh:1247 lib/checkout_op.tcl:305
 msgid "Refreshing file status..."
-msgstr "Rafraichissement du status des fichiers..."
+msgstr "Rafraîchissement du statut des fichiers..."
 
 #: git-gui.sh:1303
 msgid "Scanning for modified files ..."
@@ -163,7 +163,7 @@ msgstr "Dépôt"
 
 #: git-gui.sh:2281
 msgid "Edit"
-msgstr "Edition"
+msgstr "Édition"
 
 #: git-gui.sh:2283 lib/choose_rev.tcl:561
 msgid "Branch"
@@ -199,7 +199,7 @@ msgstr "Naviguer dans la branche..."
 
 #: git-gui.sh:2316
 msgid "Visualize Current Branch's History"
-msgstr "Visualiser historique branche courante"
+msgstr "Visualiser l'historique de la branche courante"
 
 #: git-gui.sh:2320
 msgid "Visualize All Branch History"
@@ -208,12 +208,12 @@ msgstr "Voir l'historique de toutes les branches"
 #: git-gui.sh:2327
 #, tcl-format
 msgid "Browse %s's Files"
-msgstr "Naviguer l'arborescence de %s"
+msgstr "Parcourir l'arborescence de %s"
 
 #: git-gui.sh:2329
 #, tcl-format
 msgid "Visualize %s's History"
-msgstr "Voir l'historique de la branche: %s"
+msgstr "Voir l'historique de la branche : %s"
 
 #: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
 msgid "Database Statistics"
@@ -230,7 +230,7 @@ msgstr "Vérifier le dépôt"
 #: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
 #: lib/shortcut.tcl:39 lib/shortcut.tcl:71
 msgid "Create Desktop Icon"
-msgstr "Créer icône sur bureau"
+msgstr "Créer une icône sur le bureau"
 
 #: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
 msgid "Quit"
@@ -320,7 +320,7 @@ msgstr "Désindexer"
 
 #: git-gui.sh:2484 lib/index.tcl:410
 msgid "Revert Changes"
-msgstr "Annuler les modifications (revert)"
+msgstr "Annuler les modifications"
 
 #: git-gui.sh:2491 git-gui.sh:3069
 msgid "Show Less Context"
@@ -382,7 +382,7 @@ msgstr "Documentation en ligne"
 
 #: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
 msgid "Show SSH Key"
-msgstr "Montrer clé SSH"
+msgstr "Montrer la clé SSH"
 
 #: git-gui.sh:2707
 #, tcl-format
@@ -445,7 +445,7 @@ msgstr "Fichier :"
 
 #: git-gui.sh:3078
 msgid "Refresh"
-msgstr "Rafraichir"
+msgstr "Rafraîchir"
 
 #: git-gui.sh:3099
 msgid "Decrease Font Size"
@@ -457,7 +457,7 @@ msgstr "Agrandir la police"
 
 #: git-gui.sh:3111 lib/blame.tcl:281
 msgid "Encoding"
-msgstr "Encodage"
+msgstr "Codage des caractères"
 
 #: git-gui.sh:3122
 msgid "Apply/Reverse Hunk"
@@ -469,7 +469,7 @@ msgstr "Appliquer/Inverser la ligne"
 
 #: git-gui.sh:3137
 msgid "Run Merge Tool"
-msgstr "Lancer outil de merge"
+msgstr "Lancer l'outil de fusion"
 
 #: git-gui.sh:3142
 msgid "Use Remote Version"
@@ -527,7 +527,7 @@ msgid ""
 "Tcl binary distributed by Cygwin."
 msgstr ""
 "\n"
-"Ceci est du à un problème connu avec\n"
+"Ceci est dû à un problème connu avec\n"
 "le binaire Tcl distribué par Cygwin."
 
 #: git-gui.sh:3336
@@ -630,11 +630,11 @@ msgstr "Fichier original :"
 
 #: lib/blame.tcl:1021
 msgid "Cannot find HEAD commit:"
-msgstr "Impossible de trouver le commit HEAD:"
+msgstr "Impossible de trouver le commit HEAD :"
 
 #: lib/blame.tcl:1076
 msgid "Cannot find parent commit:"
-msgstr "Impossible de trouver le commit parent:"
+msgstr "Impossible de trouver le commit parent :"
 
 #: lib/blame.tcl:1091
 msgid "Unable to display parent"
@@ -646,7 +646,7 @@ msgstr "Erreur lors du chargement des différences :"
 
 #: lib/blame.tcl:1232
 msgid "Originally By:"
-msgstr "A l'origine par :"
+msgstr "À l'origine par :"
 
 #: lib/blame.tcl:1238
 msgid "In File:"
@@ -691,11 +691,11 @@ msgstr "Détacher de la branche locale"
 
 #: lib/branch_create.tcl:22
 msgid "Create Branch"
-msgstr "Créer branche"
+msgstr "Créer une branche"
 
 #: lib/branch_create.tcl:27
 msgid "Create New Branch"
-msgstr "Créer nouvelle branche"
+msgstr "Créer une nouvelle branche"
 
 #: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
 msgid "Create"
@@ -719,7 +719,7 @@ msgstr "Révision initiale"
 
 #: lib/branch_create.tcl:72
 msgid "Update Existing Branch:"
-msgstr "Mettre à jour branche existante :"
+msgstr "Mettre à jour une branche existante :"
 
 #: lib/branch_create.tcl:75
 msgid "No"
@@ -727,7 +727,7 @@ msgstr "Non"
 
 #: lib/branch_create.tcl:80
 msgid "Fast Forward Only"
-msgstr "Mise-à-jour rectiligne seulement (fast-forward)"
+msgstr "Mise à jour rectiligne seulement (fast-forward)"
 
 #: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
 msgid "Reset"
@@ -769,7 +769,7 @@ msgstr "Branches locales"
 
 #: lib/branch_delete.tcl:52
 msgid "Delete Only If Merged Into"
-msgstr "Supprimer seulement si fusionnée dans:"
+msgstr "Supprimer seulement si fusionnée dans :"
 
 #: lib/branch_delete.tcl:54
 msgid "Always (Do not perform merge test.)"
@@ -780,23 +780,13 @@ msgstr "Toujours (Ne pas faire de test de fusion.)"
 msgid "The following branches are not completely merged into %s:"
 msgstr "Les branches suivantes ne sont pas complètement fusionnées dans %s :"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"Récupérer des branches supprimées est difficile.\n"
-"\n"
-"Supprimer les branches sélectionnées ?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
 "Failed to delete branches:\n"
 "%s"
 msgstr ""
-"La suppression des branches suivantes a échouée :\n"
+"La suppression des branches suivantes a échoué :\n"
 "%s"
 
 #: lib/branch_rename.tcl:14 lib/branch_rename.tcl:22
@@ -902,11 +892,11 @@ msgstr "La stratégie de fusion '%s' n'est pas supportée."
 #: lib/checkout_op.tcl:261
 #, tcl-format
 msgid "Failed to update '%s'."
-msgstr "La mise à jour de '%s' a échouée."
+msgstr "La mise à jour de '%s' a échoué."
 
 #: lib/checkout_op.tcl:273
 msgid "Staging area (index) is already locked."
-msgstr "L'index (staging area) est déjà vérouillé"
+msgstr "L'index (staging area) est déjà verrouillé."
 
 #: lib/checkout_op.tcl:288
 msgid ""
@@ -918,7 +908,7 @@ msgid ""
 "The rescan will be automatically started now.\n"
 msgstr ""
 "L'état lors de la dernière synchronisation ne correspond plus à l'état du "
-"dépôt\n"
+"dépôt.\n"
 "\n"
 "Un autre programme Git a modifié ce dépôt depuis la dernière "
 "synchronisation. Une resynchronisation doit être effectuée avant de pouvoir "
@@ -956,9 +946,9 @@ msgid ""
 "If you wanted to be on a branch, create one now starting from 'This Detached "
 "Checkout'."
 msgstr ""
-"Vous n'êtes plus ur une branche locale.\n"
+"Vous n'êtes plus sur une branche locale.\n"
 "\n"
-"Si vous vouliez être sur une branche, créez en une maintenant en partant de "
+"Si vous vouliez être sur une branche, créez-en une maintenant en partant de "
 "'Cet emprunt détaché'."
 
 #: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
@@ -1000,7 +990,7 @@ msgstr ""
 "mis à jour avec succès, mais la mise à jour d'un fichier interne à Git a "
 "échouée.\n"
 "\n"
-"Cela n'aurait pas du se produire. %s va abandonner et se terminer."
+"Cela n'aurait pas dû se produire. %s va abandonner et se terminer."
 
 #: lib/choose_font.tcl:39
 msgid "Select"
@@ -1023,8 +1013,8 @@ msgid ""
 "This is example text.\n"
 "If you like this text, it can be your font."
 msgstr ""
-"C'est un texte d'exemple.\n"
-"Si vous aimez ce texte, vous pouvez choisir cette police"
+"Ceci est un texte d'exemple.\n"
+"Si vous aimez ce texte, vous pouvez choisir cette police."
 
 #: lib/choose_repository.tcl:28
 msgid "Git Gui"
@@ -1040,7 +1030,7 @@ msgstr "Nouveau..."
 
 #: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
 msgid "Clone Existing Repository"
-msgstr "Cloner dépôt existant"
+msgstr "Cloner un dépôt existant"
 
 #: lib/choose_repository.tcl:106
 msgid "Clone..."
@@ -1048,7 +1038,7 @@ msgstr "Cloner..."
 
 #: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
 msgid "Open Existing Repository"
-msgstr "Ouvrir dépôt existant"
+msgstr "Ouvrir un dépôt existant"
 
 #: lib/choose_repository.tcl:119
 msgid "Open..."
@@ -1056,17 +1046,17 @@ msgstr "Ouvrir..."
 
 #: lib/choose_repository.tcl:132
 msgid "Recent Repositories"
-msgstr "Dépôt récemment utilisés"
+msgstr "Dépôts récemment utilisés"
 
 #: lib/choose_repository.tcl:138
 msgid "Open Recent Repository:"
-msgstr "Ouvrir dépôt récent :"
+msgstr "Ouvrir un dépôt récent :"
 
 #: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
 #: lib/choose_repository.tcl:316
 #, tcl-format
 msgid "Failed to create repository %s:"
-msgstr "La création du dépôt %s a échouée :"
+msgstr "La création du dépôt %s a échoué :"
 
 #: lib/choose_repository.tcl:387
 msgid "Directory:"
@@ -1093,11 +1083,11 @@ msgstr "Cloner"
 
 #: lib/choose_repository.tcl:473
 msgid "Source Location:"
-msgstr "Emplacement source:"
+msgstr "Emplacement source :"
 
 #: lib/choose_repository.tcl:484
 msgid "Target Directory:"
-msgstr "Répertoire cible:"
+msgstr "Répertoire cible :"
 
 #: lib/choose_repository.tcl:496
 msgid "Clone Type:"
@@ -1137,7 +1127,7 @@ msgstr "L'emplacement %s existe déjà."
 
 #: lib/choose_repository.tcl:622
 msgid "Failed to configure origin"
-msgstr "La configuration de l'origine a échouée."
+msgstr "La configuration de l'origine a échoué."
 
 #: lib/choose_repository.tcl:634
 msgid "Counting objects"
@@ -1242,7 +1232,7 @@ msgstr "fichiers"
 
 #: lib/choose_repository.tcl:962
 msgid "Initial file checkout failed."
-msgstr "Chargement initial du fichier échoué."
+msgstr "Le chargement initial du fichier a échoué."
 
 #: lib/choose_repository.tcl:978
 msgid "Open"
@@ -1284,7 +1274,7 @@ msgstr "Révision invalide : %s"
 
 #: lib/choose_rev.tcl:338
 msgid "No revision selected."
-msgstr "Pas de révision selectionnée."
+msgstr "Pas de révision sélectionnée."
 
 #: lib/choose_rev.tcl:346
 msgid "Revision expression is empty."
@@ -1292,7 +1282,7 @@ msgstr "L'expression de révision est vide."
 
 #: lib/choose_rev.tcl:531
 msgid "Updated"
-msgstr "Mise-à-jour:"
+msgstr "Mise à jour:"
 
 #: lib/choose_rev.tcl:559
 msgid "URL"
@@ -1320,8 +1310,8 @@ msgid ""
 msgstr ""
 "Impossible de corriger pendant une fusion.\n"
 "\n"
-"Vous êtes actuellement au milieu d'une fusion qui n'a pas été completement "
-"terminée. Vous ne pouvez pas corriger le commit précédant sauf si vous "
+"Vous êtes actuellement au milieu d'une fusion qui n'a pas été complètement "
+"terminée. Vous ne pouvez pas corriger le commit précédent sauf si vous "
 "abandonnez la fusion courante.\n"
 
 #: lib/commit.tcl:49
@@ -1409,7 +1399,7 @@ msgstr ""
 #: lib/commit.tcl:211
 #, tcl-format
 msgid "warning: Tcl does not support encoding '%s'."
-msgstr "attention : Tcl ne supporte pas l'encodage '%s'."
+msgstr "attention : Tcl ne supporte pas lcodage '%s'."
 
 #: lib/commit.tcl:227
 msgid "Calling pre-commit hook..."
@@ -1469,12 +1459,12 @@ msgstr "commit-tree a échoué :"
 
 #: lib/commit.tcl:373
 msgid "update-ref failed:"
-msgstr "update-ref a échoué"
+msgstr "update-ref a échoué :"
 
 #: lib/commit.tcl:461
 #, tcl-format
 msgid "Created commit %s: %s"
-msgstr "Commit créé %s : %s"
+msgstr "Commit %s créé : %s"
 
 #: lib/console.tcl:59
 msgid "Working... please wait..."
@@ -1581,24 +1571,24 @@ msgid ""
 "LOCAL: deleted\n"
 "REMOTE:\n"
 msgstr ""
-"LOCAL: supprimé\n"
-"DISTANT:\n"
+"LOCAL : supprimé\n"
+"DISTANT :\n"
 
 #: lib/diff.tcl:125
 msgid ""
 "REMOTE: deleted\n"
 "LOCAL:\n"
 msgstr ""
-"DISTANT: supprimé\n"
-"LOCAL:\n"
+"DISTANT : supprimé\n"
+"LOCAL :\n"
 
 #: lib/diff.tcl:132
 msgid "LOCAL:\n"
-msgstr "LOCAL:\n"
+msgstr "LOCAL :\n"
 
 #: lib/diff.tcl:135
 msgid "REMOTE:\n"
-msgstr "DISTANT:\n"
+msgstr "DISTANT :\n"
 
 #: lib/diff.tcl:197 lib/diff.tcl:296
 #, tcl-format
@@ -1624,7 +1614,7 @@ msgid ""
 "* Showing only first %d bytes.\n"
 msgstr ""
 "* Le fichier non suivi fait %d octets.\n"
-"* On montre seulement les premiers %d octets.\n"
+"* Seuls les %d premiers octets sont montrés.\n"
 
 #: lib/diff.tcl:228
 #, tcl-format
@@ -1635,7 +1625,7 @@ msgid ""
 msgstr ""
 "\n"
 "* Fichier suivi raccourcis ici de %s.\n"
-"* Pour voir le fichier entier, utiliser un éditeur externe.\n"
+"* Pour voir le fichier entier, utilisez un éditeur externe.\n"
 
 #: lib/diff.tcl:436
 msgid "Failed to unstage selected hunk."
@@ -1680,7 +1670,7 @@ msgstr "Vous devez corriger les erreurs suivantes avant de pouvoir commiter."
 
 #: lib/index.tcl:6
 msgid "Unable to unlock the index."
-msgstr "Impossible de dévérouiller l'index."
+msgstr "Impossible de déverrouiller l'index."
 
 #: lib/index.tcl:15
 msgid "Index Error"
@@ -1700,12 +1690,12 @@ msgstr "Continuer"
 
 #: lib/index.tcl:31
 msgid "Unlock Index"
-msgstr "Déverouiller l'index"
+msgstr "Déverrouiller l'index"
 
 #: lib/index.tcl:287
 #, tcl-format
 msgid "Unstaging %s from commit"
-msgstr "Désindexation de: %s"
+msgstr "Désindexation de : %s"
 
 #: lib/index.tcl:326
 msgid "Ready to commit."
@@ -1804,11 +1794,11 @@ msgid ""
 msgstr ""
 "Vous êtes au milieu d'une modification.\n"
 "\n"
-"Le fichier %s est modifié.\n"
+"Le fichier %s a été modifié.\n"
 "\n"
 "Vous devriez terminer le commit courant avant de lancer une fusion. En "
 "faisait comme cela, vous éviterez de devoir éventuellement abandonner une "
-"fusion ayant échouée.\n"
+"fusion ayant échoué.\n"
 
 #: lib/merge.tcl:107
 #, tcl-format
@@ -1826,7 +1816,7 @@ msgstr "La fusion s'est faite avec succès."
 
 #: lib/merge.tcl:133
 msgid "Merge failed.  Conflict resolution is required."
-msgstr "La fusion a echouée. Il est nécessaire de résoudre les conflicts."
+msgstr "La fusion a echoué. Il est nécessaire de résoudre les conflits."
 
 #: lib/merge.tcl:158
 #, tcl-format
@@ -1914,16 +1904,16 @@ msgid ""
 "\n"
 "This operation can be undone only by restarting the merge."
 msgstr ""
-"Noter que le diff ne montre que les modifications en conflict.\n"
+"Noter que le diff ne montre que les modifications en conflit.\n"
 "\n"
 "%s sera écrasé.\n"
 "\n"
-"Cette opération ne peut être défaite qu'en relançant la fusion."
+"Cette opération ne peut être inversée qu'en relançant la fusion."
 
 #: lib/mergetool.tcl:45
 #, tcl-format
 msgid "File %s seems to have unresolved conflicts, still stage?"
-msgstr "Le fichier %s semble avoir des conflicts non résolus, indéxer quand même ?"
+msgstr "Le fichier %s semble avoir des conflits non résolus, indexer quand même ?"
 
 #: lib/mergetool.tcl:60
 #, tcl-format
@@ -1932,11 +1922,11 @@ msgstr "Ajouter une résolution pour %s"
 
 #: lib/mergetool.tcl:141
 msgid "Cannot resolve deletion or link conflicts using a tool"
-msgstr "Impossible de résoudre la suppression ou de relier des conflicts en utilisant un outil"
+msgstr "Impossible de résoudre la suppression ou de relier des conflits en utilisant un outil"
 
 #: lib/mergetool.tcl:146
 msgid "Conflict file does not exist"
-msgstr "Le fichier en conflict n'existe pas."
+msgstr "Le fichier en conflit n'existe pas."
 
 #: lib/mergetool.tcl:264
 #, tcl-format
@@ -1958,7 +1948,7 @@ msgid ""
 "Error retrieving versions:\n"
 "%s"
 msgstr ""
-"Erreur lors de la récupération des versions:\n"
+"Erreur lors de la récupération des versions :\n"
 "%s"
 
 #: lib/mergetool.tcl:343
@@ -1968,7 +1958,7 @@ msgid ""
 "\n"
 "%s"
 msgstr ""
-"Impossible de lancer l'outil de fusion:\n"
+"Impossible de lancer l'outil de fusion :\n"
 "\n"
 "%s"
 
@@ -1983,12 +1973,12 @@ msgstr "L'outil de fusion a échoué."
 #: lib/option.tcl:11
 #, tcl-format
 msgid "Invalid global encoding '%s'"
-msgstr "Encodage global invalide '%s'"
+msgstr "Codage global '%s' invalide"
 
 #: lib/option.tcl:19
 #, tcl-format
 msgid "Invalid repo encoding '%s'"
-msgstr "Encodage de dépôt invalide '%s'"
+msgstr "Codage de dépôt '%s' invalide"
 
 #: lib/option.tcl:117
 msgid "Restore Defaults"
@@ -2001,7 +1991,7 @@ msgstr "Sauvegarder"
 #: lib/option.tcl:131
 #, tcl-format
 msgid "%s Repository"
-msgstr "Dépôt: %s"
+msgstr "Dépôt : %s"
 
 #: lib/option.tcl:132
 msgid "Global (All Repositories)"
@@ -2069,7 +2059,7 @@ msgstr "Nouveau modèle de nom de branche"
 
 #: lib/option.tcl:155
 msgid "Default File Contents Encoding"
-msgstr "Encodage du contenu des fichiers par défaut"
+msgstr "Codage du contenu des fichiers par défaut"
 
 #: lib/option.tcl:203
 msgid "Change"
@@ -2098,11 +2088,11 @@ msgstr "Préférences"
 
 #: lib/option.tcl:314
 msgid "Failed to completely save options:"
-msgstr "La sauvegarde complète des options a échouée :"
+msgstr "La sauvegarde complète des options a échoué :"
 
 #: lib/remote.tcl:163
 msgid "Remove Remote"
-msgstr "Supprimer dépôt distant"
+msgstr "Supprimer un dépôt distant"
 
 #: lib/remote.tcl:168
 msgid "Prune from"
@@ -2118,11 +2108,11 @@ msgstr "Pousser vers"
 
 #: lib/remote_add.tcl:19
 msgid "Add Remote"
-msgstr "Ajouter dépôt distant"
+msgstr "Ajouter un dépôt distant"
 
 #: lib/remote_add.tcl:24
 msgid "Add New Remote"
-msgstr "Ajouter nouveau dépôt distant"
+msgstr "Ajouter un nouveau dépôt distant"
 
 #: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
 msgid "Add"
@@ -2134,7 +2124,7 @@ msgstr "Détails des dépôts distants"
 
 #: lib/remote_add.tcl:50
 msgid "Location:"
-msgstr "Emplacement:"
+msgstr "Emplacement :"
 
 #: lib/remote_add.tcl:62
 msgid "Further Action"
@@ -2146,7 +2136,7 @@ msgstr "Récupérer immédiatement"
 
 #: lib/remote_add.tcl:71
 msgid "Initialize Remote Repository and Push"
-msgstr "Initialiser dépôt distant et pousser"
+msgstr "Initialiser un dépôt distant et pousser"
 
 #: lib/remote_add.tcl:77
 msgid "Do Nothing Else Now"
@@ -2193,7 +2183,7 @@ msgstr "Mise en place de %s (à %s)"
 
 #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
 msgid "Delete Branch Remotely"
-msgstr "Supprimer branche à distance"
+msgstr "Supprimer une branche à distance"
 
 #: lib/remote_branch_delete.tcl:47
 msgid "From Repository"
@@ -2244,8 +2234,8 @@ msgid ""
 "One or more of the merge tests failed because you have not fetched the "
 "necessary commits.  Try fetching from %s first."
 msgstr ""
-"Une ou plusieurs des tests de fusion ont échoués parce que vous n'avez pas "
-"récupéré les commits nécessaires. Essayez de récupéré à partir de %s d'abord."
+"Un ou plusieurs des tests de fusion ont échoué parce que vous n'avez pas "
+"récupéré les commits nécessaires. Essayez de récupérer à partir de %s d'abord."
 
 #: lib/remote_branch_delete.tcl:207
 msgid "Please select one or more branches to delete."
@@ -2257,14 +2247,14 @@ msgid ""
 "\n"
 "Delete the selected branches?"
 msgstr ""
-"Récupérer des branches supprimées est difficile.\n"
+"Il est difficile de récupérer des branches supprimées.\n"
 "\n"
-"Souhaitez vous supprimer les branches sélectionnées ?"
+"Supprimer les branches sélectionnées ?"
 
 #: lib/remote_branch_delete.tcl:226
 #, tcl-format
 msgid "Deleting branches from %s"
-msgstr "Supprimer les branches de %s"
+msgstr "Suppression des branches de %s"
 
 #: lib/remote_branch_delete.tcl:286
 msgid "No repository selected."
@@ -2285,7 +2275,7 @@ msgstr "Suivant"
 
 #: lib/search.tcl:24
 msgid "Prev"
-msgstr "Précédant"
+msgstr "Précédent"
 
 #: lib/search.tcl:25
 msgid "Case-Sensitive"
@@ -2293,7 +2283,7 @@ msgstr "Sensible à la casse"
 
 #: lib/shortcut.tcl:20 lib/shortcut.tcl:61
 msgid "Cannot write shortcut:"
-msgstr "Impossible d'écrire le raccourcis :"
+msgstr "Impossible d'écrire le raccourci :"
 
 #: lib/shortcut.tcl:136
 msgid "Cannot write icon:"
@@ -2318,7 +2308,7 @@ msgstr "Réinitialisation du dictionnaire à %s."
 
 #: lib/spellcheck.tcl:73
 msgid "Spell checker silently failed on startup"
-msgstr "La vérification d'orthographe a échouée silentieusement au démarrage"
+msgstr "La vérification d'orthographe a échoué silencieusement au démarrage"
 
 #: lib/spellcheck.tcl:80
 msgid "Unrecognized spell checker"
@@ -2351,11 +2341,11 @@ msgstr "Générer une clé"
 
 #: lib/sshkey.tcl:56
 msgid "Copy To Clipboard"
-msgstr "Copier dans le presse papier"
+msgstr "Copier dans le presse-papier"
 
 #: lib/sshkey.tcl:70
 msgid "Your OpenSSH Public Key"
-msgstr "Votre clé publique Open SSH"
+msgstr "Votre clé publique OpenSSH"
 
 #: lib/sshkey.tcl:78
 msgid "Generating..."
@@ -2368,7 +2358,7 @@ msgid ""
 "\n"
 "%s"
 msgstr ""
-"Impossible de lancer ssh-keygen:\n"
+"Impossible de lancer ssh-keygen :\n"
 "\n"
 "%s"
 
@@ -2398,7 +2388,7 @@ msgstr "Lancer %s nécessite qu'un fichier soit sélectionné."
 #: lib/tools.tcl:90
 #, tcl-format
 msgid "Are you sure you want to run %s?"
-msgstr "Êtes vous sûr de vouloir lancer %s ?"
+msgstr "Êtes-vous sûr de vouloir lancer %s ?"
 
 #: lib/tools.tcl:110
 #, tcl-format
@@ -2412,7 +2402,7 @@ msgstr "Lancement de : %s"
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr "L'outil a terminé avec succès : %s"
 
 #: lib/tools.tcl:151
@@ -2422,11 +2412,11 @@ msgstr "L'outil a échoué : %s"
 
 #: lib/tools_dlg.tcl:22
 msgid "Add Tool"
-msgstr "Ajouter outil"
+msgstr "Ajouter un outil"
 
 #: lib/tools_dlg.tcl:28
 msgid "Add New Tool Command"
-msgstr "Ajouter nouvelle commande d'outil"
+msgstr "Ajouter une nouvelle commande d'outil"
 
 #: lib/tools_dlg.tcl:33
 msgid "Add globally"
@@ -2438,7 +2428,7 @@ msgstr "Détails sur l'outil"
 
 #: lib/tools_dlg.tcl:48
 msgid "Use '/' separators to create a submenu tree:"
-msgstr "Utiliser les séparateurs '/' pour créer un arbre de sous menus :"
+msgstr "Utiliser les séparateurs '/' pour créer un arbre de sous-menus :"
 
 #: lib/tools_dlg.tcl:61
 msgid "Command:"
@@ -2462,7 +2452,7 @@ msgstr "Ne pas montrer la fenêtre de sortie des commandes"
 
 #: lib/tools_dlg.tcl:97
 msgid "Run only if a diff is selected ($FILENAME not empty)"
-msgstr "Lancer seulement si un diff est selectionné ($FILENAME non vide)"
+msgstr "Lancer seulement si un diff est sélectionné ($FILENAME non vide)"
 
 #: lib/tools_dlg.tcl:121
 msgid "Please supply a name for the tool."
@@ -2479,7 +2469,7 @@ msgid ""
 "Could not add tool:\n"
 "%s"
 msgstr ""
-"Impossible d'ajouter l'outil:\n"
+"Impossible d'ajouter l'outil :\n"
 "%s"
 
 #: lib/tools_dlg.tcl:190
index 15aea0dc64fd9711bf7246d347ceaafc773d874b..53b7d3634d842a43f8043e3f825d4232e17588aa 100644 (file)
@@ -753,13 +753,6 @@ msgstr ""
 msgid "The following branches are not completely merged into %s:"
 msgstr ""
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -2220,7 +2213,7 @@ msgstr ""
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr ""
 
 #: lib/tools.tcl:151
index f761b6415298809897f4f1cccdd0ea325ccbb1dc..0f87bc1cbeedca8d9040777a5484500d1071c2e6 100644 (file)
@@ -776,16 +776,6 @@ msgstr "Mindig (Ne legyen merge teszt.)"
 msgid "The following branches are not completely merged into %s:"
 msgstr "A következő branchek nem teljesen lettek merge-ölve ebbe: %s:"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"A törölt branchek visszaállítása bonyolult. \n"
-"\n"
-" Biztosan törli a kiválasztott brancheket?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -2399,7 +2389,7 @@ msgstr "Futtatás: %s..."
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr "Az eszköz sikeresen befejeződött: %s"
 
 #: lib/tools.tcl:151
index 294e5958874f41c52071313cd107124b38c710a3..762632c22f9e5567bf4603e2958401e6156f1872 100644 (file)
@@ -778,16 +778,6 @@ msgstr "Sempre (Non effettuare verifiche di fusione)."
 msgid "The following branches are not completely merged into %s:"
 msgstr "I rami seguenti non sono stati fusi completamente in %s:"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"Ricomporre rami cancellati può essere complicato. \n"
-"\n"
-" Eliminare i rami selezionati?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -2418,7 +2408,7 @@ msgstr "Eseguo: %s"
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr "Il programma esterno è terminato con successo: %s"
 
 #: lib/tools.tcl:151
index 09d60bef74990e43a9515437d9b4de53e5df0b98..63c4695103a764acbf116df1946fd0bc311c47f0 100644 (file)
@@ -773,16 +773,6 @@ msgstr "無条件(マージテストしない)"
 msgid "The following branches are not completely merged into %s:"
 msgstr "以下のブランチは %s に完全にマージされていません:"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"ブランチを削除すると元に戻すのは困難です。 \n"
-"\n"
-" 選択したブランチを削除しますか?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -2382,7 +2372,7 @@ msgstr "実行中: %s"
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr "ツールが完了しました: %s"
 
 #: lib/tools.tcl:151
index 1c5137d84ca8797238dbcf1cbc6fd80218fbca5f..6de93c28c2e2b7cb0413521c987161c4a4fcb59d 100644 (file)
@@ -761,16 +761,6 @@ msgstr "Alltid (Ikke utfør sammenslåingstest.)"
 msgid "The following branches are not completely merged into %s:"
 msgstr "Følgende grener er ikke fullstendig slått sammen med %s:"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"Gjenoppretting av fjernede grener er vanskelig. \n"
-"\n"
-" Fjern valgte grener?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -2331,7 +2321,7 @@ msgstr "Kjører: %s"
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr "Verktøyet ble fullført med suksess: %s"
 
 #: lib/tools.tcl:151
index db55b3e0a69813cba16932212ee1b2ce0f5b2b9a..0ffc4a418fccb4453c1601ca87668e2d185e14db 100644 (file)
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: git-gui\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-03-14 07:18+0100\n"
+"POT-Creation-Date: 2008-12-08 08:31-0800\n"
 "PO-Revision-Date: 2007-10-22 22:30-0200\n"
 "Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n"
 "Language-Team: Russian Translation <git@vger.kernel.org>\n"
@@ -15,33 +15,33 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: git-gui.sh:41 git-gui.sh:634 git-gui.sh:648 git-gui.sh:661 git-gui.sh:744
-#: git-gui.sh:763
+#: git-gui.sh:41 git-gui.sh:737 git-gui.sh:751 git-gui.sh:764 git-gui.sh:847
+#: git-gui.sh:866
 msgid "git-gui: fatal error"
 msgstr "git-gui: критическая ошибка"
 
-#: git-gui.sh:593
+#: git-gui.sh:689
 #, tcl-format
 msgid "Invalid font specified in %s:"
 msgstr "В %s установлен неверный шрифт:"
 
-#: git-gui.sh:620
+#: git-gui.sh:723
 msgid "Main Font"
 msgstr "Шрифт интерфейса"
 
-#: git-gui.sh:621
+#: git-gui.sh:724
 msgid "Diff/Console Font"
 msgstr "Шрифт консоли и изменений (diff)"
 
-#: git-gui.sh:635
+#: git-gui.sh:738
 msgid "Cannot find git in PATH."
 msgstr "git не найден в PATH."
 
-#: git-gui.sh:662
+#: git-gui.sh:765
 msgid "Cannot parse Git version string:"
 msgstr "Невозможно распознать строку версии Git: "
 
-#: git-gui.sh:680
+#: git-gui.sh:783
 #, tcl-format
 msgid ""
 "Git version cannot be determined.\n"
@@ -53,384 +53,451 @@ msgid ""
 "Assume '%s' is version 1.5.0?\n"
 msgstr ""
 "Невозможно определить версию Git\n"
+"\n"
 "%s указывает на версию '%s'.\n"
 "\n"
 "для %s требуется версия Git, начиная с 1.5.0\n"
 "\n"
 "Принять '%s' как версию 1.5.0?\n"
 
-#: git-gui.sh:918
+#: git-gui.sh:1062
 msgid "Git directory not found:"
 msgstr "Каталог Git не найден:"
 
-#: git-gui.sh:925
+#: git-gui.sh:1069
 msgid "Cannot move to top of working directory:"
 msgstr "Невозможно перейти к корню рабочего каталога репозитория: "
 
-#: git-gui.sh:932
+#: git-gui.sh:1076
 msgid "Cannot use funny .git directory:"
-msgstr "Каталог.git испорчен: "
+msgstr "Каталог .git испорчен: "
 
-#: git-gui.sh:937
+#: git-gui.sh:1081
 msgid "No working directory"
 msgstr "Отсутствует рабочий каталог"
 
-#: git-gui.sh:1084 lib/checkout_op.tcl:283
+#: git-gui.sh:1247 lib/checkout_op.tcl:305
 msgid "Refreshing file status..."
 msgstr "Обновление информации о состоянии файлов..."
 
-#: git-gui.sh:1149
+#: git-gui.sh:1303
 msgid "Scanning for modified files ..."
 msgstr "Поиск измененных файлов..."
 
-#: git-gui.sh:1324 lib/browser.tcl:246
+#: git-gui.sh:1367
+msgid "Calling prepare-commit-msg hook..."
+msgstr "Вызов программы поддержки репозитория prepare-commit-msg..."
+
+#: git-gui.sh:1384
+msgid "Commit declined by prepare-commit-msg hook."
+msgstr "Сохранение прервано программой поддержки репозитория prepare-commit-msg"
+
+#: git-gui.sh:1542 lib/browser.tcl:246
 msgid "Ready."
 msgstr "Готово."
 
-#: git-gui.sh:1590
+#: git-gui.sh:1819
 msgid "Unmodified"
 msgstr "Не изменено"
 
-#: git-gui.sh:1592
+#: git-gui.sh:1821
 msgid "Modified, not staged"
 msgstr "Изменено, не подготовлено"
 
-#: git-gui.sh:1593 git-gui.sh:1598
+#: git-gui.sh:1822 git-gui.sh:1830
 msgid "Staged for commit"
 msgstr "Подготовлено для сохранения"
 
-#: git-gui.sh:1594 git-gui.sh:1599
+#: git-gui.sh:1823 git-gui.sh:1831
 msgid "Portions staged for commit"
 msgstr "Части, подготовленные для сохранения"
 
-#: git-gui.sh:1595 git-gui.sh:1600
+#: git-gui.sh:1824 git-gui.sh:1832
 msgid "Staged for commit, missing"
 msgstr "Подготовлено для сохранения, отсутствует"
 
-#: git-gui.sh:1597
+#: git-gui.sh:1826
+msgid "File type changed, not staged"
+msgstr "Тип файла изменён, не подготовлено"
+
+#: git-gui.sh:1827
+msgid "File type changed, staged"
+msgstr "Тип файла изменён, подготовлено"
+
+#: git-gui.sh:1829
 msgid "Untracked, not staged"
 msgstr "Не отслеживается, не подготовлено"
 
-#: git-gui.sh:1602
+#: git-gui.sh:1834
 msgid "Missing"
 msgstr "Отсутствует"
 
-#: git-gui.sh:1603
+#: git-gui.sh:1835
 msgid "Staged for removal"
 msgstr "Подготовлено для удаления"
 
-#: git-gui.sh:1604
+#: git-gui.sh:1836
 msgid "Staged for removal, still present"
 msgstr "Подготовлено для удаления, еще не удалено"
 
-#: git-gui.sh:1606 git-gui.sh:1607 git-gui.sh:1608 git-gui.sh:1609
+#: git-gui.sh:1838 git-gui.sh:1839 git-gui.sh:1840 git-gui.sh:1841
+#: git-gui.sh:1842 git-gui.sh:1843
 msgid "Requires merge resolution"
-msgstr "Требуется разрешение конфликта при объединении"
+msgstr "Требуется разрешение конфликта при слиянии"
 
-#: git-gui.sh:1644
+#: git-gui.sh:1878
 msgid "Starting gitk... please wait..."
-msgstr "Ð\97апÑ\83Ñ\81каеÑ\82Ñ\81Ñ\8f gitk... Ð¿Ð¾Ð¶Ð°Ð»Ñ\83йÑ\81Ñ\82а, Ð¶Ð´Ð¸Ñ\82е..."
+msgstr "Ð\97апÑ\83Ñ\81каеÑ\82Ñ\81Ñ\8f gitk... Ð\9fодождиÑ\82е, Ð¿Ð¾Ð¶Ð°Ð»Ñ\83йÑ\81Ñ\82а..."
 
-#: git-gui.sh:1653
-#, tcl-format
-msgid ""
-"Unable to start gitk:\n"
-"\n"
-"%s does not exist"
-msgstr ""
-"Не удалось запустить gitk:\n"
-"\n"
-"%s не существует"
+#: git-gui.sh:1887
+msgid "Couldn't find gitk in PATH"
+msgstr "gitk не найден в PATH."
 
-#: git-gui.sh:1860 lib/choose_repository.tcl:36
+#: git-gui.sh:2280 lib/choose_repository.tcl:36
 msgid "Repository"
 msgstr "Репозиторий"
 
-#: git-gui.sh:1861
+#: git-gui.sh:2281
 msgid "Edit"
 msgstr "Редактировать"
 
-#: git-gui.sh:1863 lib/choose_rev.tcl:561
+#: git-gui.sh:2283 lib/choose_rev.tcl:561
 msgid "Branch"
 msgstr "Ветвь"
 
-#: git-gui.sh:1866 lib/choose_rev.tcl:548
+#: git-gui.sh:2286 lib/choose_rev.tcl:548
 msgid "Commit@@noun"
 msgstr "Состояние"
 
-#: git-gui.sh:1869 lib/merge.tcl:120 lib/merge.tcl:149 lib/merge.tcl:167
+#: git-gui.sh:2289 lib/merge.tcl:121 lib/merge.tcl:150 lib/merge.tcl:168
 msgid "Merge"
-msgstr "Ð\9eбÑ\8aединиÑ\82Ñ\8c"
+msgstr "СлиÑ\8fние"
 
-#: git-gui.sh:1870 lib/choose_rev.tcl:557
+#: git-gui.sh:2290 lib/choose_rev.tcl:557
 msgid "Remote"
 msgstr "Внешние репозитории"
 
-#: git-gui.sh:1879
+#: git-gui.sh:2293
+msgid "Tools"
+msgstr "Вспомогательные операции"
+
+#: git-gui.sh:2302
+msgid "Explore Working Copy"
+msgstr "Просмотр рабочего каталога"
+
+#: git-gui.sh:2307
 msgid "Browse Current Branch's Files"
 msgstr "Просмотреть файлы текущей ветви"
 
-#: git-gui.sh:1883
+#: git-gui.sh:2311
 msgid "Browse Branch Files..."
 msgstr "Показать файлы ветви..."
 
-#: git-gui.sh:1888
+#: git-gui.sh:2316
 msgid "Visualize Current Branch's History"
-msgstr "Ð\98Ñ\81Ñ\82оÑ\80иÑ\8f Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ви Ð½Ð°Ð³Ð»Ñ\8fдно"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ñ\82екÑ\83Ñ\89ей Ð²ÐµÑ\82ви"
 
-#: git-gui.sh:1892
+#: git-gui.sh:2320
 msgid "Visualize All Branch History"
-msgstr "Ð\98Ñ\81Ñ\82оÑ\80иÑ\8f Ð²Ñ\81еÑ\85 Ð²ÐµÑ\82вей Ð½Ð°Ð³Ð»Ñ\8fдно"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²Ñ\81еÑ\85 Ð²ÐµÑ\82вей"
 
-#: git-gui.sh:1899
+#: git-gui.sh:2327
 #, tcl-format
 msgid "Browse %s's Files"
 msgstr "Показать файлы ветви %s"
 
-#: git-gui.sh:1901
+#: git-gui.sh:2329
 #, tcl-format
 msgid "Visualize %s's History"
-msgstr "Ð\98Ñ\81Ñ\82оÑ\80иÑ\8f Ð²ÐµÑ\82ви %s Ð½Ð°Ð³Ð»Ñ\8fдно"
+msgstr "Ð\9fоказаÑ\82Ñ\8c Ð¸Ñ\81Ñ\82оÑ\80иÑ\8e Ð²ÐµÑ\82ви %s"
 
-#: git-gui.sh:1906 lib/database.tcl:27 lib/database.tcl:67
+#: git-gui.sh:2334 lib/database.tcl:27 lib/database.tcl:67
 msgid "Database Statistics"
 msgstr "Статистика базы данных"
 
-#: git-gui.sh:1909 lib/database.tcl:34
+#: git-gui.sh:2337 lib/database.tcl:34
 msgid "Compress Database"
 msgstr "Сжать базу данных"
 
-#: git-gui.sh:1912
+#: git-gui.sh:2340
 msgid "Verify Database"
 msgstr "Проверить базу данных"
 
-#: git-gui.sh:1919 git-gui.sh:1923 git-gui.sh:1927 lib/shortcut.tcl:7
+#: git-gui.sh:2347 git-gui.sh:2351 git-gui.sh:2355 lib/shortcut.tcl:7
 #: lib/shortcut.tcl:39 lib/shortcut.tcl:71
 msgid "Create Desktop Icon"
 msgstr "Создать ярлык на рабочем столе"
 
-#: git-gui.sh:1932 lib/choose_repository.tcl:177 lib/choose_repository.tcl:185
+#: git-gui.sh:2363 lib/choose_repository.tcl:183 lib/choose_repository.tcl:191
 msgid "Quit"
 msgstr "Выход"
 
-#: git-gui.sh:1939
+#: git-gui.sh:2371
 msgid "Undo"
 msgstr "Отменить"
 
-#: git-gui.sh:1942
+#: git-gui.sh:2374
 msgid "Redo"
 msgstr "Повторить"
 
-#: git-gui.sh:1946 git-gui.sh:2443
+#: git-gui.sh:2378 git-gui.sh:2937
 msgid "Cut"
 msgstr "Вырезать"
 
-#: git-gui.sh:1949 git-gui.sh:2446 git-gui.sh:2520 git-gui.sh:2614
+#: git-gui.sh:2381 git-gui.sh:2940 git-gui.sh:3014 git-gui.sh:3096
 #: lib/console.tcl:69
 msgid "Copy"
 msgstr "Копировать"
 
-#: git-gui.sh:1952 git-gui.sh:2449
+#: git-gui.sh:2384 git-gui.sh:2943
 msgid "Paste"
 msgstr "Вставить"
 
-#: git-gui.sh:1955 git-gui.sh:2452 lib/branch_delete.tcl:26
+#: git-gui.sh:2387 git-gui.sh:2946 lib/branch_delete.tcl:26
 #: lib/remote_branch_delete.tcl:38
 msgid "Delete"
 msgstr "Удалить"
 
-#: git-gui.sh:1959 git-gui.sh:2456 git-gui.sh:2618 lib/console.tcl:71
+#: git-gui.sh:2391 git-gui.sh:2950 git-gui.sh:3100 lib/console.tcl:71
 msgid "Select All"
 msgstr "Выделить все"
 
-#: git-gui.sh:1968
+#: git-gui.sh:2400
 msgid "Create..."
 msgstr "Создать..."
 
-#: git-gui.sh:1974
+#: git-gui.sh:2406
 msgid "Checkout..."
 msgstr "Перейти..."
 
-#: git-gui.sh:1980
+#: git-gui.sh:2412
 msgid "Rename..."
 msgstr "Переименовать..."
 
-#: git-gui.sh:1985 git-gui.sh:2085
+#: git-gui.sh:2417
 msgid "Delete..."
 msgstr "Удалить..."
 
-#: git-gui.sh:1990
+#: git-gui.sh:2422
 msgid "Reset..."
 msgstr "Сбросить..."
 
-#: git-gui.sh:2002 git-gui.sh:2389
+#: git-gui.sh:2432
+msgid "Done"
+msgstr "Завершено"
+
+#: git-gui.sh:2434
+msgid "Commit@@verb"
+msgstr "Сохранить"
+
+#: git-gui.sh:2443 git-gui.sh:2878
 msgid "New Commit"
 msgstr "Новое состояние"
 
-#: git-gui.sh:2010 git-gui.sh:2396
+#: git-gui.sh:2451 git-gui.sh:2885
 msgid "Amend Last Commit"
 msgstr "Исправить последнее состояние"
 
-#: git-gui.sh:2019 git-gui.sh:2356 lib/remote_branch_delete.tcl:99
+#: git-gui.sh:2461 git-gui.sh:2839 lib/remote_branch_delete.tcl:99
 msgid "Rescan"
 msgstr "Перечитать"
 
-#: git-gui.sh:2025
+#: git-gui.sh:2467
 msgid "Stage To Commit"
 msgstr "Подготовить для сохранения"
 
-#: git-gui.sh:2031
+#: git-gui.sh:2473
 msgid "Stage Changed Files To Commit"
 msgstr "Подготовить измененные файлы для сохранения"
 
-#: git-gui.sh:2037
+#: git-gui.sh:2479
 msgid "Unstage From Commit"
 msgstr "Убрать из подготовленного"
 
-#: git-gui.sh:2042 lib/index.tcl:395
+#: git-gui.sh:2484 lib/index.tcl:410
 msgid "Revert Changes"
 msgstr "Отменить изменения"
 
-#: git-gui.sh:2049 git-gui.sh:2368 git-gui.sh:2467
-msgid "Sign Off"
-msgstr "Ð\9fодпиÑ\81аÑ\82Ñ\8c"
+#: git-gui.sh:2491 git-gui.sh:3083
+msgid "Show Less Context"
+msgstr "Ð\9cенÑ\8cÑ\88е ÐºÐ¾Ð½Ñ\82екÑ\81Ñ\82а"
 
-#: git-gui.sh:2053 git-gui.sh:2372
-msgid "Commit@@verb"
-msgstr "Сохранить"
+#: git-gui.sh:2495 git-gui.sh:3087
+msgid "Show More Context"
+msgstr "Больше контекста"
+
+#: git-gui.sh:2502 git-gui.sh:2852 git-gui.sh:2961
+msgid "Sign Off"
+msgstr "Вставить Signed-off-by"
 
-#: git-gui.sh:2064
+#: git-gui.sh:2518
 msgid "Local Merge..."
-msgstr "Локальное объединение..."
+msgstr "Локальное слияние..."
 
-#: git-gui.sh:2069
+#: git-gui.sh:2523
 msgid "Abort Merge..."
-msgstr "Прервать объединение..."
+msgstr "Прервать слияние..."
+
+#: git-gui.sh:2535 git-gui.sh:2575
+msgid "Add..."
+msgstr "Добавить..."
 
-#: git-gui.sh:2081
+#: git-gui.sh:2539
 msgid "Push..."
 msgstr "Отправить..."
 
-#: git-gui.sh:2092 lib/choose_repository.tcl:41
-msgid "Apple"
-msgstr ""
+#: git-gui.sh:2543
+msgid "Delete Branch..."
+msgstr "Удалить ветвь..."
 
-#: git-gui.sh:2095 git-gui.sh:2117 lib/about.tcl:14
-#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:50
+#: git-gui.sh:2553 git-gui.sh:2589 lib/about.tcl:14
+#: lib/choose_repository.tcl:44 lib/choose_repository.tcl:53
 #, tcl-format
 msgid "About %s"
 msgstr "О %s"
 
-#: git-gui.sh:2099
+#: git-gui.sh:2557
 msgid "Preferences..."
 msgstr "Настройки..."
 
-#: git-gui.sh:2107 git-gui.sh:2639
+#: git-gui.sh:2565 git-gui.sh:3129
 msgid "Options..."
 msgstr "Настройки..."
 
-#: git-gui.sh:2113 lib/choose_repository.tcl:47
+#: git-gui.sh:2576
+msgid "Remove..."
+msgstr "Удалить..."
+
+#: git-gui.sh:2585 lib/choose_repository.tcl:50
 msgid "Help"
 msgstr "Помощь"
 
-#: git-gui.sh:2154
+#: git-gui.sh:2611
 msgid "Online Documentation"
 msgstr "Документация в интернете"
 
-#: git-gui.sh:2238
+#: git-gui.sh:2614 lib/choose_repository.tcl:47 lib/choose_repository.tcl:56
+msgid "Show SSH Key"
+msgstr "Показать ключ SSH"
+
+#: git-gui.sh:2721
 #, tcl-format
 msgid "fatal: cannot stat path %s: No such file or directory"
 msgstr "критическая ошибка: %s: нет такого файла или каталога"
 
-#: git-gui.sh:2271
+#: git-gui.sh:2754
 msgid "Current Branch:"
 msgstr "Текущая ветвь:"
 
-#: git-gui.sh:2292
+#: git-gui.sh:2775
 msgid "Staged Changes (Will Commit)"
 msgstr "Подготовлено (будет сохранено)"
 
-#: git-gui.sh:2312
+#: git-gui.sh:2795
 msgid "Unstaged Changes"
 msgstr "Изменено (не будет сохранено)"
 
-#: git-gui.sh:2362
+#: git-gui.sh:2845
 msgid "Stage Changed"
 msgstr "Подготовить все"
 
-#: git-gui.sh:2378 lib/transport.tcl:93 lib/transport.tcl:182
+#: git-gui.sh:2864 lib/transport.tcl:104 lib/transport.tcl:193
 msgid "Push"
 msgstr "Отправить"
 
-#: git-gui.sh:2408
+#: git-gui.sh:2899
 msgid "Initial Commit Message:"
 msgstr "Комментарий к первому состоянию:"
 
-#: git-gui.sh:2409
+#: git-gui.sh:2900
 msgid "Amended Commit Message:"
 msgstr "Комментарий к исправленному состоянию:"
 
-#: git-gui.sh:2410
+#: git-gui.sh:2901
 msgid "Amended Initial Commit Message:"
 msgstr "Комментарий к исправленному первоначальному состоянию:"
 
-#: git-gui.sh:2411
+#: git-gui.sh:2902
 msgid "Amended Merge Commit Message:"
-msgstr "Комментарий к исправленному объединению:"
+msgstr "Комментарий к исправленному слиянию:"
 
-#: git-gui.sh:2412
+#: git-gui.sh:2903
 msgid "Merge Commit Message:"
-msgstr "Комментарий к объединению:"
+msgstr "Комментарий к слиянию:"
 
-#: git-gui.sh:2413
+#: git-gui.sh:2904
 msgid "Commit Message:"
 msgstr "Комментарий к состоянию:"
 
-#: git-gui.sh:2459 git-gui.sh:2622 lib/console.tcl:73
+#: git-gui.sh:2953 git-gui.sh:3104 lib/console.tcl:73
 msgid "Copy All"
 msgstr "Копировать все"
 
-#: git-gui.sh:2483 lib/blame.tcl:107
+#: git-gui.sh:2977 lib/blame.tcl:104
 msgid "File:"
 msgstr "Файл:"
 
-#: git-gui.sh:2589
-msgid "Apply/Reverse Hunk"
-msgstr "Применить/Убрать изменение"
-
-#: git-gui.sh:2595
-msgid "Show Less Context"
-msgstr "Меньше контекста"
-
-#: git-gui.sh:2602
-msgid "Show More Context"
-msgstr "Больше контекста"
-
-#: git-gui.sh:2610
+#: git-gui.sh:3092
 msgid "Refresh"
 msgstr "Обновить"
 
-#: git-gui.sh:2631
+#: git-gui.sh:3113
 msgid "Decrease Font Size"
 msgstr "Уменьшить размер шрифта"
 
-#: git-gui.sh:2635
+#: git-gui.sh:3117
 msgid "Increase Font Size"
 msgstr "Увеличить размер шрифта"
 
-#: git-gui.sh:2646
+#: git-gui.sh:3125 lib/blame.tcl:281
+msgid "Encoding"
+msgstr "Кодировка"
+
+#: git-gui.sh:3136
+msgid "Apply/Reverse Hunk"
+msgstr "Применить/Убрать изменение"
+
+#: git-gui.sh:3141
+msgid "Apply/Reverse Line"
+msgstr "Применить/Убрать строку"
+
+#: git-gui.sh:3151
+msgid "Run Merge Tool"
+msgstr "Запустить программу слияния"
+
+#: git-gui.sh:3156
+msgid "Use Remote Version"
+msgstr "Взять внешнюю версию"
+
+#: git-gui.sh:3160
+msgid "Use Local Version"
+msgstr "Взять локальную версию"
+
+#: git-gui.sh:3164
+msgid "Revert To Base"
+msgstr "Отменить изменения"
+
+#: git-gui.sh:3183
 msgid "Unstage Hunk From Commit"
 msgstr "Не сохранять часть"
 
-#: git-gui.sh:2648
+#: git-gui.sh:3184
+msgid "Unstage Line From Commit"
+msgstr "Убрать строку из подготовленного"
+
+#: git-gui.sh:3186
 msgid "Stage Hunk For Commit"
 msgstr "Подготовить часть для сохранения"
 
-#: git-gui.sh:2667
+#: git-gui.sh:3187
+msgid "Stage Line For Commit"
+msgstr "Подготовить строку для сохранения"
+
+#: git-gui.sh:3210
 msgid "Initializing..."
 msgstr "Инициализация..."
 
-#: git-gui.sh:2762
+#: git-gui.sh:3315
 #, tcl-format
 msgid ""
 "Possible environment issues exist.\n"
@@ -447,7 +514,7 @@ msgstr ""
 "запущенными из %s\n"
 "\n"
 
-#: git-gui.sh:2792
+#: git-gui.sh:3345
 msgid ""
 "\n"
 "This is due to a known issue with the\n"
@@ -457,7 +524,7 @@ msgstr ""
 "Это известная проблема с Tcl,\n"
 "распространяемым Cygwin."
 
-#: git-gui.sh:2797
+#: git-gui.sh:3350
 #, tcl-format
 msgid ""
 "\n"
@@ -478,64 +545,108 @@ msgstr ""
 msgid "git-gui - a graphical user interface for Git."
 msgstr "git-gui - графический пользовательский интерфейс к Git."
 
-#: lib/blame.tcl:77
+#: lib/blame.tcl:72
 msgid "File Viewer"
 msgstr "Просмотр файла"
 
-#: lib/blame.tcl:81
+#: lib/blame.tcl:78
 msgid "Commit:"
 msgstr "Сохраненное состояние:"
 
-#: lib/blame.tcl:264
+#: lib/blame.tcl:271
 msgid "Copy Commit"
 msgstr "Скопировать SHA-1"
 
-#: lib/blame.tcl:384
+#: lib/blame.tcl:275
+msgid "Find Text..."
+msgstr "Найти текст..."
+
+#: lib/blame.tcl:284
+msgid "Do Full Copy Detection"
+msgstr "Провести полный поиск копий"
+
+#: lib/blame.tcl:288
+msgid "Show History Context"
+msgstr "Показать исторический контекст"
+
+#: lib/blame.tcl:291
+msgid "Blame Parent Commit"
+msgstr "Рассмотреть состояние предка"
+
+#: lib/blame.tcl:450
 #, tcl-format
 msgid "Reading %s..."
 msgstr "Чтение %s..."
 
-#: lib/blame.tcl:488
+#: lib/blame.tcl:557
 msgid "Loading copy/move tracking annotations..."
 msgstr "Загрузка аннотации копирований/переименований..."
 
-#: lib/blame.tcl:508
+#: lib/blame.tcl:577
 msgid "lines annotated"
 msgstr "строк прокомментировано"
 
-#: lib/blame.tcl:689
+#: lib/blame.tcl:769
 msgid "Loading original location annotations..."
 msgstr "Загрузка аннотаций первоначального положения объекта..."
 
-#: lib/blame.tcl:692
+#: lib/blame.tcl:772
 msgid "Annotation complete."
 msgstr "Аннотация завершена."
 
-#: lib/blame.tcl:746
+#: lib/blame.tcl:802
+msgid "Busy"
+msgstr "Занят"
+
+#: lib/blame.tcl:803
+msgid "Annotation process is already running."
+msgstr "Аннотация уже запущена"
+
+#: lib/blame.tcl:842
+msgid "Running thorough copy detection..."
+msgstr "Выполнение полного поиска копий..."
+
+#: lib/blame.tcl:910
 msgid "Loading annotation..."
 msgstr "Загрузка аннотации..."
 
-#: lib/blame.tcl:802
+#: lib/blame.tcl:963
 msgid "Author:"
 msgstr "Автор:"
 
-#: lib/blame.tcl:806
+#: lib/blame.tcl:967
 msgid "Committer:"
 msgstr "Сохранил:"
 
-#: lib/blame.tcl:811
+#: lib/blame.tcl:972
 msgid "Original File:"
 msgstr "Исходный файл:"
 
-#: lib/blame.tcl:925
+#: lib/blame.tcl:1020
+msgid "Cannot find HEAD commit:"
+msgstr "Невозможно найти текущее состояние:"
+
+#: lib/blame.tcl:1075
+msgid "Cannot find parent commit:"
+msgstr "Невозможно найти состояние предка:"
+
+#: lib/blame.tcl:1090
+msgid "Unable to display parent"
+msgstr "Не могу показать предка"
+
+#: lib/blame.tcl:1091 lib/diff.tcl:297
+msgid "Error loading diff:"
+msgstr "Ошибка загрузки изменений:"
+
+#: lib/blame.tcl:1231
 msgid "Originally By:"
 msgstr "Источник:"
 
-#: lib/blame.tcl:931
+#: lib/blame.tcl:1237
 msgid "In File:"
 msgstr "Файл:"
 
-#: lib/blame.tcl:936
+#: lib/blame.tcl:1242
 msgid "Copied Or Moved Here By:"
 msgstr "Скопировано/перемещено в:"
 
@@ -549,16 +660,18 @@ msgstr "Перейти"
 
 #: lib/branch_checkout.tcl:27 lib/branch_create.tcl:35
 #: lib/branch_delete.tcl:32 lib/branch_rename.tcl:30 lib/browser.tcl:282
-#: lib/checkout_op.tcl:522 lib/choose_font.tcl:43 lib/merge.tcl:171
-#: lib/option.tcl:103 lib/remote_branch_delete.tcl:42 lib/transport.tcl:97
+#: lib/checkout_op.tcl:544 lib/choose_font.tcl:43 lib/merge.tcl:172
+#: lib/option.tcl:125 lib/remote_add.tcl:32 lib/remote_branch_delete.tcl:42
+#: lib/tools_dlg.tcl:40 lib/tools_dlg.tcl:204 lib/tools_dlg.tcl:352
+#: lib/transport.tcl:108
 msgid "Cancel"
-msgstr "Ð\9eÑ\82мениÑ\82Ñ\8c"
+msgstr "Ð\9eÑ\82мена"
 
-#: lib/branch_checkout.tcl:32 lib/browser.tcl:287
+#: lib/branch_checkout.tcl:32 lib/browser.tcl:287 lib/tools_dlg.tcl:328
 msgid "Revision"
 msgstr "Версия"
 
-#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:242
+#: lib/branch_checkout.tcl:36 lib/branch_create.tcl:69 lib/option.tcl:280
 msgid "Options"
 msgstr "Настройки"
 
@@ -578,7 +691,7 @@ msgstr "Создание ветви"
 msgid "Create New Branch"
 msgstr "Создать новую ветвь"
 
-#: lib/branch_create.tcl:31 lib/choose_repository.tcl:371
+#: lib/branch_create.tcl:31 lib/choose_repository.tcl:377
 msgid "Create"
 msgstr "Создать"
 
@@ -586,7 +699,7 @@ msgstr "Создать"
 msgid "Branch Name"
 msgstr "Название ветви"
 
-#: lib/branch_create.tcl:43
+#: lib/branch_create.tcl:43 lib/remote_add.tcl:39 lib/tools_dlg.tcl:50
 msgid "Name:"
 msgstr "Название:"
 
@@ -610,7 +723,7 @@ msgstr "Нет"
 msgid "Fast Forward Only"
 msgstr "Только Fast Forward"
 
-#: lib/branch_create.tcl:85 lib/checkout_op.tcl:514
+#: lib/branch_create.tcl:85 lib/checkout_op.tcl:536
 msgid "Reset"
 msgstr "Сброс"
 
@@ -650,26 +763,16 @@ msgstr "Локальные ветви"
 
 #: lib/branch_delete.tcl:52
 msgid "Delete Only If Merged Into"
-msgstr "Удалить только в случае, если было объединение с"
+msgstr "Удалить только в случае, если было слияние с"
 
 #: lib/branch_delete.tcl:54
 msgid "Always (Do not perform merge test.)"
-msgstr "Всегда (не выполнять проверку на объединение)"
+msgstr "Всегда (не выполнять проверку на слияние)"
 
 #: lib/branch_delete.tcl:103
 #, tcl-format
 msgid "The following branches are not completely merged into %s:"
-msgstr "Следующие ветви объединены с %s не полностью:"
-
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"Восстанавливать удаленные ветви сложно. \n"
-"\n"
-" Удалить выбранные ветви?"
+msgstr "Ветви, которые не полностью сливаются с %s:"
 
 #: lib/branch_delete.tcl:141
 #, tcl-format
@@ -700,7 +803,7 @@ msgstr "Новое название:"
 msgid "Please select a branch to rename."
 msgstr "Укажите ветвь для переименования."
 
-#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:179
+#: lib/branch_rename.tcl:96 lib/checkout_op.tcl:201
 #, tcl-format
 msgid "Branch '%s' already exists."
 msgstr "Ветвь '%s' уже существует."
@@ -731,32 +834,38 @@ msgstr "[На уровень выше]"
 msgid "Browse Branch Files"
 msgstr "Показать файлы ветви"
 
-#: lib/browser.tcl:278 lib/choose_repository.tcl:387
-#: lib/choose_repository.tcl:474 lib/choose_repository.tcl:484
-#: lib/choose_repository.tcl:987
+#: lib/browser.tcl:278 lib/choose_repository.tcl:394
+#: lib/choose_repository.tcl:480 lib/choose_repository.tcl:491
+#: lib/choose_repository.tcl:995
 msgid "Browse"
 msgstr "Показать"
 
-#: lib/checkout_op.tcl:79
+#: lib/checkout_op.tcl:84
 #, tcl-format
 msgid "Fetching %s from %s"
 msgstr "Получение %s из %s "
 
-#: lib/checkout_op.tcl:127
+#: lib/checkout_op.tcl:132
 #, tcl-format
 msgid "fatal: Cannot resolve %s"
 msgstr "критическая ошибка: невозможно разрешить %s"
 
-#: lib/checkout_op.tcl:140 lib/console.tcl:81 lib/database.tcl:31
+#: lib/checkout_op.tcl:145 lib/console.tcl:81 lib/database.tcl:31
+#: lib/sshkey.tcl:53
 msgid "Close"
 msgstr "Закрыть"
 
-#: lib/checkout_op.tcl:169
+#: lib/checkout_op.tcl:174
 #, tcl-format
 msgid "Branch '%s' does not exist."
 msgstr "Ветвь '%s' не существует "
 
-#: lib/checkout_op.tcl:206
+#: lib/checkout_op.tcl:193
+#, tcl-format
+msgid "Failed to configure simplified git-pull for '%s'."
+msgstr "Ошибка создания упрощённой конфигурации git pull для '%s'."
+
+#: lib/checkout_op.tcl:228
 #, tcl-format
 msgid ""
 "Branch '%s' already exists.\n"
@@ -767,23 +876,23 @@ msgstr ""
 "Ветвь '%s' уже существует.\n"
 "\n"
 "Она не может быть прокручена(fast-forward) к %s.\n"
-"Требуется объединение."
+"Требуется слияние."
 
-#: lib/checkout_op.tcl:220
+#: lib/checkout_op.tcl:242
 #, tcl-format
 msgid "Merge strategy '%s' not supported."
-msgstr "СÑ\82Ñ\80аÑ\82егиÑ\8f Ð¾Ð±Ñ\8aединениÑ\8f '%s' Ð½Ðµ Ð¿Ð¾Ð´Ð´ÐµÑ\80живаеÑ\82Ñ\81Ñ\8f."
+msgstr "Ð\9dеизвеÑ\81Ñ\82наÑ\8f Ñ\81Ñ\82Ñ\80аÑ\82егиÑ\8f Ñ\81лиÑ\8fниÑ\8f: '%s'."
 
-#: lib/checkout_op.tcl:239
+#: lib/checkout_op.tcl:261
 #, tcl-format
 msgid "Failed to update '%s'."
 msgstr "Не удалось обновить '%s'."
 
-#: lib/checkout_op.tcl:251
+#: lib/checkout_op.tcl:273
 msgid "Staging area (index) is already locked."
 msgstr "Рабочая область заблокирована другим процессом."
 
-#: lib/checkout_op.tcl:266
+#: lib/checkout_op.tcl:288
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -799,30 +908,30 @@ msgstr ""
 "\n"
 "Это будет сделано сейчас автоматически.\n"
 
-#: lib/checkout_op.tcl:322
+#: lib/checkout_op.tcl:344
 #, tcl-format
 msgid "Updating working directory to '%s'..."
 msgstr "Обновление рабочего каталога из '%s'..."
 
-#: lib/checkout_op.tcl:323
+#: lib/checkout_op.tcl:345
 msgid "files checked out"
 msgstr "файлы извлечены"
 
-#: lib/checkout_op.tcl:353
+#: lib/checkout_op.tcl:375
 #, tcl-format
 msgid "Aborted checkout of '%s' (file level merging is required)."
-msgstr "Прерван переход на '%s' (требуется объединение на уровне файлов)"
+msgstr "Прерван переход на '%s' (требуется слияние содержания файлов)"
 
-#: lib/checkout_op.tcl:354
+#: lib/checkout_op.tcl:376
 msgid "File level merge required."
-msgstr "Требуется объединение на уровне файлов."
+msgstr "Требуется слияние содержания файлов."
 
-#: lib/checkout_op.tcl:358
+#: lib/checkout_op.tcl:380
 #, tcl-format
 msgid "Staying on branch '%s'."
 msgstr "Ветвь '%s' остается текущей."
 
-#: lib/checkout_op.tcl:429
+#: lib/checkout_op.tcl:451
 msgid ""
 "You are no longer on a local branch.\n"
 "\n"
@@ -834,30 +943,30 @@ msgstr ""
 "Если вы хотите снова вернуться к какой-нибудь ветви, создайте ее сейчас, "
 "начиная с 'Текущего отсоединенного состояния'."
 
-#: lib/checkout_op.tcl:446 lib/checkout_op.tcl:450
+#: lib/checkout_op.tcl:468 lib/checkout_op.tcl:472
 #, tcl-format
 msgid "Checked out '%s'."
 msgstr "Ветвь '%s' сделана текущей."
 
-#: lib/checkout_op.tcl:478
+#: lib/checkout_op.tcl:500
 #, tcl-format
 msgid "Resetting '%s' to '%s' will lose the following commits:"
 msgstr "Сброс '%s' в '%s' приведет к потере следующих сохраненных состояний: "
 
-#: lib/checkout_op.tcl:500
+#: lib/checkout_op.tcl:522
 msgid "Recovering lost commits may not be easy."
 msgstr "Восстановить потерянные сохраненные состояния будет сложно."
 
-#: lib/checkout_op.tcl:505
+#: lib/checkout_op.tcl:527
 #, tcl-format
 msgid "Reset '%s'?"
 msgstr "Сбросить '%s'?"
 
-#: lib/checkout_op.tcl:510 lib/merge.tcl:163
+#: lib/checkout_op.tcl:532 lib/merge.tcl:164 lib/tools_dlg.tcl:343
 msgid "Visualize"
 msgstr "Наглядно"
 
-#: lib/checkout_op.tcl:578
+#: lib/checkout_op.tcl:600
 #, tcl-format
 msgid ""
 "Failed to set current branch.\n"
@@ -900,224 +1009,228 @@ msgstr ""
 
 #: lib/choose_repository.tcl:28
 msgid "Git Gui"
-msgstr ""
+msgstr "Git Gui"
 
-#: lib/choose_repository.tcl:81 lib/choose_repository.tcl:376
+#: lib/choose_repository.tcl:87 lib/choose_repository.tcl:382
 msgid "Create New Repository"
 msgstr "Создать новый репозиторий"
 
-#: lib/choose_repository.tcl:87
+#: lib/choose_repository.tcl:93
 msgid "New..."
 msgstr "Новый..."
 
-#: lib/choose_repository.tcl:94 lib/choose_repository.tcl:460
+#: lib/choose_repository.tcl:100 lib/choose_repository.tcl:465
 msgid "Clone Existing Repository"
 msgstr "Склонировать существующий репозиторий"
 
-#: lib/choose_repository.tcl:100
+#: lib/choose_repository.tcl:106
 msgid "Clone..."
 msgstr "Склонировать..."
 
-#: lib/choose_repository.tcl:107 lib/choose_repository.tcl:976
+#: lib/choose_repository.tcl:113 lib/choose_repository.tcl:983
 msgid "Open Existing Repository"
 msgstr "Выбрать существующий репозиторий"
 
-#: lib/choose_repository.tcl:113
+#: lib/choose_repository.tcl:119
 msgid "Open..."
 msgstr "Открыть..."
 
-#: lib/choose_repository.tcl:126
+#: lib/choose_repository.tcl:132
 msgid "Recent Repositories"
 msgstr "Недавние репозитории"
 
-#: lib/choose_repository.tcl:132
+#: lib/choose_repository.tcl:138
 msgid "Open Recent Repository:"
 msgstr "Открыть последний репозиторий"
 
-#: lib/choose_repository.tcl:296 lib/choose_repository.tcl:303
-#: lib/choose_repository.tcl:310
+#: lib/choose_repository.tcl:302 lib/choose_repository.tcl:309
+#: lib/choose_repository.tcl:316
 #, tcl-format
 msgid "Failed to create repository %s:"
 msgstr "Не удалось создать репозиторий %s:"
 
-#: lib/choose_repository.tcl:381 lib/choose_repository.tcl:478
+#: lib/choose_repository.tcl:387
 msgid "Directory:"
 msgstr "Каталог:"
 
-#: lib/choose_repository.tcl:412 lib/choose_repository.tcl:537
-#: lib/choose_repository.tcl:1011
+#: lib/choose_repository.tcl:417 lib/choose_repository.tcl:544
+#: lib/choose_repository.tcl:1017
 msgid "Git Repository"
 msgstr "Репозиторий"
 
-#: lib/choose_repository.tcl:437
+#: lib/choose_repository.tcl:442
 #, tcl-format
 msgid "Directory %s already exists."
 msgstr "Каталог '%s' уже существует."
 
-#: lib/choose_repository.tcl:441
+#: lib/choose_repository.tcl:446
 #, tcl-format
 msgid "File %s already exists."
 msgstr "Файл '%s' уже существует."
 
-#: lib/choose_repository.tcl:455
+#: lib/choose_repository.tcl:460
 msgid "Clone"
 msgstr "Склонировать"
 
-#: lib/choose_repository.tcl:468
-msgid "URL:"
-msgstr "Ссылка:"
+#: lib/choose_repository.tcl:473
+msgid "Source Location:"
+msgstr "Исходное положение:"
+
+#: lib/choose_repository.tcl:484
+msgid "Target Directory:"
+msgstr "Каталог назначения:"
 
-#: lib/choose_repository.tcl:489
+#: lib/choose_repository.tcl:496
 msgid "Clone Type:"
 msgstr "Тип клона:"
 
-#: lib/choose_repository.tcl:495
+#: lib/choose_repository.tcl:502
 msgid "Standard (Fast, Semi-Redundant, Hardlinks)"
 msgstr "Стандартный (Быстрый, полуизбыточный, \"жесткие\" ссылки)"
 
-#: lib/choose_repository.tcl:501
+#: lib/choose_repository.tcl:508
 msgid "Full Copy (Slower, Redundant Backup)"
 msgstr "Полная копия (Медленный, создает резервную копию)"
 
-#: lib/choose_repository.tcl:507
+#: lib/choose_repository.tcl:514
 msgid "Shared (Fastest, Not Recommended, No Backup)"
 msgstr "Общий (Самый быстрый, не рекомендуется, без резервной копии)"
 
-#: lib/choose_repository.tcl:543 lib/choose_repository.tcl:590
-#: lib/choose_repository.tcl:736 lib/choose_repository.tcl:806
-#: lib/choose_repository.tcl:1017 lib/choose_repository.tcl:1025
+#: lib/choose_repository.tcl:550 lib/choose_repository.tcl:597
+#: lib/choose_repository.tcl:743 lib/choose_repository.tcl:813
+#: lib/choose_repository.tcl:1023 lib/choose_repository.tcl:1031
 #, tcl-format
 msgid "Not a Git repository: %s"
 msgstr "Каталог не является репозиторием: %s"
 
-#: lib/choose_repository.tcl:579
+#: lib/choose_repository.tcl:586
 msgid "Standard only available for local repository."
 msgstr "Стандартный клон возможен только для локального репозитория."
 
-#: lib/choose_repository.tcl:583
+#: lib/choose_repository.tcl:590
 msgid "Shared only available for local repository."
 msgstr "Общий клон возможен только для локального репозитория."
 
-#: lib/choose_repository.tcl:604
+#: lib/choose_repository.tcl:611
 #, tcl-format
 msgid "Location %s already exists."
 msgstr "Путь '%s' уже существует."
 
-#: lib/choose_repository.tcl:615
+#: lib/choose_repository.tcl:622
 msgid "Failed to configure origin"
 msgstr "Не могу сконфигурировать исходный репозиторий."
 
-#: lib/choose_repository.tcl:627
+#: lib/choose_repository.tcl:634
 msgid "Counting objects"
 msgstr "Считаю объекты"
 
-#: lib/choose_repository.tcl:628
+#: lib/choose_repository.tcl:635
 msgid "buckets"
 msgstr ""
 
-#: lib/choose_repository.tcl:652
+#: lib/choose_repository.tcl:659
 #, tcl-format
 msgid "Unable to copy objects/info/alternates: %s"
 msgstr "Не могу скопировать objects/info/alternates: %s"
 
-#: lib/choose_repository.tcl:688
+#: lib/choose_repository.tcl:695
 #, tcl-format
 msgid "Nothing to clone from %s."
 msgstr "Нечего клонировать с %s."
 
-#: lib/choose_repository.tcl:690 lib/choose_repository.tcl:904
-#: lib/choose_repository.tcl:916
+#: lib/choose_repository.tcl:697 lib/choose_repository.tcl:911
+#: lib/choose_repository.tcl:923
 msgid "The 'master' branch has not been initialized."
 msgstr "Не инициализирована ветвь 'master'."
 
-#: lib/choose_repository.tcl:703
+#: lib/choose_repository.tcl:710
 msgid "Hardlinks are unavailable.  Falling back to copying."
-msgstr "\"Жесткие ссылки\" не доступны. Буду использовать копирование."
+msgstr "\"Жесткие ссылки\" недоступны. Будет использовано копирование."
 
-#: lib/choose_repository.tcl:715
+#: lib/choose_repository.tcl:722
 #, tcl-format
 msgid "Cloning from %s"
 msgstr "Клонирование %s"
 
-#: lib/choose_repository.tcl:746
+#: lib/choose_repository.tcl:753
 msgid "Copying objects"
 msgstr "Копирование objects"
 
-#: lib/choose_repository.tcl:747
+#: lib/choose_repository.tcl:754
 msgid "KiB"
 msgstr "КБ"
 
-#: lib/choose_repository.tcl:771
+#: lib/choose_repository.tcl:778
 #, tcl-format
 msgid "Unable to copy object: %s"
 msgstr "Не могу скопировать объект: %s"
 
-#: lib/choose_repository.tcl:781
+#: lib/choose_repository.tcl:788
 msgid "Linking objects"
 msgstr "Создание ссылок на objects"
 
-#: lib/choose_repository.tcl:782
+#: lib/choose_repository.tcl:789
 msgid "objects"
 msgstr "объекты"
 
-#: lib/choose_repository.tcl:790
+#: lib/choose_repository.tcl:797
 #, tcl-format
 msgid "Unable to hardlink object: %s"
 msgstr "Не могу \"жестко связать\" объект: %s"
 
-#: lib/choose_repository.tcl:845
+#: lib/choose_repository.tcl:852
 msgid "Cannot fetch branches and objects.  See console output for details."
 msgstr ""
 "Не могу получить ветви и объекты. Дополнительная информация на консоли."
 
-#: lib/choose_repository.tcl:856
+#: lib/choose_repository.tcl:863
 msgid "Cannot fetch tags.  See console output for details."
 msgstr "Не могу получить метки. Дополнительная информация на консоли."
 
-#: lib/choose_repository.tcl:880
+#: lib/choose_repository.tcl:887
 msgid "Cannot determine HEAD.  See console output for details."
 msgstr "Не могу определить HEAD. Дополнительная информация на консоли."
 
-#: lib/choose_repository.tcl:889
+#: lib/choose_repository.tcl:896
 #, tcl-format
 msgid "Unable to cleanup %s"
 msgstr "Не могу очистить %s"
 
-#: lib/choose_repository.tcl:895
+#: lib/choose_repository.tcl:902
 msgid "Clone failed."
 msgstr "Клонирование не удалось."
 
-#: lib/choose_repository.tcl:902
+#: lib/choose_repository.tcl:909
 msgid "No default branch obtained."
 msgstr "Не было получено ветви по умолчанию."
 
-#: lib/choose_repository.tcl:913
+#: lib/choose_repository.tcl:920
 #, tcl-format
 msgid "Cannot resolve %s as a commit."
 msgstr "Не могу распознать %s как состояние."
 
-#: lib/choose_repository.tcl:925
+#: lib/choose_repository.tcl:932
 msgid "Creating working directory"
 msgstr "Создаю рабочий каталог"
 
-#: lib/choose_repository.tcl:926 lib/index.tcl:65 lib/index.tcl:127
-#: lib/index.tcl:193
+#: lib/choose_repository.tcl:933 lib/index.tcl:65 lib/index.tcl:128
+#: lib/index.tcl:196
 msgid "files"
 msgstr "файлов"
 
-#: lib/choose_repository.tcl:955
+#: lib/choose_repository.tcl:962
 msgid "Initial file checkout failed."
 msgstr "Не удалось получить начальное состояние файлов репозитория."
 
-#: lib/choose_repository.tcl:971
+#: lib/choose_repository.tcl:978
 msgid "Open"
 msgstr "Открыть"
 
-#: lib/choose_repository.tcl:981
+#: lib/choose_repository.tcl:988
 msgid "Repository:"
 msgstr "Репозиторий:"
 
-#: lib/choose_repository.tcl:1031
+#: lib/choose_repository.tcl:1037
 #, tcl-format
 msgid "Failed to open repository %s:"
 msgstr "Не удалось открыть репозиторий %s:"
@@ -1140,7 +1253,7 @@ msgstr "Ветвь слежения"
 
 #: lib/choose_rev.tcl:84 lib/choose_rev.tcl:538
 msgid "Tag"
-msgstr "Таг"
+msgstr "Ð\9cеÑ\82ка"
 
 #: lib/choose_rev.tcl:317
 #, tcl-format
@@ -1182,24 +1295,24 @@ msgid ""
 "completed.  You cannot amend the prior commit unless you first abort the "
 "current merge activity.\n"
 msgstr ""
-"Ð\9dевозможно Ð¸Ñ\81пÑ\80авиÑ\82Ñ\8c Ñ\81оÑ\81Ñ\82оÑ\8fние Ð²Ð¾ Ð²Ñ\80емÑ\8f Ð¾Ð±Ñ\8aединения.\n"
+"Ð\9dевозможно Ð¸Ñ\81пÑ\80авиÑ\82Ñ\8c Ñ\81оÑ\81Ñ\82оÑ\8fние Ð²Ð¾ Ð²Ñ\80емÑ\8f Ð¾Ð¿ÐµÑ\80аÑ\86ии Ñ\81лиÑ\8fния.\n"
 "\n"
-"Текущее объединение не завершено. Невозможно исправить предыдущее "
-"сохраненное состояние не прерывая текущее объединение.\n"
+"Текущее слияние не завершено. Невозможно исправить предыдущее "
+"сохраненное состояние, не прерывая эту операцию.\n"
 
-#: lib/commit.tcl:49
+#: lib/commit.tcl:48
 msgid "Error loading commit data for amend:"
 msgstr "Ошибка при загрузке данных для исправления сохраненного состояния:"
 
-#: lib/commit.tcl:76
+#: lib/commit.tcl:75
 msgid "Unable to obtain your identity:"
 msgstr "Невозможно получить информацию об авторстве:"
 
-#: lib/commit.tcl:81
+#: lib/commit.tcl:80
 msgid "Invalid GIT_COMMITTER_IDENT:"
 msgstr "Неверный GIT_COMMITTER_IDENT:"
 
-#: lib/commit.tcl:133
+#: lib/commit.tcl:132
 msgid ""
 "Last scanned state does not match repository state.\n"
 "\n"
@@ -1215,7 +1328,7 @@ msgstr ""
 "\n"
 "Это будет сделано сейчас автоматически.\n"
 
-#: lib/commit.tcl:154
+#: lib/commit.tcl:155
 #, tcl-format
 msgid ""
 "Unmerged files cannot be committed.\n"
@@ -1223,12 +1336,12 @@ msgid ""
 "File %s has merge conflicts.  You must resolve them and stage the file "
 "before committing.\n"
 msgstr ""
-"Нельзя сохранить необъединенные файлы.\n"
+"Нельзя сохранить файлы с незавершённой операцей слияния.\n"
 "\n"
-"Для файла %s возник конфликт объединения. Разрешите конфликт и добавьте к "
+"Для файла %s возник конфликт слияния. Разрешите конфликт и добавьте к "
 "подготовленным файлам перед сохранением.\n"
 
-#: lib/commit.tcl:162
+#: lib/commit.tcl:163
 #, tcl-format
 msgid ""
 "Unknown file state %s detected.\n"
@@ -1239,7 +1352,7 @@ msgstr ""
 "\n"
 "Файл %s не может быть сохранен данной программой.\n"
 
-#: lib/commit.tcl:170
+#: lib/commit.tcl:171
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1249,7 +1362,7 @@ msgstr ""
 "\n"
 "Подготовьте хотя бы один файл до создания сохраненного состояния.\n"
 
-#: lib/commit.tcl:183
+#: lib/commit.tcl:186
 msgid ""
 "Please supply a commit message.\n"
 "\n"
@@ -1267,45 +1380,45 @@ msgstr ""
 "- вторая строка пустая\n"
 "- оставшиеся строки: опишите, что дают ваши изменения.\n"
 
-#: lib/commit.tcl:207
+#: lib/commit.tcl:210
 #, tcl-format
 msgid "warning: Tcl does not support encoding '%s'."
 msgstr "предупреждение: Tcl не поддерживает кодировку '%s'."
 
-#: lib/commit.tcl:221
+#: lib/commit.tcl:226
 msgid "Calling pre-commit hook..."
 msgstr "Вызов программы поддержки репозитория pre-commit..."
 
-#: lib/commit.tcl:236
+#: lib/commit.tcl:241
 msgid "Commit declined by pre-commit hook."
 msgstr "Сохранение прервано программой поддержки репозитория pre-commit"
 
-#: lib/commit.tcl:259
+#: lib/commit.tcl:264
 msgid "Calling commit-msg hook..."
 msgstr "Вызов программы поддержки репозитория commit-msg..."
 
-#: lib/commit.tcl:274
+#: lib/commit.tcl:279
 msgid "Commit declined by commit-msg hook."
 msgstr "Сохранение прервано программой поддержки репозитория commit-msg"
 
-#: lib/commit.tcl:287
+#: lib/commit.tcl:292
 msgid "Committing changes..."
 msgstr "Сохранение изменений..."
 
-#: lib/commit.tcl:303
+#: lib/commit.tcl:308
 msgid "write-tree failed:"
 msgstr "Программа write-tree завершилась с ошибкой:"
 
-#: lib/commit.tcl:304 lib/commit.tcl:348 lib/commit.tcl:368
+#: lib/commit.tcl:309 lib/commit.tcl:353 lib/commit.tcl:373
 msgid "Commit failed."
 msgstr "Сохранить состояние не удалось."
 
-#: lib/commit.tcl:321
+#: lib/commit.tcl:326
 #, tcl-format
 msgid "Commit %s appears to be corrupt"
 msgstr "Состояние %s выглядит поврежденным"
 
-#: lib/commit.tcl:326
+#: lib/commit.tcl:331
 msgid ""
 "No changes to commit.\n"
 "\n"
@@ -1315,23 +1428,23 @@ msgid ""
 msgstr ""
 "Отсутствуют изменения для сохранения.\n"
 "\n"
-"Ни один файл не был изменен и не было объединения.\n"
+"Ни один файл не был изменен и не было слияния.\n"
 "\n"
 "Сейчас автоматически запустится перечитывание репозитория.\n"
 
-#: lib/commit.tcl:333
+#: lib/commit.tcl:338
 msgid "No changes to commit."
 msgstr "Отуствуют измения для сохранения."
 
-#: lib/commit.tcl:347
+#: lib/commit.tcl:352
 msgid "commit-tree failed:"
 msgstr "Программа commit-tree завершилась с ошибкой:"
 
-#: lib/commit.tcl:367
+#: lib/commit.tcl:372
 msgid "update-ref failed:"
 msgstr "Программа update-ref завершилась с ошибкой:"
 
-#: lib/commit.tcl:454
+#: lib/commit.tcl:460
 #, tcl-format
 msgid "Created commit %s: %s"
 msgstr "Создано состояние %s: %s "
@@ -1406,7 +1519,7 @@ msgstr ""
 msgid "Invalid date from Git: %s"
 msgstr "Неправильная дата в репозитории: %s"
 
-#: lib/diff.tcl:42
+#: lib/diff.tcl:59
 #, tcl-format
 msgid ""
 "No differences detected.\n"
@@ -1428,40 +1541,101 @@ msgstr ""
 "\n"
 "Сейчас будет запущено перечитывание репозитория, чтобы найти подобные файлы."
 
-#: lib/diff.tcl:81
+#: lib/diff.tcl:99
 #, tcl-format
 msgid "Loading diff of %s..."
 msgstr "Загрузка изменений в %s..."
 
-#: lib/diff.tcl:114 lib/diff.tcl:184
+#: lib/diff.tcl:120
+msgid ""
+"LOCAL: deleted\n"
+"REMOTE:\n"
+msgstr ""
+"ЛОКАЛЬНО: удалён\n"
+"ВНЕШНИЙ:\n"
+
+#: lib/diff.tcl:125
+msgid ""
+"REMOTE: deleted\n"
+"LOCAL:\n"
+msgstr ""
+"ВНЕШНИЙ: удалён\n"
+"ЛОКАЛЬНО:\n"
+
+#: lib/diff.tcl:132
+msgid "LOCAL:\n"
+msgstr "ЛОКАЛЬНО:\n"
+
+#: lib/diff.tcl:135
+msgid "REMOTE:\n"
+msgstr "ВНЕШНИЙ:\n"
+
+#: lib/diff.tcl:197 lib/diff.tcl:296
 #, tcl-format
 msgid "Unable to display %s"
 msgstr "Не могу показать %s"
 
-#: lib/diff.tcl:115
+#: lib/diff.tcl:198
 msgid "Error loading file:"
 msgstr "Ошибка загрузки файла:"
 
-#: lib/diff.tcl:122
+#: lib/diff.tcl:205
 msgid "Git Repository (subproject)"
 msgstr "Репозиторий Git (подпроект)"
 
-#: lib/diff.tcl:134
+#: lib/diff.tcl:217
 msgid "* Binary file (not showing content)."
 msgstr "* Двоичный файл (содержимое не показано)"
 
-#: lib/diff.tcl:185
-msgid "Error loading diff:"
-msgstr "Ошибка загрузки diff:"
+#: lib/diff.tcl:222
+#, tcl-format
+msgid ""
+"* Untracked file is %d bytes.\n"
+"* Showing only first %d bytes.\n"
+msgstr ""
+"* Размер неподготовленого файла %d байт.\n"
+"* Показано первых %d байт.\n"
+
+#: lib/diff.tcl:228
+#, tcl-format
+msgid ""
+"\n"
+"* Untracked file clipped here by %s.\n"
+"* To see the entire file, use an external editor.\n"
+msgstr ""
+"\n"
+"* Неподготовленый файл обрезан: %s.\n"
+"* Чтобы увидеть весь файл, используйте программу-редактор.\n"
 
-#: lib/diff.tcl:303
+#: lib/diff.tcl:436
 msgid "Failed to unstage selected hunk."
 msgstr "Не удалось исключить выбранную часть."
 
-#: lib/diff.tcl:310
+#: lib/diff.tcl:443
 msgid "Failed to stage selected hunk."
 msgstr "Не удалось подготовить к сохранению выбранную часть."
 
+#: lib/diff.tcl:509
+msgid "Failed to unstage selected line."
+msgstr "Не удалось исключить выбранную строку."
+
+#: lib/diff.tcl:517
+msgid "Failed to stage selected line."
+msgstr "Не удалось подготовить к сохранению выбранную строку."
+
+#: lib/encoding.tcl:443
+msgid "Default"
+msgstr "По умолчанию"
+
+#: lib/encoding.tcl:448
+#, tcl-format
+msgid "System (%s)"
+msgstr "Системная (%s)"
+
+#: lib/encoding.tcl:459 lib/encoding.tcl:465
+msgid "Other"
+msgstr "Другая"
+
 #: lib/error.tcl:20 lib/error.tcl:114
 msgid "error"
 msgstr "ошибка"
@@ -1480,7 +1654,7 @@ msgstr "Не удалось разблокировать индекс"
 
 #: lib/index.tcl:15
 msgid "Index Error"
-msgstr "Ð\9eÑ\88ибка Ð¸Ð½Ð´ÐµÐºÑ\81а"
+msgstr "Ð\9eÑ\88ибка Ð² Ð¸Ð½Ð´ÐµÐºÑ\81е"
 
 #: lib/index.tcl:21
 msgid ""
@@ -1498,50 +1672,59 @@ msgstr "Продолжить"
 msgid "Unlock Index"
 msgstr "Разблокировать индекс"
 
-#: lib/index.tcl:282
+#: lib/index.tcl:287
 #, tcl-format
 msgid "Unstaging %s from commit"
 msgstr "Удаление %s из подготовленного"
 
-#: lib/index.tcl:313
+#: lib/index.tcl:326
 msgid "Ready to commit."
 msgstr "Подготовлено для сохранения"
 
-#: lib/index.tcl:326
+#: lib/index.tcl:339
 #, tcl-format
 msgid "Adding %s"
 msgstr "Добавление %s..."
 
-#: lib/index.tcl:381
+#: lib/index.tcl:396
 #, tcl-format
 msgid "Revert changes in file %s?"
 msgstr "Отменить изменения в файле %s?"
 
-#: lib/index.tcl:383
+#: lib/index.tcl:398
 #, tcl-format
 msgid "Revert changes in these %i files?"
 msgstr "Отменить изменения в %i файле(-ах)?"
 
-#: lib/index.tcl:391
+#: lib/index.tcl:406
 msgid "Any unstaged changes will be permanently lost by the revert."
 msgstr ""
 "Любые изменения, не подготовленные к сохранению, будут потеряны при данной "
 "операции."
 
-#: lib/index.tcl:394
+#: lib/index.tcl:409
 msgid "Do Nothing"
 msgstr "Ничего не делать"
 
+#: lib/index.tcl:427
+msgid "Reverting selected files"
+msgstr "Удаление изменений в выбраных файлах"
+
+#: lib/index.tcl:431
+#, tcl-format
+msgid "Reverting %s"
+msgstr "Отмена изменений в %s"
+
 #: lib/merge.tcl:13
 msgid ""
 "Cannot merge while amending.\n"
 "\n"
 "You must finish amending this commit before starting any type of merge.\n"
 msgstr ""
-"Невозможно выполнить объединение во время исправления.\n"
+"Невозможно выполнить слияние во время исправления.\n"
 "\n"
 "Завершите исправление данного состояния перед выполнением операции "
-"объединения.\n"
+"слияния.\n"
 
 #: lib/merge.tcl:27
 msgid ""
@@ -1559,7 +1742,7 @@ msgstr ""
 "\n"
 "Это будет сделано сейчас автоматически.\n"
 
-#: lib/merge.tcl:44
+#: lib/merge.tcl:45
 #, tcl-format
 msgid ""
 "You are in the middle of a conflicted merge.\n"
@@ -1569,14 +1752,14 @@ msgid ""
 "You must resolve them, stage the file, and commit to complete the current "
 "merge.  Only then can you begin another merge.\n"
 msgstr ""
-"Предыдущее объединение не завершено из-за конфликта.\n"
+"Предыдущее слияние не завершено из-за конфликта.\n"
 "\n"
-"Для файла %s возник конфликт объединения.\n"
+"Для файла %s возник конфликт слияния.\n"
 "\n"
 "Разрешите конфликт, подготовьте файл и сохраните. Только после этого можно "
-"начать следующее объединение.\n"
+"начать следующее слияние.\n"
 
-#: lib/merge.tcl:54
+#: lib/merge.tcl:55
 #, tcl-format
 msgid ""
 "You are in the middle of a change.\n"
@@ -1590,36 +1773,37 @@ msgstr ""
 "\n"
 "Файл %s изменен.\n"
 "\n"
-"Подготовьте и сохраните измения перед началом объединения. В случае "
-"необходимости это позволит прервать операцию объединения.\n"
+"Подготовьте и сохраните измения перед началом слияния. В случае "
+"необходимости это позволит прервать операцию слияния.\n"
 
-#: lib/merge.tcl:106
+#: lib/merge.tcl:107
 #, tcl-format
 msgid "%s of %s"
 msgstr "%s из %s"
 
-#: lib/merge.tcl:119
+#: lib/merge.tcl:120
+#, tcl-format
 msgid "Merging %s and %s..."
-msgstr "Ð\9eбÑ\8aединение %s и %s..."
+msgstr "СлиÑ\8fние %s и %s..."
 
-#: lib/merge.tcl:130
+#: lib/merge.tcl:131
 msgid "Merge completed successfully."
-msgstr "Ð\9eбÑ\8aединение успешно завершено."
+msgstr "СлиÑ\8fние успешно завершено."
 
-#: lib/merge.tcl:132
+#: lib/merge.tcl:133
 msgid "Merge failed.  Conflict resolution is required."
-msgstr "Не удалось завершить объединение. Требуется разрешение конфликта."
+msgstr "Не удалось завершить слияние. Требуется разрешение конфликта."
 
-#: lib/merge.tcl:157
+#: lib/merge.tcl:158
 #, tcl-format
 msgid "Merge Into %s"
-msgstr "Ð\9eбÑ\8aединиÑ\82Ñ\8c с %s"
+msgstr "СлиÑ\8fние с %s"
 
-#: lib/merge.tcl:176
+#: lib/merge.tcl:177
 msgid "Revision To Merge"
-msgstr "Версия для объединения"
+msgstr "Версия, с которой провести слияние"
 
-#: lib/merge.tcl:211
+#: lib/merge.tcl:212
 msgid ""
 "Cannot abort while amending.\n"
 "\n"
@@ -1629,7 +1813,7 @@ msgstr ""
 "\n"
 "Завершите текущее исправление сохраненного состояния.\n"
 
-#: lib/merge.tcl:221
+#: lib/merge.tcl:222
 msgid ""
 "Abort merge?\n"
 "\n"
@@ -1637,13 +1821,13 @@ msgid ""
 "\n"
 "Continue with aborting the current merge?"
 msgstr ""
-"Ð\9fÑ\80еÑ\80ваÑ\82Ñ\8c Ð¾Ð±Ñ\8aединение?\n"
+"Ð\9fÑ\80еÑ\80ваÑ\82Ñ\8c Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8e Ñ\81лиÑ\8fниÑ\8f?\n"
 "\n"
-"Прерывание объединения приведет к потере *ВСЕХ* несохраненных изменений.\n"
+"Прерывание этой операции приведет к потере *ВСЕХ* несохраненных изменений.\n"
 "\n"
 "Продолжить?"
 
-#: lib/merge.tcl:227
+#: lib/merge.tcl:228
 msgid ""
 "Reset changes?\n"
 "\n"
@@ -1651,130 +1835,346 @@ msgid ""
 "\n"
 "Continue with resetting the current changes?"
 msgstr ""
-"Ð\9fÑ\80еÑ\80ваÑ\82Ñ\8c Ð¾Ð±Ñ\8aединение?\n"
+"Ð\9fÑ\80еÑ\80ваÑ\82Ñ\8c Ð¾Ð¿ÐµÑ\80аÑ\86иÑ\8e Ñ\81лиÑ\8fниÑ\8f?\n"
 "\n"
-"Прерывание объединения приведет к потере *ВСЕХ* несохраненных изменений.\n"
+"Прерывание этой операции приведет к потере *ВСЕХ* несохраненных изменений.\n"
 "\n"
 "Продолжить?"
 
-#: lib/merge.tcl:238
+#: lib/merge.tcl:239
 msgid "Aborting"
 msgstr "Прерываю"
 
-#: lib/merge.tcl:238
+#: lib/merge.tcl:239
 msgid "files reset"
 msgstr "изменения в файлах отменены"
 
-#: lib/merge.tcl:265
+#: lib/merge.tcl:267
 msgid "Abort failed."
 msgstr "Прервать не удалось."
 
-#: lib/merge.tcl:267
+#: lib/merge.tcl:269
 msgid "Abort completed.  Ready."
 msgstr "Прервано."
 
-#: lib/option.tcl:95
+#: lib/mergetool.tcl:8
+msgid "Force resolution to the base version?"
+msgstr "Использовать базовую версию для разрешения конфликта?"
+
+#: lib/mergetool.tcl:9
+msgid "Force resolution to this branch?"
+msgstr "Использовать версию этой ветви для разрешения конфликта?"
+
+#: lib/mergetool.tcl:10
+msgid "Force resolution to the other branch?"
+msgstr "Использовать версию другой ветви для разрешения конфликта?"
+
+#: lib/mergetool.tcl:14
+#, tcl-format
+msgid ""
+"Note that the diff shows only conflicting changes.\n"
+"\n"
+"%s will be overwritten.\n"
+"\n"
+"This operation can be undone only by restarting the merge."
+msgstr ""
+"Внимание! Список изменений показывает только конфликтующие отличия.\n"
+"\n"
+"%s будет переписан.\n"
+"\n"
+"Это действие можно отменить только перезапуском операции слияния."
+
+#: lib/mergetool.tcl:45
+#, tcl-format
+msgid "File %s seems to have unresolved conflicts, still stage?"
+msgstr ""
+"Файл %s кажется содержит необработаные конфликты. "
+"Продолжить подготовку к сохранению?"
+
+#: lib/mergetool.tcl:60
+#, tcl-format
+msgid "Adding resolution for %s"
+msgstr "Добавляю результат разрешения для %s"
+
+#: lib/mergetool.tcl:141
+msgid "Cannot resolve deletion or link conflicts using a tool"
+msgstr ""
+"Программа слияния не обрабатывает конфликты с удалением или участием ссылок"
+
+#: lib/mergetool.tcl:146
+msgid "Conflict file does not exist"
+msgstr "Конфликтующий файл не существует"
+
+#: lib/mergetool.tcl:264
+#, tcl-format
+msgid "Not a GUI merge tool: '%s'"
+msgstr "'%s' не является программой слияния"
+
+#: lib/mergetool.tcl:268
+#, tcl-format
+msgid "Unsupported merge tool '%s'"
+msgstr "Неизвестная программа слияния '%s'"
+
+#: lib/mergetool.tcl:303
+msgid "Merge tool is already running, terminate it?"
+msgstr "Программа слияния уже работает. Прервать?"
+
+#: lib/mergetool.tcl:323
+#, tcl-format
+msgid ""
+"Error retrieving versions:\n"
+"%s"
+msgstr ""
+"Ошибка получения версий:\n"
+"%s"
+
+#: lib/mergetool.tcl:343
+#, tcl-format
+msgid ""
+"Could not start the merge tool:\n"
+"\n"
+"%s"
+msgstr ""
+"Ошибка запуска программы слияния:\n"
+"\n"
+"%s"
+
+#: lib/mergetool.tcl:347
+msgid "Running merge tool..."
+msgstr "Запуск программы слияния..."
+
+#: lib/mergetool.tcl:375 lib/mergetool.tcl:383
+msgid "Merge tool failed."
+msgstr "Ошибка выполнения программы слияния."
+
+#: lib/option.tcl:11
+#, tcl-format
+msgid "Invalid global encoding '%s'"
+msgstr "Ошибка в глобальной установке кодировки '%s'"
+
+#: lib/option.tcl:19
+#, tcl-format
+msgid "Invalid repo encoding '%s'"
+msgstr "Неверная кодировка репозитория: '%s'"
+
+#: lib/option.tcl:117
 msgid "Restore Defaults"
 msgstr "Восстановить настройки по умолчанию"
 
-#: lib/option.tcl:99
+#: lib/option.tcl:121
 msgid "Save"
 msgstr "Сохранить"
 
-#: lib/option.tcl:109
+#: lib/option.tcl:131
 #, tcl-format
 msgid "%s Repository"
-msgstr "для репозитория %s"
+msgstr "Ð\94ля репозитория %s"
 
-#: lib/option.tcl:110
+#: lib/option.tcl:132
 msgid "Global (All Repositories)"
 msgstr "Общие (для всех репозиториев)"
 
-#: lib/option.tcl:116
+#: lib/option.tcl:138
 msgid "User Name"
 msgstr "Имя пользователя"
 
-#: lib/option.tcl:117
+#: lib/option.tcl:139
 msgid "Email Address"
 msgstr "Адрес электронной почты"
 
-#: lib/option.tcl:119
+#: lib/option.tcl:141
 msgid "Summarize Merge Commits"
-msgstr "Суммарный комментарий при объединении"
+msgstr "Суммарный комментарий при слиянии"
 
-#: lib/option.tcl:120
+#: lib/option.tcl:142
 msgid "Merge Verbosity"
-msgstr "Уровень детальности сообщений при объединении"
+msgstr "Уровень детальности сообщений при слиянии"
 
-#: lib/option.tcl:121
+#: lib/option.tcl:143
 msgid "Show Diffstat After Merge"
-msgstr "Показать отчет об изменениях после объединения"
+msgstr "Показать отчет об изменениях после слияния"
+
+#: lib/option.tcl:144
+msgid "Use Merge Tool"
+msgstr "Использовать для слияния программу"
 
-#: lib/option.tcl:123
+#: lib/option.tcl:146
 msgid "Trust File Modification Timestamps"
 msgstr "Доверять времени модификации файла"
 
-#: lib/option.tcl:124
+#: lib/option.tcl:147
 msgid "Prune Tracking Branches During Fetch"
 msgstr "Чистка ветвей слежения при получении изменений"
 
-#: lib/option.tcl:125
+#: lib/option.tcl:148
 msgid "Match Tracking Branches"
 msgstr "Имя новой ветви взять из имен ветвей слежения"
 
-#: lib/option.tcl:126
+#: lib/option.tcl:149
+msgid "Blame Copy Only On Changed Files"
+msgstr "Поиск копий только в изменённых файлах"
+
+#: lib/option.tcl:150
+msgid "Minimum Letters To Blame Copy On"
+msgstr "Минимальное количество символов для поиска копий"
+
+#: lib/option.tcl:151
+msgid "Blame History Context Radius (days)"
+msgstr "Радиус исторического контекста (в днях)"
+
+#: lib/option.tcl:152
 msgid "Number of Diff Context Lines"
 msgstr "Число строк в контексте diff"
 
-#: lib/option.tcl:127
+#: lib/option.tcl:153
 msgid "Commit Message Text Width"
-msgstr "Ширина комментария к состоянию:"
+msgstr "Ширина текста комментария"
 
-#: lib/option.tcl:128
+#: lib/option.tcl:154
 msgid "New Branch Name Template"
 msgstr "Шаблон для имени новой ветви"
 
-#: lib/option.tcl:192
+#: lib/option.tcl:155
+msgid "Default File Contents Encoding"
+msgstr "Кодировка содержания файла по умолчанию"
+
+#: lib/option.tcl:203
+msgid "Change"
+msgstr "Изменить"
+
+#: lib/option.tcl:230
 msgid "Spelling Dictionary:"
 msgstr "Словарь для проверки правописания:"
 
-#: lib/option.tcl:216
+#: lib/option.tcl:254
 msgid "Change Font"
-msgstr "Изменить шрифт"
+msgstr "Изменить"
 
-#: lib/option.tcl:220
+#: lib/option.tcl:258
 #, tcl-format
 msgid "Choose %s"
 msgstr "Выберите %s"
 
 # carbon copy
-#: lib/option.tcl:226
+#: lib/option.tcl:264
 msgid "pt."
-msgstr ""
+msgstr "pt."
 
-#: lib/option.tcl:240
+#: lib/option.tcl:278
 msgid "Preferences"
 msgstr "Настройки"
 
-#: lib/option.tcl:275
+#: lib/option.tcl:314
 msgid "Failed to completely save options:"
 msgstr "Не удалось полностью сохранить настройки:"
 
+#: lib/remote.tcl:163
+msgid "Remove Remote"
+msgstr "Удалить ссылку на внешний репозиторий"
+
+#: lib/remote.tcl:168
+msgid "Prune from"
+msgstr "Чистка"
+
+#: lib/remote.tcl:173
+msgid "Fetch from"
+msgstr "Получение из"
+
+#: lib/remote.tcl:215
+msgid "Push to"
+msgstr "Отправить"
+
+#: lib/remote_add.tcl:19
+msgid "Add Remote"
+msgstr "Зарегистрировать внешний репозиторий"
+
+#: lib/remote_add.tcl:24
+msgid "Add New Remote"
+msgstr "Добавить внешний репозиторий"
+
+#: lib/remote_add.tcl:28 lib/tools_dlg.tcl:36
+msgid "Add"
+msgstr ""
+
+#: lib/remote_add.tcl:37
+msgid "Remote Details"
+msgstr "Информация о внешнем репозитории"
+
+#: lib/remote_add.tcl:50
+msgid "Location:"
+msgstr "Положение:"
+
+#: lib/remote_add.tcl:62
+msgid "Further Action"
+msgstr "Следующая операция"
+
+#: lib/remote_add.tcl:65
+msgid "Fetch Immediately"
+msgstr "Скачать сразу"
+
+#: lib/remote_add.tcl:71
+msgid "Initialize Remote Repository and Push"
+msgstr "Инициализировать внешний репозиторий и отправить"
+
+#: lib/remote_add.tcl:77
+msgid "Do Nothing Else Now"
+msgstr "Больше ничего не делать"
+
+#: lib/remote_add.tcl:101
+msgid "Please supply a remote name."
+msgstr "Укажите название внешнего репозитория."
+
+#: lib/remote_add.tcl:114
+#, tcl-format
+msgid "'%s' is not an acceptable remote name."
+msgstr "Недопустимое название внешнего репозитория '%s'."
+
+#: lib/remote_add.tcl:125
+#, tcl-format
+msgid "Failed to add remote '%s' of location '%s'."
+msgstr "Не удалось добавить '%s' из '%s'. "
+
+#: lib/remote_add.tcl:133 lib/transport.tcl:6
+#, tcl-format
+msgid "fetch %s"
+msgstr "получение %s"
+
+#: lib/remote_add.tcl:134
+#, tcl-format
+msgid "Fetching the %s"
+msgstr "Получение %s"
+
+#: lib/remote_add.tcl:157
+#, tcl-format
+msgid "Do not know how to initialize repository at location '%s'."
+msgstr "Невозможно инициалировать репозиторий в '%s'."
+
+#: lib/remote_add.tcl:163 lib/transport.tcl:25 lib/transport.tcl:63
+#: lib/transport.tcl:81
+#, tcl-format
+msgid "push %s"
+msgstr "отправить %s"
+
+#: lib/remote_add.tcl:164
+#, tcl-format
+msgid "Setting up the %s (at %s)"
+msgstr "Настройка %s (в %s)"
+
 #: lib/remote_branch_delete.tcl:29 lib/remote_branch_delete.tcl:34
-msgid "Delete Remote Branch"
-msgstr "УдалиÑ\82Ñ\8c Ð²Ð½ÐµÑ\88нÑ\8eÑ\8e Ð²ÐµÑ\82вÑ\8c"
+msgid "Delete Branch Remotely"
+msgstr "Удаление Ð²ÐµÑ\82ви Ð²Ð¾ Ð²Ð½ÐµÑ\88нем Ñ\80епозиÑ\82оÑ\80ии"
 
 #: lib/remote_branch_delete.tcl:47
 msgid "From Repository"
 msgstr "Из репозитория"
 
-#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:123
+#: lib/remote_branch_delete.tcl:50 lib/transport.tcl:134
 msgid "Remote:"
 msgstr "внешний:"
 
-#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:138
-msgid "Arbitrary URL:"
-msgstr "по Ñ\83казанномÑ\83 URL:"
+#: lib/remote_branch_delete.tcl:66 lib/transport.tcl:149
+msgid "Arbitrary Location:"
+msgstr "Указаное Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ðµ:"
 
 #: lib/remote_branch_delete.tcl:84
 msgid "Branches"
@@ -1786,15 +2186,15 @@ msgstr "Удалить только в случае, если"
 
 #: lib/remote_branch_delete.tcl:111
 msgid "Merged Into:"
-msgstr "Ð\9eбÑ\8aединено с:"
+msgstr "СлиÑ\8fние с:"
 
 #: lib/remote_branch_delete.tcl:119
 msgid "Always (Do not perform merge checks)"
-msgstr "Ð\92Ñ\81егда (не Ð²Ñ\8bполнÑ\8fÑ\82Ñ\8c Ð¿Ñ\80овеÑ\80кÑ\83 Ð¾Ð±Ñ\8aединений)"
+msgstr "Ð\92Ñ\81егда (не Ð²Ñ\8bполнÑ\8fÑ\82Ñ\8c Ð¿Ñ\80овеÑ\80кÑ\83 Ð½Ð° Ñ\81лиÑ\8fние)"
 
 #: lib/remote_branch_delete.tcl:152
 msgid "A branch is required for 'Merged Into'."
-msgstr "Ð\94лÑ\8f Ð¾Ð¿Ñ\86ии 'Ð\9eбÑ\8aединено с' требуется указать ветвь."
+msgstr "Ð\94лÑ\8f Ð¾Ð¿Ñ\86ии 'СлиÑ\8fние с' требуется указать ветвь."
 
 #: lib/remote_branch_delete.tcl:184
 #, tcl-format
@@ -1803,7 +2203,8 @@ msgid ""
 "\n"
 " - %s"
 msgstr ""
-"Следующие ветви объединены с %s не полностью:\n"
+"Следующие ветви могут быть объединены с %s при помощи операции слияния:\n"
+"\n"
 " - %s"
 
 #: lib/remote_branch_delete.tcl:189
@@ -1812,7 +2213,7 @@ msgid ""
 "One or more of the merge tests failed because you have not fetched the "
 "necessary commits.  Try fetching from %s first."
 msgstr ""
-"Ð\9eдин Ð¸Ð»Ð¸ Ð½ÐµÑ\81колÑ\8cко Ñ\82еÑ\81Ñ\82ов Ð½Ð° Ð¾Ð±Ñ\8aединение не прошли, потому что Вы не "
+"Ð\9dекоÑ\82оÑ\80Ñ\8bе Ñ\82еÑ\81Ñ\82Ñ\8b Ð½Ð° Ñ\81лиÑ\8fние не прошли, потому что Вы не "
 "получили необходимые состояния. Попытайтесь получить их из %s."
 
 #: lib/remote_branch_delete.tcl:207
@@ -1843,17 +2244,21 @@ msgstr "Не указан репозиторий."
 msgid "Scanning %s..."
 msgstr "Перечитывание %s... "
 
-#: lib/remote.tcl:165
-msgid "Prune from"
-msgstr "ЧиÑ\81Ñ\82ка"
+#: lib/search.tcl:21
+msgid "Find:"
+msgstr "Ð\9fоиÑ\81к:"
 
-#: lib/remote.tcl:170
-msgid "Fetch from"
-msgstr "Ð\9fолÑ\83Ñ\87ение Ð¸Ð·"
+#: lib/search.tcl:23
+msgid "Next"
+msgstr "Ð\94алÑ\8cÑ\88е"
 
-#: lib/remote.tcl:213
-msgid "Push to"
-msgstr "Отправить"
+#: lib/search.tcl:24
+msgid "Prev"
+msgstr "Обратно"
+
+#: lib/search.tcl:25
+msgid "Case-Sensitive"
+msgstr "Игн. большие/маленькие"
 
 #: lib/shortcut.tcl:20 lib/shortcut.tcl:61
 msgid "Cannot write shortcut:"
@@ -1888,27 +2293,192 @@ msgstr "Программа проверки правописания не смо
 msgid "Unrecognized spell checker"
 msgstr "Нераспознаная программа проверки правописания"
 
-#: lib/spellcheck.tcl:180
+#: lib/spellcheck.tcl:186
 msgid "No Suggestions"
 msgstr "Исправлений не найдено"
 
-#: lib/spellcheck.tcl:381
+#: lib/spellcheck.tcl:388
 msgid "Unexpected EOF from spell checker"
 msgstr "Программа проверки правописания прервала передачу данных"
 
-#: lib/spellcheck.tcl:385
+#: lib/spellcheck.tcl:392
 msgid "Spell Checker Failed"
 msgstr "Ошибка проверки правописания"
 
+#: lib/sshkey.tcl:31
+msgid "No keys found."
+msgstr "Ключ не найден"
+
+#: lib/sshkey.tcl:34
+#, tcl-format
+msgid "Found a public key in: %s"
+msgstr "Публичный ключ из %s"
+
+#: lib/sshkey.tcl:40
+msgid "Generate Key"
+msgstr "Создать ключ"
+
+#: lib/sshkey.tcl:56
+msgid "Copy To Clipboard"
+msgstr "Скопировать в буфер обмена"
+
+#: lib/sshkey.tcl:70
+msgid "Your OpenSSH Public Key"
+msgstr "Ваш публичный ключ OpenSSH"
+
+#: lib/sshkey.tcl:78
+msgid "Generating..."
+msgstr "Создание..."
+
+#: lib/sshkey.tcl:84
+#, tcl-format
+msgid ""
+"Could not start ssh-keygen:\n"
+"\n"
+"%s"
+msgstr ""
+"Ошибка запуска ssh-keygen:\n"
+"\n"
+"%s"
+
+#: lib/sshkey.tcl:111
+msgid "Generation failed."
+msgstr "Ключ не создан."
+
+#: lib/sshkey.tcl:118
+msgid "Generation succeded, but no keys found."
+msgstr "Создание ключа завершилось, но результат не был найден"
+
+#: lib/sshkey.tcl:121
+#, tcl-format
+msgid "Your key is in: %s"
+msgstr "Ваш ключ находится в: %s"
+
 #: lib/status_bar.tcl:83
 #, tcl-format
 msgid "%s ... %*i of %*i %s (%3i%%)"
 msgstr "%s ... %*i из %*i %s (%3i%%)"
 
-#: lib/transport.tcl:6
+#: lib/tools.tcl:75
 #, tcl-format
-msgid "fetch %s"
-msgstr "получение %s"
+msgid "Running %s requires a selected file."
+msgstr "Запуск %s требует выбранного файла."
+
+#: lib/tools.tcl:90
+#, tcl-format
+msgid "Are you sure you want to run %s?"
+msgstr "Действительно запустить %s?"
+
+#: lib/tools.tcl:110
+#, tcl-format
+msgid "Tool: %s"
+msgstr "Вспомогательная операция: %s"
+
+#: lib/tools.tcl:111
+#, tcl-format
+msgid "Running: %s"
+msgstr "Выполнение: %s"
+
+#: lib/tools.tcl:149
+#, tcl-format
+msgid "Tool completed succesfully: %s"
+msgstr "Программа %s успешно завершилась."
+
+#: lib/tools.tcl:151
+#, tcl-format
+msgid "Tool failed: %s"
+msgstr "Ошибка выполнения программы: %s"
+
+#: lib/tools_dlg.tcl:22
+msgid "Add Tool"
+msgstr "Добавить вспомогательную операцию"
+
+#: lib/tools_dlg.tcl:28
+msgid "Add New Tool Command"
+msgstr "Новая вспомогательная операция"
+
+#: lib/tools_dlg.tcl:33
+msgid "Add globally"
+msgstr "Добавить для всех репозиториев"
+
+#: lib/tools_dlg.tcl:45
+msgid "Tool Details"
+msgstr "Описание вспомогательной операции"
+
+#: lib/tools_dlg.tcl:48
+msgid "Use '/' separators to create a submenu tree:"
+msgstr "Испольуйте '/' для создания подменю"
+
+#: lib/tools_dlg.tcl:61
+msgid "Command:"
+msgstr "Команда:"
+
+#: lib/tools_dlg.tcl:74
+msgid "Show a dialog before running"
+msgstr "Показать диалог перед запуском"
+
+#: lib/tools_dlg.tcl:80
+msgid "Ask the user to select a revision (sets $REVISION)"
+msgstr "Запрос на выбор версии (устанавливает $REVISION)"
+
+#: lib/tools_dlg.tcl:85
+msgid "Ask the user for additional arguments (sets $ARGS)"
+msgstr "Запрос дополнительных аргументов (устанавливает $ARGS)"
+
+#: lib/tools_dlg.tcl:92
+msgid "Don't show the command output window"
+msgstr "Не показывать окно вывода команды"
+
+#: lib/tools_dlg.tcl:97
+msgid "Run only if a diff is selected ($FILENAME not empty)"
+msgstr "Запуск только если показан список изменений ($FILENAME не пусто)"
+
+#: lib/tools_dlg.tcl:121
+msgid "Please supply a name for the tool."
+msgstr "Укажите название вспомогательной операции."
+
+#: lib/tools_dlg.tcl:129
+#, tcl-format
+msgid "Tool '%s' already exists."
+msgstr "Вспомогательная операция '%s' уже существует."
+
+#: lib/tools_dlg.tcl:151
+#, tcl-format
+msgid ""
+"Could not add tool:\n"
+"%s"
+msgstr ""
+"Ошибка добавления программы:\n"
+"%s"
+
+#: lib/tools_dlg.tcl:190
+msgid "Remove Tool"
+msgstr "Удалить программу"
+
+#: lib/tools_dlg.tcl:196
+msgid "Remove Tool Commands"
+msgstr "Удалить команды программы"
+
+#: lib/tools_dlg.tcl:200
+msgid "Remove"
+msgstr "Удалить"
+
+#: lib/tools_dlg.tcl:236
+msgid "(Blue denotes repository-local tools)"
+msgstr "(Синим выделены программы локальные репозиторию)"
+
+#: lib/tools_dlg.tcl:297
+#, tcl-format
+msgid "Run Command: %s"
+msgstr "Запуск команды: %s"
+
+#: lib/tools_dlg.tcl:311
+msgid "Arguments"
+msgstr "Аргументы"
+
+#: lib/tools_dlg.tcl:348
+msgid "OK"
+msgstr "OK"
 
 #: lib/transport.tcl:7
 #, tcl-format
@@ -1926,48 +2496,46 @@ msgstr "чистка внешнего %s"
 msgid "Pruning tracking branches deleted from %s"
 msgstr "Чистка ветвей слежения, удаленных из %s"
 
-#: lib/transport.tcl:25 lib/transport.tcl:71
-#, tcl-format
-msgid "push %s"
-msgstr "отправить %s"
-
 #: lib/transport.tcl:26
 #, tcl-format
 msgid "Pushing changes to %s"
 msgstr "Отправка изменений в %s "
 
-#: lib/transport.tcl:72
+#: lib/transport.tcl:64
+#, tcl-format
+msgid "Mirroring to %s"
+msgstr "Точное копирование в %s"
+
+#: lib/transport.tcl:82
 #, tcl-format
 msgid "Pushing %s %s to %s"
 msgstr "Отправка %s %s в %s"
 
-#: lib/transport.tcl:89
+#: lib/transport.tcl:100
 msgid "Push Branches"
 msgstr "Отправить изменения в ветвях"
 
-#: lib/transport.tcl:103
+#: lib/transport.tcl:114
 msgid "Source Branches"
 msgstr "Исходные ветви"
 
-#: lib/transport.tcl:120
+#: lib/transport.tcl:131
 msgid "Destination Repository"
 msgstr "Репозиторий назначения"
 
-#: lib/transport.tcl:158
+#: lib/transport.tcl:169
 msgid "Transfer Options"
 msgstr "Настройки отправки"
 
-#: lib/transport.tcl:160
+#: lib/transport.tcl:171
 msgid "Force overwrite existing branch (may discard changes)"
 msgstr "Намеренно переписать существующую ветвь (возможна потеря изменений)"
 
-#: lib/transport.tcl:164
+#: lib/transport.tcl:175
 msgid "Use thin pack (for slow network connections)"
 msgstr "Использовать thin pack (для медленных сетевых подключений)"
 
-#: lib/transport.tcl:168
+#: lib/transport.tcl:179
 msgid "Include tags"
-msgstr "Передать таги"
+msgstr "Передать метки"
 
-#~ msgid "Next >"
-#~ msgstr "Дальше >"
index 167654c7094c73f85b212ce5f48c401a31fca91b..c1535f94e86f2d619b4609965324a7c8ca0b396c 100644 (file)
@@ -780,16 +780,6 @@ msgstr "Alltid (utför inte sammanslagningstest)."
 msgid "The following branches are not completely merged into %s:"
 msgstr "Följande grenar är inte till fullo sammanslagna med %s:"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"Det är svårt att återställa borttagna grenar.\n"
-"\n"
-" Ta bort valda grenar?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
@@ -2398,7 +2388,7 @@ msgstr "Exekverar: %s"
 
 #: lib/tools.tcl:149
 #, tcl-format
-msgid "Tool completed succesfully: %s"
+msgid "Tool completed successfully: %s"
 msgstr "Verktyget avslutades framgångsrikt: %s"
 
 #: lib/tools.tcl:151
index d2c686667163ec4cd83045efd429e3413564290e..91c1be23c2bb8e87a6e08188f5a0f0b860af4242 100644 (file)
@@ -676,16 +676,6 @@ msgstr "总是合并 (不作合并测试.)"
 msgid "The following branches are not completely merged into %s:"
 msgstr "下列分支没有完全被合并到 %s:"
 
-#: lib/branch_delete.tcl:115
-msgid ""
-"Recovering deleted branches is difficult. \n"
-"\n"
-" Delete the selected branches?"
-msgstr ""
-"恢复被删除的分支非常困难.\n"
-"\n"
-"是否要删除所选分支?"
-
 #: lib/branch_delete.tcl:141
 #, tcl-format
 msgid ""
index 53c3a94686813936445efbb055dc4f02885c70e9..66bbb2f8faaf83bc87819a9e288a0592f400e147 100644 (file)
@@ -3,7 +3,12 @@
 exec wish "$0" -- "$@"
 
 if { $argc >=2 && [lindex $argv 0] == "--working-dir" } {
-       cd [lindex $argv 1]
+       set workdir [lindex $argv 1]
+       cd $workdir
+       if {[lindex [file split $workdir] end] eq {.git}} {
+               # Workaround for Explorer right click "Git GUI Here" on .git/
+               cd ..
+       }
        set argv [lrange $argv 2 end]
        incr argc -2
 }
index 0843372b57371b62cd68f2818f634209f55d5395..5f4419b69b58769e8fee7a60109520a095a831c5 100755 (executable)
@@ -49,7 +49,7 @@ resolve_full_httpd () {
        esac
 
        httpd_only="$(echo $httpd | cut -f1 -d' ')"
-       if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null;; esac
+       if case "$httpd_only" in /*) : ;; *) which $httpd_only >/dev/null 2>&1;; esac
        then
                full_httpd=$httpd
        else
@@ -179,11 +179,74 @@ lighttpd_conf () {
        cat > "$conf" <<EOF
 server.document-root = "$fqgitdir/gitweb"
 server.port = $port
-server.modules = ( "mod_cgi" )
+server.modules = ( "mod_setenv", "mod_cgi" )
 server.indexfiles = ( "gitweb.cgi" )
 server.pid-file = "$fqgitdir/pid"
+server.errorlog = "$fqgitdir/gitweb/error.log"
+
+# to enable, add "mod_access", "mod_accesslog" to server.modules
+# variable above and uncomment this
+#accesslog.filename = "$fqgitdir/gitweb/access.log"
+
+setenv.add-environment = ( "PATH" => "/usr/local/bin:/usr/bin:/bin" )
+
 cgi.assign = ( ".cgi" => "" )
-mimetype.assign = ( ".css" => "text/css" )
+
+# mimetype mapping
+mimetype.assign             = (
+  ".pdf"          =>      "application/pdf",
+  ".sig"          =>      "application/pgp-signature",
+  ".spl"          =>      "application/futuresplash",
+  ".class"        =>      "application/octet-stream",
+  ".ps"           =>      "application/postscript",
+  ".torrent"      =>      "application/x-bittorrent",
+  ".dvi"          =>      "application/x-dvi",
+  ".gz"           =>      "application/x-gzip",
+  ".pac"          =>      "application/x-ns-proxy-autoconfig",
+  ".swf"          =>      "application/x-shockwave-flash",
+  ".tar.gz"       =>      "application/x-tgz",
+  ".tgz"          =>      "application/x-tgz",
+  ".tar"          =>      "application/x-tar",
+  ".zip"          =>      "application/zip",
+  ".mp3"          =>      "audio/mpeg",
+  ".m3u"          =>      "audio/x-mpegurl",
+  ".wma"          =>      "audio/x-ms-wma",
+  ".wax"          =>      "audio/x-ms-wax",
+  ".ogg"          =>      "application/ogg",
+  ".wav"          =>      "audio/x-wav",
+  ".gif"          =>      "image/gif",
+  ".jpg"          =>      "image/jpeg",
+  ".jpeg"         =>      "image/jpeg",
+  ".png"          =>      "image/png",
+  ".xbm"          =>      "image/x-xbitmap",
+  ".xpm"          =>      "image/x-xpixmap",
+  ".xwd"          =>      "image/x-xwindowdump",
+  ".css"          =>      "text/css",
+  ".html"         =>      "text/html",
+  ".htm"          =>      "text/html",
+  ".js"           =>      "text/javascript",
+  ".asc"          =>      "text/plain",
+  ".c"            =>      "text/plain",
+  ".cpp"          =>      "text/plain",
+  ".log"          =>      "text/plain",
+  ".conf"         =>      "text/plain",
+  ".text"         =>      "text/plain",
+  ".txt"          =>      "text/plain",
+  ".dtd"          =>      "text/xml",
+  ".xml"          =>      "text/xml",
+  ".mpeg"         =>      "video/mpeg",
+  ".mpg"          =>      "video/mpeg",
+  ".mov"          =>      "video/quicktime",
+  ".qt"           =>      "video/quicktime",
+  ".avi"          =>      "video/x-msvideo",
+  ".asf"          =>      "video/x-ms-asf",
+  ".asx"          =>      "video/x-ms-asf",
+  ".wmv"          =>      "video/x-ms-wmv",
+  ".bz2"          =>      "application/x-bzip",
+  ".tbz"          =>      "application/x-bzip-compressed-tar",
+  ".tar.bz2"      =>      "application/x-bzip-compressed-tar",
+  ""              =>      "text/plain"
+ )
 EOF
        test x"$local" = xtrue && echo 'server.bind = "127.0.0.1"' >> "$conf"
 }
index e1eb9632660146396a0b5f3f2a410d8cb027ff9d..9c2c1b7202462370509a7bdb391982ae32564bd6 100755 (executable)
@@ -113,6 +113,10 @@ case "${1:-.}${2:-.}${3:-.}" in
        src1=`git-unpack-file $2`
        git merge-file "$src1" "$orig" "$src2"
        ret=$?
+       msg=
+       if [ $ret -ne 0 ]; then
+               msg='content conflict'
+       fi
 
        # Create the working tree file, using "our tree" version from the
        # index, and then store the result of the merge.
@@ -120,7 +124,10 @@ case "${1:-.}${2:-.}${3:-.}" in
        rm -f -- "$orig" "$src1" "$src2"
 
        if [ "$6" != "$7" ]; then
-               echo "ERROR: Permissions conflict: $5->$6,$7."
+               if [ -n "$msg" ]; then
+                       msg="$msg, "
+               fi
+               msg="${msg}permissions conflict: $5->$6,$7"
                ret=1
        fi
        if [ "$1" = '' ]; then
@@ -128,7 +135,7 @@ case "${1:-.}${2:-.}${3:-.}" in
        fi
 
        if [ $ret -ne 0 ]; then
-               echo "ERROR: Merge conflict in $4"
+               echo "ERROR: $msg in $4"
                exit 1
        fi
        exec git update-index -- "$4"
index 93bcfc2f5dce418d00f26257788932d5c738785c..c9da747fcfe504b1fd233c68d91e549def0f3571 100755 (executable)
@@ -37,10 +37,10 @@ then
        exit 2
 fi
 
-git update-index --refresh 2>/dev/null
+git update-index -q --refresh
 git read-tree -u -m --aggressive $bases $head $remotes || exit 2
 echo "Trying simple merge."
-if result_tree=$(git write-tree  2>/dev/null)
+if result_tree=$(git write-tree 2>/dev/null)
 then
        exit 0
 else
diff --git a/git-mergetool--lib.sh b/git-mergetool--lib.sh
new file mode 100644 (file)
index 0000000..a16a279
--- /dev/null
@@ -0,0 +1,385 @@
+# git-mergetool--lib is a library for common merge tool functions
+diff_mode() {
+       test "$TOOL_MODE" = diff
+}
+
+merge_mode() {
+       test "$TOOL_MODE" = merge
+}
+
+translate_merge_tool_path () {
+       case "$1" in
+       vimdiff)
+               echo vim
+               ;;
+       gvimdiff)
+               echo gvim
+               ;;
+       emerge)
+               echo emacs
+               ;;
+       *)
+               echo "$1"
+               ;;
+       esac
+}
+
+check_unchanged () {
+       if test "$MERGED" -nt "$BACKUP"; then
+               status=0
+       else
+               while true; do
+                       echo "$MERGED seems unchanged."
+                       printf "Was the merge successful? [y/n] "
+                       read answer < /dev/tty
+                       case "$answer" in
+                       y*|Y*) status=0; break ;;
+                       n*|N*) status=1; break ;;
+                       esac
+               done
+       fi
+}
+
+valid_tool () {
+       case "$1" in
+       kdiff3 | tkdiff | xxdiff | meld | opendiff | \
+       emerge | vimdiff | gvimdiff | ecmerge | diffuse)
+               ;; # happy
+       tortoisemerge)
+               if ! merge_mode; then
+                       return 1
+               fi
+               ;;
+       kompare)
+               if ! diff_mode; then
+                       return 1
+               fi
+               ;;
+       *)
+               if test -z "$(get_merge_tool_cmd "$1")"; then
+                       return 1
+               fi
+               ;;
+       esac
+}
+
+get_merge_tool_cmd () {
+       # Prints the custom command for a merge tool
+       if test -n "$1"; then
+               merge_tool="$1"
+       else
+               merge_tool="$(get_merge_tool)"
+       fi
+       if diff_mode; then
+               echo "$(git config difftool.$merge_tool.cmd ||
+                       git config mergetool.$merge_tool.cmd)"
+       else
+               echo "$(git config mergetool.$merge_tool.cmd)"
+       fi
+}
+
+run_merge_tool () {
+       merge_tool_path="$(get_merge_tool_path "$1")" || exit
+       base_present="$2"
+       status=0
+
+       case "$1" in
+       kdiff3)
+               if merge_mode; then
+                       if $base_present; then
+                               ("$merge_tool_path" --auto \
+                                       --L1 "$MERGED (Base)" \
+                                       --L2 "$MERGED (Local)" \
+                                       --L3 "$MERGED (Remote)" \
+                                       -o "$MERGED" \
+                                       "$BASE" "$LOCAL" "$REMOTE" \
+                               > /dev/null 2>&1)
+                       else
+                               ("$merge_tool_path" --auto \
+                                       --L1 "$MERGED (Local)" \
+                                       --L2 "$MERGED (Remote)" \
+                                       -o "$MERGED" \
+                                       "$LOCAL" "$REMOTE" \
+                               > /dev/null 2>&1)
+                       fi
+                       status=$?
+               else
+                       ("$merge_tool_path" --auto \
+                               --L1 "$MERGED (A)" \
+                               --L2 "$MERGED (B)" "$LOCAL" "$REMOTE" \
+                       > /dev/null 2>&1)
+               fi
+               ;;
+       kompare)
+               "$merge_tool_path" "$LOCAL" "$REMOTE"
+               ;;
+       tkdiff)
+               if merge_mode; then
+                       if $base_present; then
+                               "$merge_tool_path" -a "$BASE" \
+                                       -o "$MERGED" "$LOCAL" "$REMOTE"
+                       else
+                               "$merge_tool_path" \
+                                       -o "$MERGED" "$LOCAL" "$REMOTE"
+                       fi
+                       status=$?
+               else
+                       "$merge_tool_path" "$LOCAL" "$REMOTE"
+               fi
+               ;;
+       meld)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
+                       check_unchanged
+               else
+                       "$merge_tool_path" "$LOCAL" "$REMOTE"
+               fi
+               ;;
+       diffuse)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       if $base_present; then
+                               "$merge_tool_path" \
+                                       "$LOCAL" "$MERGED" "$REMOTE" \
+                                       "$BASE" | cat
+                       else
+                               "$merge_tool_path" \
+                                       "$LOCAL" "$MERGED" "$REMOTE" | cat
+                       fi
+                       check_unchanged
+               else
+                       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+               fi
+               ;;
+       vimdiff)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       "$merge_tool_path" -d -c "wincmd l" \
+                               "$LOCAL" "$MERGED" "$REMOTE"
+                       check_unchanged
+               else
+                       "$merge_tool_path" -d -c "wincmd l" \
+                               "$LOCAL" "$REMOTE"
+               fi
+               ;;
+       gvimdiff)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       "$merge_tool_path" -d -c "wincmd l" -f \
+                               "$LOCAL" "$MERGED" "$REMOTE"
+                       check_unchanged
+               else
+                       "$merge_tool_path" -d -c "wincmd l" -f \
+                               "$LOCAL" "$REMOTE"
+               fi
+               ;;
+       xxdiff)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       if $base_present; then
+                               "$merge_tool_path" -X --show-merged-pane \
+                                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+                                       -R 'Accel.Search: "Ctrl+F"' \
+                                       -R 'Accel.SearchForward: "Ctrl-G"' \
+                                       --merged-file "$MERGED" \
+                                       "$LOCAL" "$BASE" "$REMOTE"
+                       else
+                               "$merge_tool_path" -X $extra \
+                                       -R 'Accel.SaveAsMerged: "Ctrl-S"' \
+                                       -R 'Accel.Search: "Ctrl+F"' \
+                                       -R 'Accel.SearchForward: "Ctrl-G"' \
+                                       --merged-file "$MERGED" \
+                                       "$LOCAL" "$REMOTE"
+                       fi
+                       check_unchanged
+               else
+                       "$merge_tool_path" \
+                               -R 'Accel.Search: "Ctrl+F"' \
+                               -R 'Accel.SearchForward: "Ctrl-G"' \
+                               "$LOCAL" "$REMOTE"
+               fi
+               ;;
+       opendiff)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       if $base_present; then
+                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                                       -ancestor "$BASE" \
+                                       -merge "$MERGED" | cat
+                       else
+                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                                       -merge "$MERGED" | cat
+                       fi
+                       check_unchanged
+               else
+                       "$merge_tool_path" "$LOCAL" "$REMOTE" | cat
+               fi
+               ;;
+       ecmerge)
+               if merge_mode; then
+                       touch "$BACKUP"
+                       if $base_present; then
+                               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" \
+                                       --default --mode=merge3 --to="$MERGED"
+                       else
+                               "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                                       --default --mode=merge2 --to="$MERGED"
+                       fi
+                       check_unchanged
+               else
+                       "$merge_tool_path" "$LOCAL" "$REMOTE" \
+                               --default --mode=merge2 --to="$MERGED"
+               fi
+               ;;
+       emerge)
+               if merge_mode; then
+                       if $base_present; then
+                               "$merge_tool_path" \
+                                       -f emerge-files-with-ancestor-command \
+                                       "$LOCAL" "$REMOTE" "$BASE" \
+                                       "$(basename "$MERGED")"
+                       else
+                               "$merge_tool_path" \
+                                       -f emerge-files-command \
+                                       "$LOCAL" "$REMOTE" \
+                                       "$(basename "$MERGED")"
+                       fi
+                       status=$?
+               else
+                       "$merge_tool_path" -f emerge-files-command \
+                               "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
+               fi
+               ;;
+       tortoisemerge)
+               if $base_present; then
+                       touch "$BACKUP"
+                       "$merge_tool_path" \
+                               -base:"$BASE" -mine:"$LOCAL" \
+                               -theirs:"$REMOTE" -merged:"$MERGED"
+                       check_unchanged
+               else
+                       echo "TortoiseMerge cannot be used without a base" 1>&2
+                       status=1
+               fi
+               ;;
+       *)
+               merge_tool_cmd="$(get_merge_tool_cmd "$1")"
+               if test -z "$merge_tool_cmd"; then
+                       if merge_mode; then
+                               status=1
+                       fi
+                       break
+               fi
+               if merge_mode; then
+                       trust_exit_code="$(git config --bool \
+                               mergetool."$1".trustExitCode || echo false)"
+                       if test "$trust_exit_code" = "false"; then
+                               touch "$BACKUP"
+                               ( eval $merge_tool_cmd )
+                               check_unchanged
+                       else
+                               ( eval $merge_tool_cmd )
+                               status=$?
+                       fi
+               else
+                       ( eval $merge_tool_cmd )
+               fi
+               ;;
+       esac
+       return $status
+}
+
+guess_merge_tool () {
+       if merge_mode; then
+               tools="tortoisemerge"
+       else
+               tools="kompare"
+       fi
+       if test -n "$DISPLAY"; then
+               if test -n "$GNOME_DESKTOP_SESSION_ID" ; then
+                       tools="meld opendiff kdiff3 tkdiff xxdiff $tools"
+               else
+                       tools="opendiff kdiff3 tkdiff xxdiff meld $tools"
+               fi
+               tools="$tools gvimdiff diffuse ecmerge"
+       fi
+       if echo "${VISUAL:-$EDITOR}" | grep emacs > /dev/null 2>&1; then
+               # $EDITOR is emacs so add emerge as a candidate
+               tools="$tools emerge vimdiff"
+       elif echo "${VISUAL:-$EDITOR}" | grep vim > /dev/null 2>&1; then
+               # $EDITOR is vim so add vimdiff as a candidate
+               tools="$tools vimdiff emerge"
+       else
+               tools="$tools emerge vimdiff"
+       fi
+       echo >&2 "merge tool candidates: $tools"
+
+       # Loop over each candidate and stop when a valid merge tool is found.
+       for i in $tools
+       do
+               merge_tool_path="$(translate_merge_tool_path "$i")"
+               if type "$merge_tool_path" > /dev/null 2>&1; then
+                       echo "$i"
+                       return 0
+               fi
+       done
+
+       echo >&2 "No known merge resolution program available."
+       return 1
+}
+
+get_configured_merge_tool () {
+       # Diff mode first tries diff.tool and falls back to merge.tool.
+       # Merge mode only checks merge.tool
+       if diff_mode; then
+               merge_tool=$(git config diff.tool || git config merge.tool)
+       else
+               merge_tool=$(git config merge.tool)
+       fi
+       if test -n "$merge_tool" && ! valid_tool "$merge_tool"; then
+               echo >&2 "git config option $TOOL_MODE.tool set to unknown tool: $merge_tool"
+               echo >&2 "Resetting to default..."
+               return 1
+       fi
+       echo "$merge_tool"
+}
+
+get_merge_tool_path () {
+       # A merge tool has been set, so verify that it's valid.
+       if test -n "$1"; then
+               merge_tool="$1"
+       else
+               merge_tool="$(get_merge_tool)"
+       fi
+       if ! valid_tool "$merge_tool"; then
+               echo >&2 "Unknown merge tool $merge_tool"
+               exit 1
+       fi
+       if diff_mode; then
+               merge_tool_path=$(git config difftool."$merge_tool".path ||
+                                 git config mergetool."$merge_tool".path)
+       else
+               merge_tool_path=$(git config mergetool."$merge_tool".path)
+       fi
+       if test -z "$merge_tool_path"; then
+               merge_tool_path="$(translate_merge_tool_path "$merge_tool")"
+       fi
+       if test -z "$(get_merge_tool_cmd "$merge_tool")" &&
+       ! type "$merge_tool_path" > /dev/null 2>&1; then
+               echo >&2 "The $TOOL_MODE tool $merge_tool is not available as"\
+                        "'$merge_tool_path'"
+               exit 1
+       fi
+       echo "$merge_tool_path"
+}
+
+get_merge_tool () {
+       # Check if a merge tool has been configured
+       merge_tool=$(get_configured_merge_tool)
+       # Try to guess an appropriate merge tool if no tool has been set.
+       if test -z "$merge_tool"; then
+               merge_tool="$(guess_merge_tool)" || exit
+       fi
+       echo "$merge_tool"
+}
index d4078a6affd9b4c1fa52e6dba0fe6c151fa452dc..b52a7410bcb7b37dce0f4d6213dddedd2c1e42e7 100755 (executable)
@@ -8,12 +8,13 @@
 # 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=
+TOOL_MODE=merge
 . git-sh-setup
+. git-mergetool--lib
 require_work_tree
-prefix=$(git rev-parse --show-prefix)
 
 # Returns true if the mode reflects a symlink
 is_symlink () {
@@ -70,16 +71,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,47 +98,39 @@ 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
 }
 
-check_unchanged () {
-    if test "$MERGED" -nt "$BACKUP" ; then
-       status=0;
-    else
-       while true; do
-           echo "$MERGED seems unchanged."
-           printf "Was the merge successful? [y/n] "
-           read answer < /dev/tty
-           case "$answer" in
-               y*|Y*) status=0; break ;;
-               n*|N*) status=1; break ;;
-           esac
-       done
+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"
 
-    f=`git ls-files -u -- "$MERGED"`
+    f=$(git ls-files -u -- "$MERGED")
     if test -z "$f" ; then
        if test ! -f "$MERGED" ; then
            echo "$MERGED: file not found"
        else
            echo "$MERGED: file does not need merging"
        fi
-       exit 1
+       return 1
     fi
 
     ext="$$$(expr "$MERGED" : '.*\(\.[^/]*\)$')"
@@ -149,13 +142,13 @@ merge_file () {
     mv -- "$MERGED" "$BACKUP"
     cp -- "$BACKUP" "$MERGED"
 
-    base_mode=`git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}'`
-    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_mode=$(git ls-files -u -- "$MERGED" | awk '{if ($3==1) print $1;}')
+    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,98 +169,26 @@ 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
-
-    case "$merge_tool" in
-       kdiff3)
-           if base_present ; then
-               ("$merge_tool_path" --auto --L1 "$MERGED (Base)" --L2 "$MERGED (Local)" --L3 "$MERGED (Remote)" \
-                   -o "$MERGED" "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
-           else
-               ("$merge_tool_path" --auto --L1 "$MERGED (Local)" --L2 "$MERGED (Remote)" \
-                   -o "$MERGED" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
-           fi
-           status=$?
-           ;;
-       tkdiff)
-           if base_present ; then
-               "$merge_tool_path" -a "$BASE" -o "$MERGED" "$LOCAL" "$REMOTE"
-           else
-               "$merge_tool_path" -o "$MERGED" "$LOCAL" "$REMOTE"
-           fi
-           status=$?
-           ;;
-       meld|vimdiff)
-           touch "$BACKUP"
-           "$merge_tool_path" "$LOCAL" "$MERGED" "$REMOTE"
-           check_unchanged
-           ;;
-       gvimdiff)
-           touch "$BACKUP"
-           "$merge_tool_path" -f "$LOCAL" "$MERGED" "$REMOTE"
-           check_unchanged
-           ;;
-       xxdiff)
-           touch "$BACKUP"
-           if base_present ; then
-               "$merge_tool_path" -X --show-merged-pane \
-                   -R 'Accel.SaveAsMerged: "Ctrl-S"' \
-                   -R 'Accel.Search: "Ctrl+F"' \
-                   -R 'Accel.SearchForward: "Ctrl-G"' \
-                   --merged-file "$MERGED" "$LOCAL" "$BASE" "$REMOTE"
-           else
-               "$merge_tool_path" -X --show-merged-pane \
-                   -R 'Accel.SaveAsMerged: "Ctrl-S"' \
-                   -R 'Accel.Search: "Ctrl+F"' \
-                   -R 'Accel.SearchForward: "Ctrl-G"' \
-                   --merged-file "$MERGED" "$LOCAL" "$REMOTE"
-           fi
-           check_unchanged
-           ;;
-       opendiff)
-           touch "$BACKUP"
-           if base_present; then
-               "$merge_tool_path" "$LOCAL" "$REMOTE" -ancestor "$BASE" -merge "$MERGED" | cat
-           else
-               "$merge_tool_path" "$LOCAL" "$REMOTE" -merge "$MERGED" | cat
-           fi
-           check_unchanged
-           ;;
-       ecmerge)
-           touch "$BACKUP"
-           if base_present; then
-               "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" --default --mode=merge3 --to="$MERGED"
-           else
-               "$merge_tool_path" "$LOCAL" "$REMOTE" --default --mode=merge2 --to="$MERGED"
-           fi
-           check_unchanged
-           ;;
-       emerge)
-           if base_present ; then
-               "$merge_tool_path" -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$MERGED")"
-           else
-               "$merge_tool_path" -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$MERGED")"
-           fi
-           status=$?
-           ;;
-       *)
-           if test -n "$merge_tool_cmd"; then
-               if test "$merge_tool_trust_exit_code" = "false"; then
-                   touch "$BACKUP"
-                   ( eval $merge_tool_cmd )
-                   check_unchanged
-               else
-                   ( eval $merge_tool_cmd )
-                   status=$?
-               fi
-           fi
-           ;;
-    esac
-    if test "$status" -ne 0; then
+    if "$prompt" = true; then
+       printf "Hit return to start merge resolution tool (%s): " "$merge_tool"
+       read ans
+    fi
+
+    if base_present; then
+           present=true
+    else
+           present=false
+    fi
+
+    if ! run_merge_tool "$merge_tool" "$present"; 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,15 +199,18 @@ 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
        -t|--tool*)
            case "$#,$1" in
                *,*=*)
-                   merge_tool=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+                   merge_tool=$(expr "z$1" : 'z-[^=]*=\(.*\)')
                    ;;
                1,*)
                    usage ;;
@@ -295,6 +219,12 @@ do
                    shift ;;
            esac
            ;;
+       -y|--no-prompt)
+           prompt=false
+           ;;
+       --prompt)
+           prompt=true
+           ;;
        --)
            shift
            break
@@ -309,118 +239,67 @@ do
     shift
 done
 
-valid_custom_tool()
-{
-    merge_tool_cmd="$(git config mergetool.$1.cmd)"
-    test -n "$merge_tool_cmd"
-}
+prompt_after_failed_merge() {
+    while true; do
+       printf "Continue merging other unresolved paths (y/n) ? "
+       read ans
+       case "$ans" in
 
-valid_tool() {
-       case "$1" in
-               kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | ecmerge)
-                       ;; # happy
-               *)
-                       if ! valid_custom_tool "$1"; then
-                               return 1
-                       fi
-                       ;;
-       esac
-}
+           [yY]*)
+               return 0
+               ;;
 
-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
+           [nN]*)
+               return 1
+               ;;
+       esac
+    done
 }
 
-
 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
-
-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"
-        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"
-    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
-        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
-    if ! valid_tool "$merge_tool"; then
-        echo >&2 "Unknown merge_tool $merge_tool"
-        exit 1
-    fi
-
-    init_merge_tool_path "$merge_tool"
-
-    merge_keep_backup="$(git config --bool merge.keepBackup || echo true)"
-
-    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
-
-    if ! test -z "$merge_tool_cmd"; then
-        merge_tool_trust_exit_code="$(git config --bool mergetool.$merge_tool.trustExitCode || echo false)"
-    fi
+    merge_tool=$(get_merge_tool "$merge_tool") || exit
 fi
+merge_keep_backup="$(git config --bool mergetool.keepBackup || echo true)"
+merge_keep_temporaries="$(git config --bool mergetool.keepTemporaries || echo false)"
 
+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 695a4094bb4230341618bd6f16d0bea9bff2e826..a296719861ec3b7aab5e530e67dc99b58267a56a 100755 (executable)
@@ -2,7 +2,7 @@
 
 # git-ls-remote could be called from outside a git managed repository;
 # this would fail in that case and would issue an error message.
-GIT_DIR=$(git rev-parse --git-dir 2>/dev/null) || :;
+GIT_DIR=$(git rev-parse -q --git-dir) || :;
 
 get_data_source () {
        case "$1" in
index 2c7f432dc04254dcb9906f4d078eb16d195848ca..35261539ab80ffa46fef945dce1a82c5636c1b49 100755 (executable)
@@ -16,7 +16,7 @@ cd_to_toplevel
 test -z "$(git ls-files -u)" ||
        die "You are in the middle of a conflicted merge."
 
-strategy_args= no_stat= no_commit= squash= no_ff= log_arg= verbosity=
+strategy_args= diffstat= no_commit= squash= no_ff= log_arg= verbosity=
 curr_branch=$(git symbolic-ref -q HEAD)
 curr_branch_short=$(echo "$curr_branch" | sed "s|refs/heads/||")
 rebase=$(git config --bool branch.$curr_branch_short.rebase)
@@ -28,9 +28,9 @@ do
        -v|--verbose)
                verbosity="$verbosity -v" ;;
        -n|--no-stat|--no-summary)
-               no_stat=-n ;;
+               diffstat=--no-stat ;;
        --stat|--summary)
-               no_stat=$1 ;;
+               diffstat=--stat ;;
        --log|--no-log)
                log_arg=$1 ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
@@ -90,23 +90,31 @@ error_on_no_merge_candidates () {
 
        curr_branch=${curr_branch#refs/heads/}
 
-       echo "You asked me to pull without telling me which branch you"
-       echo "want to merge with, and 'branch.${curr_branch}.merge' in"
-       echo "your configuration file does not tell me either.  Please"
-       echo "name which branch you want to merge on the command line and"
-       echo "try again (e.g. 'git pull <repository> <refspec>')."
-       echo "See git-pull(1) for details on the refspec."
-       echo
-       echo "If you often merge with the same branch, you may want to"
-       echo "configure the following variables in your configuration"
-       echo "file:"
-       echo
-       echo "    branch.${curr_branch}.remote = <nickname>"
-       echo "    branch.${curr_branch}.merge = <remote-ref>"
-       echo "    remote.<nickname>.url = <url>"
-       echo "    remote.<nickname>.fetch = <refspec>"
-       echo
-       echo "See git-config(1) for details."
+       if [ -z "$curr_branch" ]; then
+               echo "You are not currently on a branch, so I cannot use any"
+               echo "'branch.<branchname>.merge' in your configuration file."
+               echo "Please specify which branch you want to merge on the command"
+               echo "line and try again (e.g. 'git pull <repository> <refspec>')."
+               echo "See git-pull(1) for details."
+       else
+               echo "You asked me to pull without telling me which branch you"
+               echo "want to merge with, and 'branch.${curr_branch}.merge' in"
+               echo "your configuration file does not tell me either.  Please"
+               echo "specify which branch you want to merge on the command line and"
+               echo "try again (e.g. 'git pull <repository> <refspec>')."
+               echo "See git-pull(1) for details."
+               echo
+               echo "If you often merge with the same branch, you may want to"
+               echo "configure the following variables in your configuration"
+               echo "file:"
+               echo
+               echo "    branch.${curr_branch}.remote = <nickname>"
+               echo "    branch.${curr_branch}.merge = <remote-ref>"
+               echo "    remote.<nickname>.url = <url>"
+               echo "    remote.<nickname>.fetch = <refspec>"
+               echo
+               echo "See git-config(1) for details."
+       fi
        exit 1
 }
 
@@ -139,7 +147,7 @@ then
        echo >&2 "Warning: fetch updated the current branch head."
        echo >&2 "Warning: fast forwarding your working tree from"
        echo >&2 "Warning: commit $orig_head."
-       git update-index --refresh 2>/dev/null
+       git update-index -q --refresh
        git read-tree -u -m "$orig_head" "$curr_head" ||
                die 'Cannot fast-forward your working tree.
 After making sure that you saved anything precious from
@@ -171,6 +179,11 @@ case "$merge_head" in
                echo >&2 "Cannot merge multiple branches into empty head"
                exit 1
        fi
+       if test true = "$rebase"
+       then
+               echo >&2 "Cannot rebase onto multiple branches"
+               exit 1
+       fi
        ;;
 esac
 
@@ -183,7 +196,7 @@ fi
 
 merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
 test true = "$rebase" &&
-       exec git-rebase $strategy_args --onto $merge_head \
+       exec git-rebase $diffstat $strategy_args --onto $merge_head \
        ${oldremoteref:-$merge_head}
-exec git-merge $no_stat $no_commit $squash $no_ff $log_arg $strategy_args \
+exec git-merge $diffstat $no_commit $squash $no_ff $log_arg $strategy_args \
        "$merge_name" HEAD $merge_head $verbosity
index cebaee1cc9dfc28d80173583b144a480be2f9bfd..9a6ba2b9874e12d31adeba354d8d44436ceadaf3 100755 (executable)
@@ -63,7 +63,7 @@ tmp_info="$tmp_dir/info"
 commit=$(git rev-parse HEAD)
 
 mkdir $tmp_dir || exit 2
-while read patch_name level garbage
+while read patch_name level garbage <&3
 do
        case "$patch_name" in ''|'#'*) continue;; esac
        case "$level" in
@@ -134,5 +134,5 @@ do
                commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git commit-tree $tree -p $commit) &&
                git update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
        fi
-done <"$QUILT_PATCHES/series"
+done 3<"$QUILT_PATCHES/series"
 rm -rf $tmp_dir || exit 5
index 1ceb57ae8302dc3f0673779cf60f70df5c26a64d..314cd364b8f4df5e170dd0ffd9e874b3e6c2737c 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
@@ -429,6 +442,30 @@ do_rest () {
        done
 }
 
+# skip picking commits whose parents are unchanged
+skip_unnecessary_picks () {
+       fd=3
+       while read command sha1 rest
+       do
+               # fd=3 means we skip the command
+               case "$fd,$command,$(git rev-parse --verify --quiet $sha1^)" in
+               3,pick,"$ONTO"*|3,p,"$ONTO"*)
+                       # pick a commit whose parent is current $ONTO -> skip
+                       ONTO=$sha1
+                       ;;
+               3,#*|3,,*)
+                       # copy comments
+                       ;;
+               *)
+                       fd=1
+                       ;;
+               esac
+               echo "$command${sha1:+ }$sha1${rest:+ }$rest" >&$fd
+       done <"$TODO" >"$TODO.new" 3>>"$DONE" &&
+       mv -f "$TODO".new "$TODO" ||
+       die "Could not skip unnecessary pick commands"
+}
+
 # check if no other options are set
 is_standalone () {
        test $# -eq 2 -a "$2" = '--' &&
@@ -442,6 +479,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
@@ -546,6 +584,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") ||
@@ -553,27 +594,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?"
@@ -584,7 +636,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
@@ -597,12 +654,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
@@ -612,12 +676,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"
@@ -625,14 +698,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
@@ -646,11 +724,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)" = ""
@@ -659,17 +737,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
@@ -691,6 +770,8 @@ EOF
                has_action "$TODO" ||
                        die_abort "Nothing to do"
 
+               test -d "$REWRITTEN" || skip_unnecessary_picks
+
                git update-ref ORIG_HEAD $HEAD
                output git checkout $ONTO && do_rest
                ;;
index ebd4df3a0e821ddcfd1eabfcaac17f854e172a85..b83fd3f970161d9ca8a4212a0b335f784fc3ff84 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>]'
 LONG_USAGE='git-rebase replaces <branch> with a new branch of the
 same name.  When the --onto option is provided the new branch starts
 out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
@@ -46,7 +46,10 @@ do_merge=
 dotest="$GIT_DIR"/rebase-merge
 prec=4
 verbose=
+diffstat=$(git config --bool rebase.stat)
 git_am_opt=
+rebase_root=
+force_rebase=
 
 continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@ -288,15 +291,37 @@ do
                esac
                do_merge=t
                ;;
+       -n|--no-stat)
+               diffstat=
+               ;;
+       --stat)
+               diffstat=t
+               ;;
        -v|--verbose)
                verbose=t
+               diffstat=t
                ;;
        --whitespace=*)
                git_am_opt="$git_am_opt $1"
+               case "$1" in
+               --whitespace=fix|--whitespace=strip)
+                       force_rebase=t
+                       ;;
+               esac
+               ;;
+       --committer-date-is-author-date|--ignore-date)
+               git_am_opt="$git_am_opt $1"
+               force_rebase=t
                ;;
        -C*)
                git_am_opt="$git_am_opt $1"
                ;;
+       --root)
+               rebase_root=t
+               ;;
+       -f|--f|--fo|--for|--forc|force|--force-r|--force-re|--force-reb|--force-reba|--force-rebas|--force-rebase)
+               force_rebase=t
+               ;;
        -*)
                usage
                ;;
@@ -306,6 +331,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 +370,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 +400,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 +431,8 @@ case "$#" in
 esac
 orig_head=$branch
 
-# Now we are rebasing commits $upstream..$branch on top of $onto
+# Now we are rebasing commits $upstream..$branch (or with --root,
+# everything leading up to $branch) on top of $onto
 
 # Check if we are already based on $onto with linear history,
 # but this should be done only when upstream and onto are the same.
@@ -402,17 +441,15 @@ if test "$upstream" = "$onto" && test "$mb" = "$onto" &&
        # linear history?
        ! (git rev-list --parents "$onto".."$branch" | grep " .* ") > /dev/null
 then
-       # Lazily switch to the target branch if needed...
-       test -z "$switch_to" || git checkout "$switch_to"
-       echo >&2 "Current branch $branch_name is up to date."
-       exit 0
-fi
-
-if test -n "$verbose"
-then
-       echo "Changes from $mb to $onto:"
-       # We want color (if set), but no pager
-       GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
+       if test -z "$force_rebase"
+       then
+               # Lazily switch to the target branch if needed...
+               test -z "$switch_to" || git checkout "$switch_to"
+               echo >&2 "Current branch $branch_name is up to date."
+               exit 0
+       else
+               echo "Current branch $branch_name is up to date, rebase forced."
+       fi
 fi
 
 # Detach HEAD and reset the tree
@@ -420,6 +457,16 @@ echo "First, rewinding head to replay your work on top of it..."
 git checkout -q "$onto^0" || die "could not detach HEAD"
 git update-ref ORIG_HEAD $branch
 
+if test -n "$diffstat"
+then
+       if test -n "$verbose"
+       then
+               echo "Changes from $mb to $onto:"
+       fi
+       # We want color (if set), but no pager
+       GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
+fi
+
 # If the $onto is a proper descendant of the tip of the branch, then
 # we just fast forwarded.
 if test "$mb" = "$branch"
@@ -429,10 +476,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 +509,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 77ca8fe8803f102b877c4d63ed1ffa41920cbd54..cccbf4517aa46d4d217a1ce25105f7c413301d2d 100755 (executable)
@@ -23,7 +23,7 @@ use Getopt::Long;
 use Text::ParseWords;
 use Data::Dumper;
 use Term::ANSIColor;
-use File::Temp qw/ tempdir /;
+use File::Temp qw/ tempdir tempfile /;
 use Error qw(:try);
 use Git;
 
@@ -68,14 +68,15 @@ git send-email [options] <file | directory | rev-list options >
   Automating:
     --identity              <str>  * Use the sendemail.<id> options.
     --cc-cmd                <str>  * Email Cc: via `<str> \$patch_path`
-    --suppress-cc           <str>  * author, self, sob, cccmd, all.
-    --[no-]signed-off-by-cc        * Send to Cc: and Signed-off-by:
-                                     addresses. Default on.
+    --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, all.
+    --[no-]signed-off-by-cc        * Send to Signed-off-by: addresses. Default on.
     --[no-]suppress-from           * Send to self. Default off.
     --[no-]chain-reply-to          * Chain In-Reply-To: fields. Default on.
     --[no-]thread                  * Use In-Reply-To: field. Default on.
 
   Administering:
+    --confirm               <str>  * Confirm recipients before sending;
+                                     auto, cc, compose, always, or never.
     --quiet                        * Output one line of info per email.
     --dry-run                      * Don't actually send the emails.
     --[no-]validate                * Perform patch sanity checks. Default on.
@@ -126,6 +127,7 @@ sub format_2822_time {
 }
 
 my $have_email_valid = eval { require Email::Valid; 1 };
+my $have_mail_address = eval { require Mail::Address; 1 };
 my $smtp;
 my $auth;
 
@@ -156,7 +158,7 @@ if ($@) {
 # Behavior modification variables
 my ($quiet, $dry_run) = (0, 0);
 my $format_patch;
-my $compose_filename = $repo->repo_path() . "/.gitsendemail.msg.$$";
+my $compose_filename;
 
 # Handle interactive edition of files.
 my $multiedit;
@@ -181,7 +183,7 @@ sub do_edit {
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
 my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
 my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts);
-my ($validate);
+my ($validate, $confirm);
 my (@suppress_cc);
 
 my %config_bool_settings = (
@@ -207,6 +209,7 @@ my %config_settings = (
     "suppresscc" => \@suppress_cc,
     "envelopesender" => \$envelope_sender,
     "multiedit" => \$multiedit,
+    "confirm"   => \$confirm,
 );
 
 # Handle Uncouth Termination
@@ -219,11 +222,13 @@ sub signal_handler {
        system "stty echo";
 
        # tmp files from --compose
-       if (-e $compose_filename) {
-               print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
-       }
-       if (-e ($compose_filename . ".final")) {
-               print "'$compose_filename.final' contains the composed email.\n"
+       if (defined $compose_filename) {
+               if (-e $compose_filename) {
+                       print "'$compose_filename' contains an intermediate version of the email you were composing.\n";
+               }
+               if (-e ($compose_filename . ".final")) {
+                       print "'$compose_filename.final' contains the composed email.\n"
+               }
        }
 
        exit;
@@ -256,6 +261,7 @@ my $rc = GetOptions("sender|from=s" => \$sender,
                    "suppress-from!" => \$suppress_from,
                    "suppress-cc=s" => \@suppress_cc,
                    "signed-off-cc|signed-off-by-cc!" => \$signed_off_by_cc,
+                   "confirm=s" => \$confirm,
                    "dry-run" => \$dry_run,
                    "envelope-sender=s" => \$envelope_sender,
                    "thread!" => \$thread,
@@ -267,6 +273,9 @@ unless ($rc) {
     usage();
 }
 
+die "Cannot run git format-patch from outside a repository\n"
+       if $format_patch and not $repo;
+
 # Now, let's fill any that aren't set in with defaults:
 
 sub read_config {
@@ -318,13 +327,13 @@ my(%suppress_cc);
 if (@suppress_cc) {
        foreach my $entry (@suppress_cc) {
                die "Unknown --suppress-cc field: '$entry'\n"
-                       unless $entry =~ /^(all|cccmd|cc|author|self|sob)$/;
+                       unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/;
                $suppress_cc{$entry} = 1;
        }
 }
 
 if ($suppress_cc{'all'}) {
-       foreach my $entry (qw (ccmd cc author self sob)) {
+       foreach my $entry (qw (ccmd cc author self sob body bodycc)) {
                $suppress_cc{$entry} = 1;
        }
        delete $suppress_cc{'all'};
@@ -334,6 +343,21 @@ if ($suppress_cc{'all'}) {
 $suppress_cc{'self'} = $suppress_from if defined $suppress_from;
 $suppress_cc{'sob'} = !$signed_off_by_cc if defined $signed_off_by_cc;
 
+if ($suppress_cc{'body'}) {
+       foreach my $entry (qw (sob bodycc)) {
+               $suppress_cc{$entry} = 1;
+       }
+       delete $suppress_cc{'body'};
+}
+
+# Set confirm's default value
+my $confirm_unconfigured = !defined $confirm;
+if ($confirm_unconfigured) {
+       $confirm = scalar %suppress_cc ? 'compose' : 'auto';
+};
+die "Unknown --confirm setting: '$confirm'\n"
+       unless $confirm =~ /^(?:auto|cc|compose|always|never)/;
+
 # Debugging, print out the suppressions.
 if (0) {
        print "suppressions:\n";
@@ -360,6 +384,14 @@ foreach my $entry (@bcclist) {
        die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
 }
 
+sub parse_address_line {
+       if ($have_mail_address) {
+               return map { $_->format } Mail::Address->parse($_[0]);
+       } else {
+               return split_addrs($_[0]);
+       }
+}
+
 sub split_addrs {
        return quotewords('\s*,\s*', 1, @_);
 }
@@ -386,6 +418,14 @@ my %parse_alias = (
                        $x =~ /^(\S+)$f\t\(?([^\t]+?)\)?(:?$f){0,2}$/ or next;
                        $aliases{$1} = [ split_addrs($2) ];
                }},
+       elm => sub  { my $fh = shift;
+                     while (<$fh>) {
+                         if (/^(\S+)\s+=\s+[^=]+=\s(\S+)/) {
+                             my ($alias, $addr) = ($1, $2);
+                              $aliases{$alias} = [ split_addrs($addr) ];
+                         }
+                     } },
+
        gnus => sub { my $fh = shift; while (<$fh>) {
                if (/\(define-mail-alias\s+"(\S+?)"\s+"(\S+?)"\)/) {
                        $aliases{$1} = [ $2 ];
@@ -404,6 +444,7 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
 
 # returns 1 if the conflict must be solved using it as a format-patch argument
 sub check_file_rev_conflict($) {
+       return unless $repo;
        my $f = shift;
        try {
                $repo->command('rev-parse', '--verify', '--quiet', $f);
@@ -445,6 +486,8 @@ while (defined(my $f = shift @ARGV)) {
 }
 
 if (@rev_list_opts) {
+       die "Cannot run git format-patch from outside a repository\n"
+               unless $repo;
        push @files, $repo->command('format-patch', '-o', tempdir(CLEANUP => 1), @rev_list_opts);
 }
 
@@ -481,6 +524,9 @@ sub get_patch_subject($) {
 if ($compose) {
        # Note that this does not need to be secure, but we will make a small
        # effort to have it be unique
+       $compose_filename = ($repo ?
+               tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
+               tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
        open(C,">",$compose_filename)
                or die "Failed to open for writing $compose_filename: $!";
 
@@ -568,32 +614,43 @@ EOT
        do_edit(@files);
 }
 
+sub ask {
+       my ($prompt, %arg) = @_;
+       my $valid_re = $arg{valid_re};
+       my $default = $arg{default};
+       my $resp;
+       my $i = 0;
+       return defined $default ? $default : undef
+               unless defined $term->IN and defined fileno($term->IN) and
+                      defined $term->OUT and defined fileno($term->OUT);
+       while ($i++ < 10) {
+               $resp = $term->readline($prompt);
+               if (!defined $resp) { # EOF
+                       print "\n";
+                       return defined $default ? $default : undef;
+               }
+               if ($resp eq '' and defined $default) {
+                       return $default;
+               }
+               if (!defined $valid_re or $resp =~ /$valid_re/) {
+                       return $resp;
+               }
+       }
+       return undef;
+}
+
 my $prompting = 0;
 if (!defined $sender) {
        $sender = $repoauthor || $repocommitter || '';
-
-       while (1) {
-               $_ = $term->readline("Who should the emails appear to be from? [$sender] ");
-               last if defined $_;
-               print "\n";
-       }
-
-       $sender = $_ if ($_);
+       $sender = ask("Who should the emails appear to be from? [$sender] ",
+                     default => $sender);
        print "Emails will be sent from: ", $sender, "\n";
        $prompting++;
 }
 
 if (!@to) {
-
-
-       while (1) {
-               $_ = $term->readline("Who should the emails be sent to? ", "");
-               last if defined $_;
-               print "\n";
-       }
-
-       my $to = $_;
-       push @to, split_addrs($to);
+       my $to = ask("Who should the emails be sent to? ");
+       push @to, parse_address_line($to) if defined $to; # sanitized/validated later
        $prompting++;
 }
 
@@ -613,13 +670,8 @@ sub expand_aliases {
 @bcclist = expand_aliases(@bcclist);
 
 if ($thread && !defined $initial_reply_to && $prompting) {
-       while (1) {
-               $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ", $initial_reply_to);
-               last if defined $_;
-               print "\n";
-       }
-
-       $initial_reply_to = $_;
+       $initial_reply_to = ask(
+               "Message-ID to be used as In-Reply-To for the first email? ");
 }
 if (defined $initial_reply_to) {
        $initial_reply_to =~ s/^\s*<?//;
@@ -637,25 +689,13 @@ if (!defined $smtp_server) {
        $smtp_server ||= 'localhost'; # could be 127.0.0.1, too... *shrug*
 }
 
-if ($compose) {
-       while (1) {
-               $_ = $term->readline("Send this email? (y|n) ");
-               last if defined $_;
-               print "\n";
-       }
-
-       if (uc substr($_,0,1) ne 'Y') {
-               cleanup_compose_files();
-               exit(0);
-       }
-
-       if ($compose > 0) {
-               @files = ($compose_filename . ".final", @files);
-       }
+if ($compose && $compose > 0) {
+       @files = ($compose_filename . ".final", @files);
 }
 
 # Variables we set as part of the loop over files
-our ($message_id, %mail, $subject, $reply_to, $references, $message);
+our ($message_id, %mail, $subject, $reply_to, $references, $message,
+       $needs_confirm, $message_num, $ask_default);
 
 sub extract_valid_address {
        my $address = shift;
@@ -744,12 +784,13 @@ sub sanitize_address
        }
 
        # if recipient_name is already quoted, do nothing
-       if ($recipient_name =~ /^(".*"|=\?utf-8\?q\?.*\?=)$/) {
+       if ($recipient_name =~ /^("[[:ascii:]]*"|=\?utf-8\?q\?.*\?=)$/) {
                return $recipient;
        }
 
        # rfc2047 is needed if a non-ascii char is included
        if ($recipient_name =~ /[^[:ascii:]]/) {
+               $recipient_name =~ s/^"(.*)"$/$1/;
                $recipient_name = quote_rfc2047($recipient_name);
        }
 
@@ -795,7 +836,7 @@ Date: $date
 Message-Id: $message_id
 X-Mailer: git-send-email $gitversion
 ";
-       if ($thread && $reply_to) {
+       if ($reply_to) {
 
                $header .= "In-Reply-To: $reply_to\n";
                $header .= "References: $references\n";
@@ -811,6 +852,35 @@ X-Mailer: git-send-email $gitversion
        unshift (@sendmail_parameters,
                        '-f', $raw_from) if(defined $envelope_sender);
 
+       if ($needs_confirm && !$dry_run) {
+               print "\n$header\n";
+               if ($needs_confirm eq "inform") {
+                       $confirm_unconfigured = 0; # squelch this message for the rest of this run
+                       $ask_default = "y"; # assume yes on EOF since user hasn't explicitly asked for confirmation
+                       print "    The Cc list above has been expanded by additional\n";
+                       print "    addresses found in the patch commit message. By default\n";
+                       print "    send-email prompts before sending whenever this occurs.\n";
+                       print "    This behavior is controlled by the sendemail.confirm\n";
+                       print "    configuration setting.\n";
+                       print "\n";
+                       print "    For additional information, run 'git send-email --help'.\n";
+                       print "    To retain the current behavior, but squelch this message,\n";
+                       print "    run 'git config --global sendemail.confirm auto'.\n\n";
+               }
+               $_ = ask("Send this email? ([y]es|[n]o|[q]uit|[a]ll): ",
+                        valid_re => qr/^(?:yes|y|no|n|quit|q|all|a)/i,
+                        default => $ask_default);
+               die "Send this email reply required" unless defined $_;
+               if (/^n/i) {
+                       return;
+               } elsif (/^q/i) {
+                       cleanup_compose_files();
+                       exit(0);
+               } elsif (/^a/i) {
+                       $confirm = 'never';
+               }
+       }
+
        if ($dry_run) {
                # We don't want to send the email.
        } elsif ($smtp_server =~ m#^/#) {
@@ -909,6 +979,7 @@ X-Mailer: git-send-email $gitversion
 $reply_to = $initial_reply_to;
 $references = $initial_reply_to || '';
 $subject = $initial_subject;
+$message_num = 0;
 
 foreach my $t (@files) {
        open(F,"<",$t) or die "can't open file $t";
@@ -917,89 +988,104 @@ foreach my $t (@files) {
        my $author_encoding;
        my $has_content_type;
        my $body_encoding;
-       @cc = @initial_cc;
+       @cc = ();
        @xh = ();
        my $input_format = undef;
-       my $header_done = 0;
+       my @header = ();
        $message = "";
+       $message_num++;
+       # First unfold multiline header fields
        while(<F>) {
-               if (!$header_done) {
-                       if (/^From /) {
-                               $input_format = 'mbox';
-                               next;
+               last if /^\s*$/;
+               if (/^\s+\S/ and @header) {
+                       chomp($header[$#header]);
+                       s/^\s+/ /;
+                       $header[$#header] .= $_;
+           } else {
+                       push(@header, $_);
+               }
+       }
+       # Now parse the header
+       foreach(@header) {
+               if (/^From /) {
+                       $input_format = 'mbox';
+                       next;
+               }
+               chomp;
+               if (!defined $input_format && /^[-A-Za-z]+:\s/) {
+                       $input_format = 'mbox';
+               }
+
+               if (defined $input_format && $input_format eq 'mbox') {
+                       if (/^Subject:\s+(.*)$/) {
+                               $subject = $1;
                        }
-                       chomp;
-                       if (!defined $input_format && /^[-A-Za-z]+:\s/) {
-                               $input_format = 'mbox';
+                       elsif (/^From:\s+(.*)$/) {
+                               ($author, $author_encoding) = unquote_rfc2047($1);
+                               next if $suppress_cc{'author'};
+                               next if $suppress_cc{'self'} and $author eq $sender;
+                               printf("(mbox) Adding cc: %s from line '%s'\n",
+                                       $1, $_) unless $quiet;
+                               push @cc, $1;
                        }
-
-                       if (defined $input_format && $input_format eq 'mbox') {
-                               if (/^Subject:\s+(.*)$/) {
-                                       $subject = $1;
-
-                               } elsif (/^(Cc|From):\s+(.*)$/) {
-                                       if (unquote_rfc2047($2) eq $sender) {
+                       elsif (/^Cc:\s+(.*)$/) {
+                               foreach my $addr (parse_address_line($1)) {
+                                       if (unquote_rfc2047($addr) eq $sender) {
                                                next if ($suppress_cc{'self'});
-                                       }
-                                       elsif ($1 eq 'From') {
-                                               ($author, $author_encoding)
-                                                 = unquote_rfc2047($2);
-                                               next if ($suppress_cc{'author'});
                                        } else {
                                                next if ($suppress_cc{'cc'});
                                        }
                                        printf("(mbox) Adding cc: %s from line '%s'\n",
-                                               $2, $_) unless $quiet;
-                                       push @cc, $2;
-                               }
-                               elsif (/^Content-type:/i) {
-                                       $has_content_type = 1;
-                                       if (/charset="?([^ "]+)/) {
-                                               $body_encoding = $1;
-                                       }
-                                       push @xh, $_;
-                               }
-                               elsif (/^Message-Id: (.*)/i) {
-                                       $message_id = $1;
-                               }
-                               elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
-                                       push @xh, $_;
+                                               $addr, $_) unless $quiet;
+                                       push @cc, $addr;
                                }
-
-                       } else {
-                               # In the traditional
-                               # "send lots of email" format,
-                               # line 1 = cc
-                               # line 2 = subject
-                               # So let's support that, too.
-                               $input_format = 'lots';
-                               if (@cc == 0 && !$suppress_cc{'cc'}) {
-                                       printf("(non-mbox) Adding cc: %s from line '%s'\n",
-                                               $_, $_) unless $quiet;
-
-                                       push @cc, $_;
-
-                               } elsif (!defined $subject) {
-                                       $subject = $_;
+                       }
+                       elsif (/^Content-type:/i) {
+                               $has_content_type = 1;
+                               if (/charset="?([^ "]+)/) {
+                                       $body_encoding = $1;
                                }
+                               push @xh, $_;
                        }
-
-                       # A whitespace line will terminate the headers
-                       if (m/^\s*$/) {
-                               $header_done = 1;
+                       elsif (/^Message-Id: (.*)/i) {
+                               $message_id = $1;
+                       }
+                       elsif (!/^Date:\s/ && /^[-A-Za-z]+:\s+\S/) {
+                               push @xh, $_;
                        }
+
                } else {
-                       $message .=  $_;
-                       if (/^(Signed-off-by|Cc): (.*)$/i) {
-                               next if ($suppress_cc{'sob'});
-                               chomp;
-                               my $c = $2;
-                               chomp $c;
-                               next if ($c eq $sender and $suppress_cc{'self'});
-                               push @cc, $c;
-                               printf("(sob) Adding cc: %s from line '%s'\n",
-                                       $c, $_) unless $quiet;
+                       # In the traditional
+                       # "send lots of email" format,
+                       # line 1 = cc
+                       # line 2 = subject
+                       # So let's support that, too.
+                       $input_format = 'lots';
+                       if (@cc == 0 && !$suppress_cc{'cc'}) {
+                               printf("(non-mbox) Adding cc: %s from line '%s'\n",
+                                       $_, $_) unless $quiet;
+                               push @cc, $_;
+                       } elsif (!defined $subject) {
+                               $subject = $_;
+                       }
+               }
+       }
+       # Now parse the message body
+       while(<F>) {
+               $message .=  $_;
+               if (/^(Signed-off-by|Cc): (.*)$/i) {
+                       chomp;
+                       my ($what, $c) = ($1, $2);
+                       chomp $c;
+                       if ($c eq $sender) {
+                               next if ($suppress_cc{'self'});
+                       } else {
+                               next if $suppress_cc{'sob'} and $what =~ /Signed-off-by/i;
+                               next if $suppress_cc{'bodycc'} and $what =~ /Cc/i;
                        }
+                       push @cc, $c;
+                       printf("(body) Adding cc: %s from line '%s'\n",
+                               $c, $_) unless $quiet;
                }
        }
        close F;
@@ -1020,7 +1106,7 @@ foreach my $t (@files) {
                        or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
        }
 
-       if (defined $author) {
+       if (defined $author and $author ne $sender) {
                $message = "From: $author\n\n$message";
                if (defined $author_encoding) {
                        if ($has_content_type) {
@@ -1040,6 +1126,14 @@ foreach my $t (@files) {
                }
        }
 
+       $needs_confirm = (
+               $confirm eq "always" or
+               ($confirm =~ /^(?:auto|cc)$/ && @cc) or
+               ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
+       $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
+
+       @cc = (@initial_cc, @cc);
+
        send_message();
 
        # set up for the next message
@@ -1054,13 +1148,10 @@ foreach my $t (@files) {
        $message_id = undef;
 }
 
-if ($compose) {
-       cleanup_compose_files();
-}
+cleanup_compose_files();
 
 sub cleanup_compose_files() {
-       unlink($compose_filename, $compose_filename . ".final");
-
+       unlink($compose_filename, $compose_filename . ".final") if $compose;
 }
 
 $smtp->quit if $smtp;
index 2142308bcc6d2e2c4962859d18e12070cd4c1b1d..838233926f7ed07f781f2eb2e7547a2a5a086071 100755 (executable)
@@ -85,27 +85,14 @@ cd_to_toplevel () {
        cdup=$(git rev-parse --show-cdup)
        if test ! -z "$cdup"
        then
-               case "$cdup" in
-               /*)
-                       # Not quite the same as if we did "cd -P '$cdup'" when
-                       # $cdup contains ".." after symlink path components.
-                       # Don't fix that case at least until Git switches to
-                       # "cd -P" across the board.
-                       phys="$cdup"
-                       ;;
-               ..|../*|*/..|*/../*)
-                       # Interpret $cdup relative to the physical, not logical, cwd.
-                       # Probably /bin/pwd is more portable than passing -P to cd or pwd.
-                       phys="$(unset PWD; /bin/pwd)/$cdup"
-                       ;;
-               *)
-                       # There's no "..", so no need to make things absolute.
-                       phys="$cdup"
-                       ;;
-               esac
-
-               cd "$phys" || {
-                       echo >&2 "Cannot chdir to $phys, the toplevel of the working tree"
+               # The "-P" option says to follow "physical" directory
+               # structure instead of following symbolic links.  When cdup is
+               # "../", this means following the ".." entry in the current
+               # directory instead textually removing a symlink path element
+               # from the PWD shell variable.  The "-P" behavior is more
+               # consistent with the C-style chdir used by most of Git.
+               cd -P "$cdup" || {
+                       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
                        exit 1
                }
        fi
index 2f47e065fe8b7ca856f4527d6a507a28f1b2a06b..8e234a4028d22e11baedba11f871d33f56945716 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 [-b branch] <repo> <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 '
 }
 
 #
@@ -166,9 +167,18 @@ cmd_add()
        ;;
        esac
 
-       # strip trailing slashes from path
-       path=$(echo "$path" | sed -e 's|/*$||')
-
+       # normalize path:
+       # multiple //; leading ./; /./; /../; trailing /
+       path=$(printf '%s/\n' "$path" |
+               sed -e '
+                       s|//*|/|g
+                       s|^\(\./\)*||
+                       s|/\./|/|g
+                       :start
+                       s|\([^/]*\)/\.\./||
+                       tstart
+                       s|/*$||
+               ')
        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
        die "'$path' already exists in the index"
 
@@ -194,8 +204,15 @@ cmd_add()
        else
 
                module_clone "$path" "$realrepo" || exit
-               (unset GIT_DIR; cd "$path" && git checkout -f -q ${branch:+-b "$branch" "origin/$branch"}) ||
-               die "Unable to checkout submodule '$path'"
+               (
+                       unset GIT_DIR
+                       cd "$path" &&
+                       # ash fails to wordsplit ${branch:+-b "$branch"...}
+                       case "$branch" in
+                       '') git checkout -f -q ;;
+                       ?*) git checkout -f -q -b "$branch" "origin/$branch" ;;
+                       esac
+               ) || die "Unable to checkout submodule '$path'"
        fi
 
        git add "$path" ||
@@ -300,6 +317,10 @@ cmd_update()
                        shift
                        cmd_init "$@" || return
                        ;;
+               -N|--no-fetch)
+                       shift
+                       nofetch=1
+                       ;;
                --)
                        shift
                        break
@@ -345,8 +366,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..ef1d30db3889d0d33b43b1d15cd19281291f5aea 100755 (executable)
@@ -47,7 +47,8 @@ BEGIN {
        # import functions from Git into our packages, en masse
        no strict 'refs';
        foreach (qw/command command_oneline command_noisy command_output_pipe
-                   command_input_pipe command_close_pipe/) {
+                   command_input_pipe command_close_pipe
+                   command_bidi_pipe command_close_bidi_pipe/) {
                for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
                        Git::SVN::Migration Git::SVN::Log Git::SVN),
                        __PACKAGE__) {
@@ -63,14 +64,16 @@ $sha1_short = qr/[a-f\d]{4,40}/;
 my ($_stdin, $_help, $_edit,
        $_message, $_file,
        $_template, $_shared,
-       $_version, $_fetch_all, $_no_rebase,
+       $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
        $_merge, $_strategy, $_dry_run, $_local,
        $_prefix, $_no_checkout, $_url, $_verbose,
        $_git_format, $_commit_url, $_tag);
 $Git::SVN::_follow_parent = 1;
+$_q ||= 0;
 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,
@@ -79,11 +82,12 @@ my %fc_opts = ( 'follow-parent|follow!' => \$Git::SVN::_follow_parent,
                'useSvnsyncProps' => \$Git::SVN::_use_svnsync_props,
                'log-window-size=i' => \$Git::SVN::Ra::_log_window_size,
                'no-checkout' => \$_no_checkout,
-               'quiet|q' => \$_q,
+               'quiet|q+' => \$_q,
                'repack-flags|repack-args|repack-opts=s' =>
                   \$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);
@@ -109,6 +113,7 @@ my %cmd = (
        fetch => [ \&cmd_fetch, "Download new revisions from SVN",
                        { 'revision|r=s' => \$_revision,
                          'fetch-all|all' => \$_fetch_all,
+                         'parent|p' => \$_fetch_parent,
                           %fc_opts } ],
        clone => [ \&cmd_clone, "Initialize and fetch revisions",
                        { 'revision|r=s' => \$_revision,
@@ -142,7 +147,7 @@ my %cmd = (
                   'dry-run|n' => \$_dry_run } ],
        'set-tree' => [ \&cmd_set_tree,
                        "Set an SVN repository to a git tree-ish",
-                       { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
+                       { 'stdin' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'create-ignore' => [ \&cmd_create_ignore,
                             'Create a .gitignore per svn:ignore',
                             { 'revision|r=i' => \$_revision
@@ -323,6 +328,7 @@ sub do_git_init_db {
                command_noisy(@init_db);
                $_repository = Git->repository(Repository => ".git");
        }
+       command_noisy('config', 'core.autocrlf', 'false');
        my $set;
        my $pfx = "svn-remote.$Git::SVN::default_repo_id";
        foreach my $i (keys %icv) {
@@ -331,6 +337,9 @@ sub do_git_init_db {
                command_noisy('config', "$pfx.$i", $icv{$i});
                $set = $i;
        }
+       my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
+       command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
+               if defined $$ignore_regex;
 }
 
 sub init_subdir {
@@ -378,12 +387,21 @@ sub cmd_fetch {
        }
        my ($remote) = @_;
        if (@_ > 1) {
-               die "Usage: $0 fetch [--all] [svn-remote]\n";
+               die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
        }
-       $remote ||= $Git::SVN::default_repo_id;
-       if ($_fetch_all) {
+       if ($_fetch_parent) {
+               my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+               unless ($gs) {
+                       die "Unable to determine upstream SVN information from ",
+                           "working tree history\n";
+               }
+               # just fetch, don't checkout.
+               $_no_checkout = 'true';
+               $_fetch_all ? $gs->fetch_all : $gs->fetch;
+       } elsif ($_fetch_all) {
                cmd_multi_fetch();
        } else {
+               $remote ||= $Git::SVN::default_repo_id;
                Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes());
        }
 }
@@ -436,7 +454,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 +696,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 +943,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: " .
@@ -1236,6 +1269,40 @@ sub cmt_metadata {
                command(qw/cat-file commit/, shift)))[-1]);
 }
 
+sub cmt_sha2rev_batch {
+       my %s2r;
+       my ($pid, $in, $out, $ctx) = command_bidi_pipe(qw/cat-file --batch/);
+       my $list = shift;
+
+       foreach my $sha (@{$list}) {
+               my $first = 1;
+               my $size = 0;
+               print $out $sha, "\n";
+
+               while (my $line = <$in>) {
+                       if ($first && $line =~ /^[[:xdigit:]]{40}\smissing$/) {
+                               last;
+                       } elsif ($first &&
+                              $line =~ /^[[:xdigit:]]{40}\scommit\s(\d+)$/) {
+                               $first = 0;
+                               $size = $1;
+                               next;
+                       } elsif ($line =~ /^(git-svn-id: )/) {
+                               my (undef, $rev, undef) =
+                                                     extract_metadata($line);
+                               $s2r{$sha} = $rev;
+                       }
+
+                       $size -= length($line);
+                       last if ($size == 0);
+               }
+       }
+
+       command_close_bidi_pipe($pid, $in, $out, $ctx);
+
+       return \%s2r;
+}
+
 sub working_head_info {
        my ($head, $refs) = @_;
        my @args = ('log', '--no-color', '--first-parent', '--pretty=medium');
@@ -1364,7 +1431,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 +1757,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};
@@ -2313,13 +2381,13 @@ sub do_git_commit {
 
        $self->{last_rev} = $log_entry->{revision};
        $self->{last_commit} = $commit;
-       print "r$log_entry->{revision}";
+       print "r$log_entry->{revision}" unless $::_q > 1;
        if (defined $log_entry->{svm_revision}) {
-                print " (\@$log_entry->{svm_revision})";
+                print " (\@$log_entry->{svm_revision})" unless $::_q > 1;
                 $self->rev_map_set($log_entry->{svm_revision}, $commit,
                                   0, $self->svm_uuid);
        }
-       print " = $commit ($self->{ref_id})\n";
+       print " = $commit ($self->{ref_id})\n" unless $::_q > 1;
        if (--$_gc_nr == 0) {
                $_gc_nr = $_gc_period;
                gc();
@@ -2333,7 +2401,10 @@ sub match_paths {
        if (my $path = $paths->{"/$self->{path}"}) {
                return ($path->{action} eq 'D') ? 0 : 1;
        }
-       $self->{path_regex} ||= qr/^\/\Q$self->{path}\E\//;
+       my $repos_root = $self->ra->{repos_root};
+       my $extended_path = $self->{url} . '/' . $self->{path};
+       $extended_path =~ s#^\Q$repos_root\E(/|$)##;
+       $self->{path_regex} ||= qr/^\/\Q$extended_path\E\//;
        if (grep /$self->{path_regex}/, keys %$paths) {
                return 1;
        }
@@ -2386,22 +2457,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 +2484,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 +2584,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 +3323,20 @@ 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->{ignore_regex} = eval { command_oneline('config', '--get',
+                            "svn-remote.$git_svn->{repo_id}.ignore-paths") };
        $self->{empty} = {};
        $self->{dir_prop} = {};
        $self->{file_prop} = {};
@@ -3210,6 +3346,70 @@ 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 ($self, $path) = @_;
+       return 1 if in_dot_git($path);
+       return 1 if defined($self->{ignore_regex}) &&
+                   $path =~ m!$self->{ignore_regex}!;
+       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,20 +3435,24 @@ sub git_path {
 
 sub delete_entry {
        my ($self, $path, $rev, $pb) = @_;
+       return undef if $self->is_path_ignored($path);
 
        my $gpath = $self->git_path($path);
        return undef if ($gpath eq '');
 
        # remove entire directories.
-       if (command('ls-tree', $self->{c}, '--', $gpath) =~ /^040000 tree/) {
+       my ($tree) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
+                        =~ /\A040000 tree ([a-f\d]{40})\t\Q$gpath\E\0/);
+       if ($tree) {
                my ($ls, $ctx) = command_output_pipe(qw/ls-tree
                                                     -r --name-only -z/,
-                                                    $self->{c}, '--', $gpath);
+                                                    $tree);
                local $/ = "\0";
                while (<$ls>) {
                        chomp;
-                       $self->{gii}->remove($_);
-                       print "\tD\t$_\n" unless $::_q;
+                       my $rmpath = "$gpath/$_";
+                       $self->{gii}->remove($rmpath);
+                       print "\tD\t$rmpath\n" unless $::_q;
                }
                print "\tD\t$gpath/\n" unless $::_q;
                command_close_pipe($ls, $ctx);
@@ -3262,26 +3466,40 @@ sub delete_entry {
 
 sub open_file {
        my ($self, $path, $pb, $rev) = @_;
+       my ($mode, $blob);
+
+       goto out if $self->is_path_ignored($path);
+
        my $gpath = $self->git_path($path);
-       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--', $gpath)
-                            =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
+       ($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
+                            =~ /\A(\d{6}) blob ([a-f\d]{40})\t\Q$gpath\E\0/);
        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 (!$self->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 $self->is_path_ignored($path);
        my $gpath = $self->git_path($path);
        if ($gpath eq '') {
                my ($ls, $ctx) = command_output_pipe(qw/ls-tree
@@ -3299,11 +3517,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 $self->is_path_ignored($db->{path});
        $self->{dir_prop}->{$db->{path}} ||= {};
        $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
        undef;
@@ -3311,6 +3531,7 @@ sub change_dir_prop {
 
 sub absent_directory {
        my ($self, $path, $pb) = @_;
+       return undef if $self->is_path_ignored($path);
        $self->{absent_dir}->{$pb->{path}} ||= [];
        push @{$self->{absent_dir}->{$pb->{path}}}, $path;
        undef;
@@ -3318,6 +3539,7 @@ sub absent_directory {
 
 sub absent_file {
        my ($self, $path, $pb) = @_;
+       return undef if $self->is_path_ignored($path);
        $self->{absent_file}->{$pb->{path}} ||= [];
        push @{$self->{absent_file}->{$pb->{path}}}, $path;
        undef;
@@ -3325,6 +3547,7 @@ sub absent_file {
 
 sub change_file_prop {
        my ($self, $fb, $prop, $value) = @_;
+       return undef if $self->is_path_ignored($fb->{path});
        if ($prop eq 'svn:executable') {
                if ($fb->{mode_b} != 120000) {
                        $fb->{mode_b} = defined $value ? 100755 : 100644;
@@ -3340,22 +3563,43 @@ sub change_file_prop {
 
 sub apply_textdelta {
        my ($self, $fb, $exp) = @_;
+       return undef if $self->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 +3610,8 @@ sub apply_textdelta {
 
 sub close_file {
        my ($self, $fb, $exp) = @_;
+       return undef if $self->is_path_ignored($fb->{path});
+
        my $hash;
        my $path = $self->git_path($fb->{path});
        if (my $fh = $fb->{fh}) {
@@ -3379,11 +3625,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 +4137,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 +4274,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 +4453,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 +4690,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 +4797,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 {
@@ -4767,11 +5044,22 @@ sub cmd_blame {
                                                  '--', $path);
                my ($sha1);
                my %authors;
+               my @buffer;
+               my %dsha; #distinct sha keys
+
                while (my $line = <$fh>) {
+                       push @buffer, $line;
+                       if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
+                               $dsha{$1} = 1;
+                       }
+               }
+
+               my $s2r = ::cmt_sha2rev_batch([keys %dsha]);
+
+               foreach my $line (@buffer) {
                        if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
-                               $sha1 = $1;
-                               (undef, $rev, undef) = ::cmt_metadata($1);
-                               $rev = '0' if (!$rev);
+                               $rev = $s2r->{$1};
+                               $rev = '0' if (!$rev)
                        }
                        elsif ($line =~ /^author (.*)/) {
                                $authors{$rev} = $1;
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 af747613f02af94bb6e3cba6d4e070061e2d8c0f..5a00726d09a13d5f70ab0378e02bbbd299124d5c 100644 (file)
--- a/git.c
+++ b/git.c
@@ -5,7 +5,7 @@
 #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]";
+       "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
 
 const char git_more_info_string[] =
        "See 'git help COMMAND' for more information on a specific command.";
@@ -47,7 +47,7 @@ static void commit_pager_choice(void) {
        }
 }
 
-static int handle_options(const char*** argv, int* argc, int* envchanged)
+static int handle_options(const char ***argv, int *argc, int *envchanged)
 {
        int handled = 0;
 
@@ -75,6 +75,9 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
                                puts(git_exec_path());
                                exit(0);
                        }
+               } else if (!strcmp(cmd, "--html-path")) {
+                       puts(system_path(GIT_HTML_PATH));
+                       exit(0);
                } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
                        use_pager = 1;
                } else if (!strcmp(cmd, "--no-pager")) {
@@ -133,7 +136,7 @@ static int handle_alias(int *argcp, const char ***argv)
        int envchanged = 0, ret = 0, saved_errno = errno;
        const char *subdir;
        int count, option_count;
-       const char** new_argv;
+       const char **new_argv;
        const char *alias_command;
        char *alias_string;
        int unused_nongit;
@@ -159,7 +162,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);
@@ -184,10 +187,10 @@ static int handle_alias(int *argcp, const char ***argv)
                                  "trace: alias expansion: %s =>",
                                  alias_command);
 
-               new_argv = xrealloc(new_argv, sizeof(char*) *
+               new_argv = xrealloc(new_argv, sizeof(char *) *
                                    (count + *argcp + 1));
                /* insert after command name */
-               memcpy(new_argv + count, *argv + 1, sizeof(char*) * *argcp);
+               memcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);
                new_argv[count+*argcp] = NULL;
 
                *argv = new_argv;
@@ -271,6 +274,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "annotate", cmd_annotate, RUN_SETUP },
                { "apply", cmd_apply },
                { "archive", cmd_archive },
+               { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE },
                { "blame", cmd_blame, RUN_SETUP },
                { "branch", cmd_branch, RUN_SETUP },
                { "bundle", cmd_bundle },
@@ -425,27 +429,38 @@ static void execv_dashed_external(const char **argv)
        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:
         *
@@ -482,38 +497,29 @@ int main(int argc, const char **argv)
 
        /*
         * We use PATH to find git commands, but we prepend some higher
-        * precidence paths: the "--exec-path" option, the GIT_EXEC_PATH
+        * precedence paths: the "--exec-path" option, the GIT_EXEC_PATH
         * environment, and the $(gitexecdir) from the Makefile at build
         * time.
         */
        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..1a7887b2528140d63b84a72843c0d0ef64f56538 100644 (file)
@@ -521,7 +521,7 @@ proc updatecommits {} {
     incr viewactive($view)
     set viewcomplete($view) 0
     reset_pending_select {}
-    nowbusy $view "Reading"
+    nowbusy $view [mc "Reading"]
     if {$showneartags} {
        getallcommits
     }
@@ -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} {
@@ -1826,7 +1830,9 @@ proc setoptions {} {
     option add *Button.font uifont startupFile
     option add *Checkbutton.font uifont startupFile
     option add *Radiobutton.font uifont startupFile
-    option add *Menu.font uifont startupFile
+    if {[tk windowingsystem] ne "aqua"} {
+       option add *Menu.font uifont startupFile
+    }
     option add *Menubutton.font uifont startupFile
     option add *Label.font uifont startupFile
     option add *Message.font uifont startupFile
@@ -1906,8 +1912,8 @@ proc makewindow {} {
 
     # The "mc" arguments here are purely so that xgettext
     # sees the following string as needing to be translated
-    makemenu .bar {
-       {mc "File" cascade {
+    set file {
+       mc "File" cascade {
            {mc "Update" command updatecommits -accelerator F5}
            {mc "Reload" command reloadcommits -accelerator Meta1-F5}
            {mc "Reread references" command rereadrefs}
@@ -1917,21 +1923,41 @@ proc makewindow {} {
            {xx "" separator}
            {mc "Quit" command doquit -accelerator Meta1-Q}
        }}
-       {mc "Edit" cascade {
+    set edit {
+       mc "Edit" cascade {
            {mc "Preferences" command doprefs}
        }}
-       {mc "View" cascade {
+    set view {
+       mc "View" cascade {
            {mc "New view..." command {newview 0} -accelerator Shift-F4}
            {mc "Edit view..." command editview -state disabled -accelerator F4}
            {mc "Delete view" command delview -state disabled}
            {xx "" separator}
            {mc "All files" radiobutton {selectedview 0} -command {showview 0}}
        }}
-       {mc "Help" cascade {
+    if {[tk windowingsystem] ne "aqua"} {
+       set help {
+       mc "Help" cascade {
            {mc "About gitk" command about}
            {mc "Key bindings" command keys}
        }}
+       set bar [list $file $edit $view $help]
+    } else {
+       proc ::tk::mac::ShowPreferences {} {doprefs}
+       proc ::tk::mac::Quit {} {doquit}
+       lset file end [lreplace [lindex $file end] end-1 end]
+       set apple {
+       xx "Apple" cascade {
+           {mc "About gitk" command about}
+           {xx "" separator}
+       }}
+       set help {
+       mc "Help" cascade {
+           {mc "Key bindings" command keys}
+       }}
+       set bar [list $apple $file $view $help]
     }
+    makemenu .bar $bar
     . configure -menu .bar
 
     # the gui has upper and lower half, parts of a paned window.
@@ -2225,10 +2251,16 @@ proc makewindow {} {
        }
     }
 
+    if {[info exists geometry(state)] && $geometry(state) eq "zoomed"} {
+        wm state . $geometry(state)
+    }
+
     if {[tk windowingsystem] eq {aqua}} {
         set M1B M1
+        set ::BM "3"
     } else {
         set M1B Control
+        set ::BM "2"
     }
 
     bind .pwbottom <Configure> {resizecdetpanes %W %w}
@@ -2246,10 +2278,14 @@ proc makewindow {} {
                 set delta [expr {- (%D)}]
                 allcanvs yview scroll $delta units
             }
+            bindall <Shift-MouseWheel> {
+                set delta [expr {- (%D)}]
+                $canv xview scroll $delta units
+            }
         }
     }
-    bindall <2> "canvscan mark %W %x %y"
-    bindall <B2-Motion> "canvscan dragto %W %x %y"
+    bindall <$::BM> "canvscan mark %W %x %y"
+    bindall <B$::BM-Motion> "canvscan dragto %W %x %y"
     bindkey <Home> selfirstline
     bindkey <End> sellastline
     bind . <Key-Up> "selnextline -1"
@@ -2281,6 +2317,7 @@ proc makewindow {} {
     bindkey d "$ctext yview scroll 18 units"
     bindkey u "$ctext yview scroll -18 units"
     bindkey / {focus $fstring}
+    bindkey <Key-KP_Divide> {focus $fstring}
     bindkey <Key-Return> {dofind 1 1}
     bindkey ? {dofind -1 1}
     bindkey f nextfile
@@ -2327,6 +2364,10 @@ proc makewindow {} {
        {mc "Create new branch" command mkbranch}
        {mc "Cherry-pick this commit" command cherrypick}
        {mc "Reset HEAD branch to here" command resethead}
+       {mc "Mark this commit" command markhere}
+       {mc "Return to mark" command gotomark}
+       {mc "Find descendant of this and mark" command find_common_desc}
+       {mc "Compare with marked commit" command compare_commits}
     }
     $rowctxmenu configure -tearoff 0
 
@@ -2483,6 +2524,9 @@ proc savestuff {w} {
     if {![winfo viewable .]} return
     catch {
        set f [open "~/.gitk-new" w]
+       if {$::tcl_platform(platform) eq {windows}} {
+           file attributes "~/.gitk-new" -hidden true
+       }
        puts $f [list set mainfont $mainfont]
        puts $f [list set textfont $textfont]
        puts $f [list set uifont $uifont]
@@ -2508,6 +2552,7 @@ proc savestuff {w} {
        puts $f [list set perfile_attrs $perfile_attrs]
 
        puts $f "set geometry(main) [wm geometry .]"
+       puts $f "set geometry(state) [wm state .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
        puts $f "set geometry(topheight) [winfo height .tf]"
         puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
@@ -3200,9 +3245,8 @@ proc external_diff {} {
     set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]
 
     if {$difffromfile ne {} && $difftofile ne {}} {
-        set cmd [concat | [shellsplit $extdifftool] \
-                    [list $difffromfile $difftofile]]
-        if {[catch {set fl [open $cmd r]} err]} {
+        set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
+        if {[catch {set fl [open |$cmd r]} err]} {
             file delete -force $diffdir
             error_popup "$extdifftool: [mc "command failed:"] $err"
         } else {
@@ -3363,7 +3407,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"
     }
@@ -3734,7 +3777,7 @@ proc editview {} {
     set newviewopts($curview,perm) $viewperm($curview)
     set newviewopts($curview,cmd)  $viewargscmd($curview)
     decode_view_opts $curview $viewargs($curview)
-    vieweditor $top $curview "Gitk: edit view $viewname($curview)"
+    vieweditor $top $curview "[mc "Gitk: edit view"] $viewname($curview)"
 }
 
 proc vieweditor {top n title} {
@@ -4043,7 +4086,7 @@ proc ishighlighted {id} {
 }
 
 proc bolden {id font} {
-    global canv linehtag currentid boldids need_redisplay
+    global canv linehtag currentid boldids need_redisplay markedid
 
     # need_redisplay = 1 means the display is stale and about to be redrawn
     if {$need_redisplay} return
@@ -4056,6 +4099,9 @@ proc bolden {id font} {
                   -fill [$canv cget -selectbackground]]
        $canv lower $t
     }
+    if {[info exists markedid] && $id eq $markedid} {
+       make_idmark $id
+    }
 }
 
 proc bolden_name {id font} {
@@ -5560,7 +5606,7 @@ proc drawcmittext {id row col} {
     global cmitlisted commitinfo rowidlist parentlist
     global rowtextx idpos idtags idheads idotherrefs
     global linehtag linentag linedtag selectedline
-    global canvxmax boldids boldnameids fgcolor
+    global canvxmax boldids boldnameids fgcolor markedid
     global mainheadid nullid nullid2 circleitem circlecolors ctxbut
 
     # listed is 0 for boundary, 1 for normal, 2 for negative, 3 for left, 4 for right
@@ -5642,6 +5688,9 @@ proc drawcmittext {id row col} {
     if {$selectedline == $row} {
        make_secsel $id
     }
+    if {[info exists markedid] && $markedid eq $id} {
+       make_idmark $id
+    }
     set xr [expr {$xt + [font measure $font $headline]}]
     if {$xr > $canvxmax} {
        set canvxmax $xr
@@ -5731,7 +5780,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
@@ -6441,6 +6489,17 @@ proc setlink {id lk} {
     }
 }
 
+proc appendshortlink {id {pre {}} {post {}}} {
+    global ctext linknum
+
+    $ctext insert end $pre
+    $ctext tag delete link$linknum
+    $ctext insert end [string range $id 0 7] link$linknum
+    $ctext insert end $post
+    setlink $id link$linknum
+    incr linknum
+}
+
 proc makelink {id} {
     global pendinglinks
 
@@ -6497,7 +6556,7 @@ proc appendrefs {pos ids var} {
        }
     }
     if {[llength $tags] > $maxrefs} {
-       $ctext insert $pos "many ([llength $tags])"
+       $ctext insert $pos "[mc "many"] ([llength $tags])"
     } else {
        set tags [lsort -index 0 -decreasing $tags]
        set sep {}
@@ -6584,6 +6643,16 @@ proc make_secsel {id} {
     $canv3 lower $t
 }
 
+proc make_idmark {id} {
+    global linehtag canv fgcolor
+
+    if {![info exists linehtag($id)]} return
+    $canv delete markid
+    set t [eval $canv create rect [$canv bbox $linehtag($id)] \
+              -tags markid -outline $fgcolor]
+    $canv raise $t
+}
+
 proc selectline {l isnew {desired_loc {}}} {
     global canv ctext commitinfo selectedline
     global canvy0 linespc parents children curview
@@ -7214,7 +7283,7 @@ proc getblobdiffs {ids} {
     set diffnparents 0
     set diffinhdr 0
     set diffencoding [get_path_encoding {}]
-    fconfigure $bdf -blocking 0 -encoding binary
+    fconfigure $bdf -blocking 0 -encoding binary -eofchar {}
     set blobdifffd($ids) $bdf
     filerun $bdf [list getblobdiffline $bdf $diffids]
 }
@@ -7365,7 +7434,8 @@ proc getblobdiffline {bdf ids} {
            $ctext insert end "$line\n" filesep
 
        } else {
-           set line [encoding convertfrom $diffencoding $line]
+           set line [string map {\x1A ^Z} \
+                          [encoding convertfrom $diffencoding $line]]
            # parse the prefix - one ' ', '-' or '+' for each parent
            set prefix [string range $line 0 [expr {$diffnparents - 1}]]
            set tag [expr {$diffnparents > 1? "m": "d"}]
@@ -7968,7 +8038,7 @@ proc mstime {} {
 
 proc rowmenu {x y id} {
     global rowctxmenu selectedline rowmenuid curview
-    global nullid nullid2 fakerowmenu mainhead
+    global nullid nullid2 fakerowmenu mainhead markedid
 
     stopfinding
     set rowmenuid $id
@@ -7984,6 +8054,15 @@ proc rowmenu {x y id} {
        } else {
            $menu entryconfigure 7 -label [mc "Detached head: can't reset" $mainhead] -state disabled
        }
+       if {[info exists markedid] && $markedid ne $id} {
+           $menu entryconfigure 9 -state normal
+           $menu entryconfigure 10 -state normal
+           $menu entryconfigure 11 -state normal
+       } else {
+           $menu entryconfigure 9 -state disabled
+           $menu entryconfigure 10 -state disabled
+           $menu entryconfigure 11 -state disabled
+       }
     } else {
        set menu $fakerowmenu
     }
@@ -7993,6 +8072,162 @@ proc rowmenu {x y id} {
     tk_popup $menu $x $y
 }
 
+proc markhere {} {
+    global rowmenuid markedid canv
+
+    set markedid $rowmenuid
+    make_idmark $markedid
+}
+
+proc gotomark {} {
+    global markedid
+
+    if {[info exists markedid]} {
+       selbyid $markedid
+    }
+}
+
+proc replace_by_kids {l r} {
+    global curview children
+
+    set id [commitonrow $r]
+    set l [lreplace $l 0 0]
+    foreach kid $children($curview,$id) {
+       lappend l [rowofcommit $kid]
+    }
+    return [lsort -integer -decreasing -unique $l]
+}
+
+proc find_common_desc {} {
+    global markedid rowmenuid curview children
+
+    if {![info exists markedid]} return
+    if {![commitinview $markedid $curview] ||
+       ![commitinview $rowmenuid $curview]} return
+    #set t1 [clock clicks -milliseconds]
+    set l1 [list [rowofcommit $markedid]]
+    set l2 [list [rowofcommit $rowmenuid]]
+    while 1 {
+       set r1 [lindex $l1 0]
+       set r2 [lindex $l2 0]
+       if {$r1 eq {} || $r2 eq {}} break
+       if {$r1 == $r2} {
+           selectline $r1 1
+           break
+       }
+       if {$r1 > $r2} {
+           set l1 [replace_by_kids $l1 $r1]
+       } else {
+           set l2 [replace_by_kids $l2 $r2]
+       }
+    }
+    #set t2 [clock clicks -milliseconds]
+    #puts "took [expr {$t2-$t1}]ms"
+}
+
+proc compare_commits {} {
+    global markedid rowmenuid curview children
+
+    if {![info exists markedid]} return
+    if {![commitinview $markedid $curview]} return
+    addtohistory [list do_cmp_commits $markedid $rowmenuid]
+    do_cmp_commits $markedid $rowmenuid
+}
+
+proc getpatchid {id} {
+    global patchids
+
+    if {![info exists patchids($id)]} {
+       set cmd [diffcmd [list $id] {-p --root}]
+       # trim off the initial "|"
+       set cmd [lrange $cmd 1 end]
+       if {[catch {
+           set x [eval exec $cmd | git patch-id]
+           set patchids($id) [lindex $x 0]
+       }]} {
+           set patchids($id) "error"
+       }
+    }
+    return $patchids($id)
+}
+
+proc do_cmp_commits {a b} {
+    global ctext curview parents children patchids commitinfo
+
+    $ctext conf -state normal
+    clear_ctext
+    init_flist {}
+    for {set i 0} {$i < 100} {incr i} {
+       set skipa 0
+       set skipb 0
+       if {[llength $parents($curview,$a)] > 1} {
+           appendshortlink $a [mc "Skipping merge commit "] "\n"
+           set skipa 1
+       } else {
+           set patcha [getpatchid $a]
+       }
+       if {[llength $parents($curview,$b)] > 1} {
+           appendshortlink $b [mc "Skipping merge commit "] "\n"
+           set skipb 1
+       } else {
+           set patchb [getpatchid $b]
+       }
+       if {!$skipa && !$skipb} {
+           set heada [lindex $commitinfo($a) 0]
+           set headb [lindex $commitinfo($b) 0]
+           if {$patcha eq "error"} {
+               appendshortlink $a [mc "Error getting patch ID for "] \
+                   [mc " - stopping\n"]
+               break
+           }
+           if {$patchb eq "error"} {
+               appendshortlink $b [mc "Error getting patch ID for "] \
+                   [mc " - stopping\n"]
+               break
+           }
+           if {$patcha eq $patchb} {
+               if {$heada eq $headb} {
+                   appendshortlink $a [mc "Commit "]
+                   appendshortlink $b " == " "  $heada\n"
+               } else {
+                   appendshortlink $a [mc "Commit "] "  $heada\n"
+                   appendshortlink $b [mc " is the same patch as\n       "] \
+                       "  $headb\n"
+               }
+               set skipa 1
+               set skipb 1
+           } else {
+               $ctext insert end "\n"
+               appendshortlink $a [mc "Commit "] "  $heada\n"
+               appendshortlink $b [mc " differs from\n       "] \
+                   "  $headb\n"
+               $ctext insert end [mc "- stopping\n"]
+               break
+           }
+       }
+       if {$skipa} {
+           if {[llength $children($curview,$a)] != 1} {
+               $ctext insert end "\n"
+               appendshortlink $a [mc "Commit "] \
+                   [mc " has %s children - stopping\n" \
+                        [llength $children($curview,$a)]]
+               break
+           }
+           set a [lindex $children($curview,$a) 0]
+       }
+       if {$skipb} {
+           if {[llength $children($curview,$b)] != 1} {
+               appendshortlink $b [mc "Commit "] \
+                   [mc " has %s children - stopping\n" \
+                        [llength $children($curview,$b)]]
+               break
+           }
+           set b [lindex $children($curview,$b) 0]
+       }
+    }
+    $ctext conf -state disabled
+}
+
 proc diffvssel {dirn} {
     global rowmenuid selectedline
 
@@ -8187,7 +8422,7 @@ proc domktag {} {
 }
 
 proc redrawtags {id} {
-    global canv linehtag idpos currentid curview cmitlisted
+    global canv linehtag idpos currentid curview cmitlisted markedid
     global canvxmax iddrawn circleitem mainheadid circlecolors
 
     if {![commitinview $id $curview]} return
@@ -8212,6 +8447,9 @@ proc redrawtags {id} {
     if {[info exists currentid] && $currentid == $id} {
        make_secsel $id
     }
+    if {[info exists markedid] && $markedid eq $id} {
+       make_idmark $id
+    }
 }
 
 proc mktagcan {} {
@@ -10195,7 +10433,7 @@ proc doprefs {} {
 proc choose_extdiff {} {
     global extdifftool
 
-    set prog [tk_getOpenFile -title "External diff tool" -multiple false]
+    set prog [tk_getOpenFile -title [mc "External diff tool"] -multiple false]
     if {$prog ne {}} {
        set extdifftool $prog
     }
@@ -10238,6 +10476,7 @@ proc setfg {c} {
     }
     allcanvs itemconf text -fill $c
     $canv itemconf circle -outline $c
+    $canv itemconf markid -outline $c
 }
 
 proc prefscan {} {
@@ -10687,9 +10926,15 @@ catch {
     }
 }
 
-set mainfont {Helvetica 9}
-set textfont {Courier 9}
-set uifont {Helvetica 9 bold}
+if {[tk windowingsystem] eq "aqua"} {
+    set mainfont {{Lucida Grande} 9}
+    set textfont {Monaco 9}
+    set uifont {{Lucida Grande} 9 bold}
+} else {
+    set mainfont {Helvetica 9}
+    set textfont {Courier 9}
+    set uifont {Helvetica 9 bold}
+}
 set tabstop 8
 set findmergefiles 0
 set maxgraphpct 50
@@ -10710,7 +10955,11 @@ set datetimeformat "%Y-%m-%d %H:%M:%S"
 set autoselect 1
 set perfile_attrs 0
 
-set extdifftool "meld"
+if {[tk windowingsystem] eq "aqua"} {
+    set extdifftool "opendiff"
+} else {
+    set extdifftool "meld"
+}
 
 set colors {green red blue magenta darkgrey brown orange}
 set bgcolor white
@@ -10881,9 +11130,33 @@ set lserial 0
 set isworktree [expr {[exec git rev-parse --is-inside-work-tree] == "true"}]
 setcoords
 makewindow
+catch {
+    image create photo gitlogo      -width 16 -height 16
+
+    image create photo gitlogominus -width  4 -height  2
+    gitlogominus put #C00000 -to 0 0 4 2
+    gitlogo copy gitlogominus -to  1 5
+    gitlogo copy gitlogominus -to  6 5
+    gitlogo copy gitlogominus -to 11 5
+    image delete gitlogominus
+
+    image create photo gitlogoplus  -width  4 -height  4
+    gitlogoplus  put #008000 -to 1 0 3 4
+    gitlogoplus  put #008000 -to 0 1 4 3
+    gitlogo copy gitlogoplus  -to  1 9
+    gitlogo copy gitlogoplus  -to  6 9
+    gitlogo copy gitlogoplus  -to 11 9
+    image delete gitlogoplus
+
+    image create photo gitlogo32    -width 32 -height 32
+    gitlogo32 copy gitlogo -zoom 2 2
+
+    wm iconphoto . -default gitlogo gitlogo32
+}
 # wait for the window to become visible
 tkwait visibility .
 wm title . "[file tail $argv0]: [file tail [pwd]]"
+update
 readrefs
 
 if {$cmdline_files ne {} || $revtreeargs ne {} || $revtreeargscmd ne {}} {
diff --git a/gitk-git/po/ru.po b/gitk-git/po/ru.po
new file mode 100644 (file)
index 0000000..704eba8
--- /dev/null
@@ -0,0 +1,1085 @@
+#
+# Translation of gitk to Russian.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: gitk\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-04-24 16:00+0200\n"
+"PO-Revision-Date: 2009-04-24 16:00+0200\n"
+"Last-Translator: Alex Riesen <raa.lkml@gmail.com>\n"
+"Language-Team: Russian\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: gitk:113
+msgid "Couldn't get list of unmerged files:"
+msgstr ""
+"Невозможно получить список файлов незавершённой операции слияния:"
+
+#: gitk:268
+msgid "Error parsing revisions:"
+msgstr "Ошибка в идентификаторе версии:"
+
+#: gitk:323
+msgid "Error executing --argscmd command:"
+msgstr "Ошибка выполнения команды заданой --argscmd:"
+
+#: gitk:336
+msgid "No files selected: --merge specified but no files are unmerged."
+msgstr ""
+"Файлы не выбраны: указан --merge, но не было найдено ни одного файла "
+"где эта операция должна быть завершена."
+
+#: gitk:339
+msgid ""
+"No files selected: --merge specified but no unmerged files are within file "
+"limit."
+msgstr ""
+"Файлы не выбраны: указан --merge, но в рамках указаного "
+"ограничения на имена файлов нет ни одного "
+"где эта операция должна быть завершена."
+
+#: gitk:361 gitk:508
+msgid "Error executing git log:"
+msgstr "Ошибка запуска git log:"
+
+#: gitk:379
+msgid "Reading"
+msgstr "Чтение"
+
+#: gitk:439 gitk:4021
+msgid "Reading commits..."
+msgstr "Чтение версий..."
+
+#: gitk:442 gitk:1560 gitk:4024
+msgid "No commits selected"
+msgstr "Ничего не выбрано"
+
+#: gitk:1436
+msgid "Can't parse git log output:"
+msgstr "Ошибка обработки вывода команды git log:"
+
+#: gitk:1656
+msgid "No commit information available"
+msgstr "Нет информации о состоянии"
+
+#: gitk:1791 gitk:1815 gitk:3814 gitk:8478 gitk:10014 gitk:10186
+msgid "OK"
+msgstr "Ok"
+
+#: gitk:1817 gitk:3816 gitk:8078 gitk:8152 gitk:8259 gitk:8308 gitk:8480
+#: gitk:10015 gitk:10187
+msgid "Cancel"
+msgstr "Отмена"
+
+#: gitk:1915
+msgid "Update"
+msgstr "Обновить"
+
+#: gitk:1916
+msgid "Reload"
+msgstr "Перечитать"
+
+#: gitk:1917
+msgid "Reread references"
+msgstr "Обновить список ссылок"
+
+#: gitk:1918
+msgid "List references"
+msgstr "Список ссылок"
+
+#: gitk:1920
+msgid "Start git gui"
+msgstr "Запустить git gui"
+
+#: gitk:1922
+msgid "Quit"
+msgstr "Завершить"
+
+#: gitk:1914
+msgid "File"
+msgstr "Файл"
+
+#: gitk:1925
+msgid "Preferences"
+msgstr "Настройки"
+
+#: gitk:1924
+msgid "Edit"
+msgstr "Редактировать"
+
+#: gitk:1928
+msgid "New view..."
+msgstr "Новое представление..."
+
+#: gitk:1929
+msgid "Edit view..."
+msgstr "Редактировать представление..."
+
+#: gitk:1930
+msgid "Delete view"
+msgstr "Удалить представление"
+
+#: gitk:1932
+msgid "All files"
+msgstr "Все файлы"
+
+#: gitk:1927 gitk:3626
+msgid "View"
+msgstr "Представление"
+
+#: gitk:1935 gitk:2609
+msgid "About gitk"
+msgstr "О gitk"
+
+#: gitk:1936
+msgid "Key bindings"
+msgstr "Назначения клавиатуры"
+
+#: gitk:1934
+msgid "Help"
+msgstr "Подсказка"
+
+#: gitk:1994
+msgid "SHA1 ID: "
+msgstr "SHA1:"
+
+#: gitk:2025
+msgid "Row"
+msgstr "Строка"
+
+#: gitk:2056
+msgid "Find"
+msgstr "Поиск"
+
+#: gitk:2057
+msgid "next"
+msgstr "След."
+
+#: gitk:2058
+msgid "prev"
+msgstr "Пред."
+
+#: gitk:2059
+msgid "commit"
+msgstr "состояние"
+
+#: gitk:2062 gitk:2064 gitk:4179 gitk:4202 gitk:4226 gitk:6164 gitk:6236
+#: gitk:6320
+msgid "containing:"
+msgstr "содержащее:"
+
+#: gitk:2065 gitk:3117 gitk:3122 gitk:4254
+msgid "touching paths:"
+msgstr "касательно файлов:"
+
+#: gitk:2066 gitk:4259
+msgid "adding/removing string:"
+msgstr "добавив/удалив строку:"
+
+#: gitk:2075 gitk:2077
+msgid "Exact"
+msgstr "Точно"
+
+#: gitk:2077 gitk:4334 gitk:6132
+msgid "IgnCase"
+msgstr "Игнорировать большие/маленькие"
+
+#: gitk:2077 gitk:4228 gitk:4332 gitk:6128
+msgid "Regexp"
+msgstr "Регулярные выражения"
+
+#: gitk:2079 gitk:2080 gitk:4353 gitk:4383 gitk:4390 gitk:6256 gitk:6324
+msgid "All fields"
+msgstr "Во всех полях"
+
+#: gitk:2080 gitk:4351 gitk:4383 gitk:6195
+msgid "Headline"
+msgstr "Заголовок"
+
+#: gitk:2081 gitk:4351 gitk:6195 gitk:6324 gitk:6737
+msgid "Comments"
+msgstr "Комментарии"
+
+#: gitk:2081 gitk:4351 gitk:4355 gitk:4390 gitk:6195 gitk:6672 gitk:7923
+#: gitk:7938
+msgid "Author"
+msgstr "Автор"
+
+#: gitk:2081 gitk:4351 gitk:6195 gitk:6674
+msgid "Committer"
+msgstr "Сохранивший состояние"
+
+#: gitk:2110
+msgid "Search"
+msgstr "Найти"
+
+#: gitk:2117
+msgid "Diff"
+msgstr "Сравнить"
+
+#: gitk:2119
+msgid "Old version"
+msgstr "Старая версия"
+
+#: gitk:2121
+msgid "New version"
+msgstr "Новая версия"
+
+#: gitk:2123
+msgid "Lines of context"
+msgstr "Строк контекста"
+
+#: gitk:2133
+msgid "Ignore space change"
+msgstr "Игнорировать пробелы"
+
+#: gitk:2191
+msgid "Patch"
+msgstr "Патч"
+
+#: gitk:2193
+msgid "Tree"
+msgstr "Файлы"
+
+#: gitk:2326 gitk:2339
+msgid "Diff this -> selected"
+msgstr "Сравнить это состояние с выделеным"
+
+#: gitk:2327 gitk:2340
+msgid "Diff selected -> this"
+msgstr "Сравнить выделеное с этим состоянием"
+
+#: gitk:2328 gitk:2341
+msgid "Make patch"
+msgstr "Создать патч"
+
+#: gitk:2329 gitk:8136
+msgid "Create tag"
+msgstr "Создать метку"
+
+#: gitk:2330 gitk:8239
+msgid "Write commit to file"
+msgstr "Сохранить изменения в файл"
+
+#: gitk:2331 gitk:8296
+msgid "Create new branch"
+msgstr "Создать ветвь"
+
+#: gitk:2332
+msgid "Cherry-pick this commit"
+msgstr "Скопировать это состояние"
+
+#: gitk:2333
+msgid "Reset HEAD branch to here"
+msgstr "Установить HEAD на это состояние"
+
+#: gitk:2347
+msgid "Check out this branch"
+msgstr "Перейти на эту ветвь"
+
+#: gitk:2348
+msgid "Remove this branch"
+msgstr "Удалить эту ветвь"
+
+#: gitk:2355
+msgid "Highlight this too"
+msgstr "Подсветить этот тоже"
+
+#: gitk:2356
+msgid "Highlight this only"
+msgstr "Подсветить только этот"
+
+#: gitk:2357
+msgid "External diff"
+msgstr "Программа сравнения"
+
+#: gitk:2358
+msgid "Blame parent commit"
+msgstr "Аннотировать родительское состояние"
+
+#: gitk:2365
+msgid "Show origin of this line"
+msgstr "Показать источник этой строки"
+
+#: gitk:2366
+msgid "Run git gui blame on this line"
+msgstr "Запустить git gui blame для этой строки"
+
+#: gitk:2611
+msgid ""
+"\n"
+"Gitk - a commit viewer for git\n"
+"\n"
+"Copyright © 2005-2008 Paul Mackerras\n"
+"\n"
+"Use and redistribute under the terms of the GNU General Public License"
+msgstr ""
+"\n"
+"Gitk - программа просмотра истории репозиториев Git\n"
+"\n"
+"Copyright (c) 2005-2008 Paul Mackerras\n"
+"\n"
+"Использование и распространение согласно условиям GNU General Public License"
+
+#: gitk:2619 gitk:2681 gitk:8661
+msgid "Close"
+msgstr "Закрыть"
+
+#: gitk:2638
+msgid "Gitk key bindings"
+msgstr "Назначения клавиатуры в Gitk"
+
+#: gitk:2641
+msgid "Gitk key bindings:"
+msgstr "Назначения клавиатуры в Gitk:"
+
+#: gitk:2643
+#, tcl-format
+msgid "<%s-Q>\t\tQuit"
+msgstr "<%s-Q>\t\tЗавершить"
+
+#: gitk:2644
+msgid "<Home>\t\tMove to first commit"
+msgstr "<Home>\t\tПерейти к первому состоянию"
+
+#: gitk:2645
+msgid "<End>\t\tMove to last commit"
+msgstr "<End>\t\tПерейти к последнему состоянию"
+
+#: gitk:2646
+msgid "<Up>, p, i\tMove up one commit"
+msgstr "<Up>, p, i\tПерейти к следующему состоянию"
+
+#: gitk:2647
+msgid "<Down>, n, k\tMove down one commit"
+msgstr "<Down>, n, k\tПерейти к предыдущему состоянию"
+
+#: gitk:2648
+msgid "<Left>, z, j\tGo back in history list"
+msgstr "<Left>, z, j\tПоказать ранее посещённое состояние"
+
+#: gitk:2649
+msgid "<Right>, x, l\tGo forward in history list"
+msgstr "<Right>, x, l\tПоказать следующее посещённое состояние"
+
+#: gitk:2650
+msgid "<PageUp>\tMove up one page in commit list"
+msgstr "<PageUp>\tПерейти на страницу выше в списке состояний"
+
+#: gitk:2651
+msgid "<PageDown>\tMove down one page in commit list"
+msgstr "<PageDown>\tПерейти на страницу ниже в списке состояний"
+
+#: gitk:2652
+#, tcl-format
+msgid "<%s-Home>\tScroll to top of commit list"
+msgstr "<%s-Home>\tПоказать начало списка состояний"
+
+#: gitk:2653
+#, tcl-format
+msgid "<%s-End>\tScroll to bottom of commit list"
+msgstr "<%s-End>\tПоказать конец списка состояний"
+
+#: gitk:2654
+#, tcl-format
+msgid "<%s-Up>\tScroll commit list up one line"
+msgstr "<%s-Up>\tПровернуть список состояний вверх"
+
+#: gitk:2655
+#, tcl-format
+msgid "<%s-Down>\tScroll commit list down one line"
+msgstr "<%s-Down>\tПровернуть список состояний вниз"
+
+#: gitk:2656
+#, tcl-format
+msgid "<%s-PageUp>\tScroll commit list up one page"
+msgstr "<%s-PageUp>\tПровернуть список состояний на страницу вверх"
+
+#: gitk:2657
+#, tcl-format
+msgid "<%s-PageDown>\tScroll commit list down one page"
+msgstr "<%s-PageDown>\tПровернуть список состояний на страницу вниз"
+
+#: gitk:2658
+msgid "<Shift-Up>\tFind backwards (upwards, later commits)"
+msgstr ""
+"<Shift-Up>\tПоиск в обратном порядке (вверх, среди новых состояний)"
+
+#: gitk:2659
+msgid "<Shift-Down>\tFind forwards (downwards, earlier commits)"
+msgstr "<Shift-Down>\tПоиск (вниз, среди старых состояний)"
+
+#: gitk:2660
+msgid "<Delete>, b\tScroll diff view up one page"
+msgstr "<Delete>, b\tПрокрутить список изменений на страницу выше"
+
+#: gitk:2661
+msgid "<Backspace>\tScroll diff view up one page"
+msgstr "<Backspace>\tПрокрутить список изменений на страницу выше"
+
+#: gitk:2662
+msgid "<Space>\t\tScroll diff view down one page"
+msgstr "<Leertaste>\t\tПрокрутить список изменений на страницу ниже"
+
+#: gitk:2663
+msgid "u\t\tScroll diff view up 18 lines"
+msgstr "u\t\tПрокрутить список изменений на 18 строк вверх"
+
+#: gitk:2664
+msgid "d\t\tScroll diff view down 18 lines"
+msgstr "d\t\tПрокрутить список изменений на 18 строк вниз"
+
+#: gitk:2665
+#, tcl-format
+msgid "<%s-F>\t\tFind"
+msgstr "<%s-F>\t\tПоиск"
+
+#: gitk:2666
+#, tcl-format
+msgid "<%s-G>\t\tMove to next find hit"
+msgstr "<%s-G>\t\tПерейти к следующему найденому состоянию"
+
+#: gitk:2667
+msgid "<Return>\tMove to next find hit"
+msgstr "<Return>\tПерейти к следующему найденому состоянию"
+
+#: gitk:2668
+msgid "/\t\tFocus the search box"
+msgstr "/\t\tПерейти к полю поиска"
+
+#: gitk:2669
+msgid "?\t\tMove to previous find hit"
+msgstr "?\t\tПерейти к предыдущему найденому состоянию"
+
+#: gitk:2670
+msgid "f\t\tScroll diff view to next file"
+msgstr "f\t\tПрокрутить список изменений к следующему файлу"
+
+#: gitk:2671
+#, tcl-format
+msgid "<%s-S>\t\tSearch for next hit in diff view"
+msgstr "<%s-S>\t\tПродолжить поиск в списке изменений"
+
+#: gitk:2672
+#, tcl-format
+msgid "<%s-R>\t\tSearch for previous hit in diff view"
+msgstr "<%s-R>\t\tПерейти к предыдущему найденому тексту в списке изменений"
+
+#: gitk:2673
+#, tcl-format
+msgid "<%s-KP+>\tIncrease font size"
+msgstr "<%s-KP+>\tУвеличить размер шрифта"
+
+#: gitk:2674
+#, tcl-format
+msgid "<%s-plus>\tIncrease font size"
+msgstr "<%s-plus>\tУвеличить размер шрифта"
+
+#: gitk:2675
+#, tcl-format
+msgid "<%s-KP->\tDecrease font size"
+msgstr "<%s-KP->\tУменьшить размер шрифта"
+
+#: gitk:2676
+#, tcl-format
+msgid "<%s-minus>\tDecrease font size"
+msgstr "<%s-minus>\tУменьшить размер шрифта"
+
+#: gitk:2677
+msgid "<F5>\t\tUpdate"
+msgstr "<F5>\t\tОбновить"
+
+#: gitk:3132
+#, tcl-format
+msgid "Error getting \"%s\" from %s:"
+msgstr "Ошибка получения \"%s\" из %s:"
+
+#: gitk:3189 gitk:3198
+#, tcl-format
+msgid "Error creating temporary directory %s:"
+msgstr "Ошибка создания временного каталога %s:"
+
+#: gitk:3211
+msgid "command failed:"
+msgstr "ошибка выполнения команды:"
+
+#: gitk:3357
+msgid "No such commit"
+msgstr "Состояние не найдено"
+
+#: gitk:3371
+msgid "git gui blame: command failed:"
+msgstr "git gui blame: ошибка выполнения команды:"
+
+#: gitk:3402
+#, tcl-format
+msgid "Couldn't read merge head: %s"
+msgstr "Ошибка чтения MERGE_HEAD: %s"
+
+#: gitk:3410
+#, tcl-format
+msgid "Error reading index: %s"
+msgstr "Ошибка чтения индекса: %s"
+
+#: gitk:3435
+#, tcl-format
+msgid "Couldn't start git blame: %s"
+msgstr "Ошибка запуска git blame: %s"
+
+#: gitk:3438 gitk:6163
+msgid "Searching"
+msgstr "Поиск"
+
+#: gitk:3470
+#, tcl-format
+msgid "Error running git blame: %s"
+msgstr "Ошибка выполнения git blame: %s"
+
+#: gitk:3498
+#, tcl-format
+msgid "That line comes from commit %s,  which is not in this view"
+msgstr ""
+"Эта строка принадлежит состоянию %s, которое не показано в этом "
+"представлении"
+
+#: gitk:3512
+msgid "External diff viewer failed:"
+msgstr "Ошибка выполнения программы сравнения:"
+
+#: gitk:3630
+msgid "Gitk view definition"
+msgstr "Gitk определение представлений"
+
+#: gitk:3634
+msgid "Remember this view"
+msgstr "Запомнить представление"
+
+#: gitk:3635
+msgid "Commits to include (arguments to git log):"
+msgstr "Включить состояния (аргументы для git-log):"
+
+#: gitk:3636
+msgid "Use all refs"
+msgstr "Использовать все ветви"
+
+#: gitk:3637
+msgid "Strictly sort by date"
+msgstr "Строгая сортировка по дате"
+
+#: gitk:3638
+msgid "Mark branch sides"
+msgstr "Отметить стороны ветвей"
+
+#: gitk:3639
+msgid "Since date:"
+msgstr "С даты:"
+
+#: gitk:3640
+msgid "Until date:"
+msgstr "По дату:"
+
+#: gitk:3641
+msgid "Max count:"
+msgstr "Макс. количество:"
+
+#: gitk:3642
+msgid "Skip:"
+msgstr "Пропустить:"
+
+#: gitk:3643
+msgid "Limit to first parent"
+msgstr "Ограничить первым предком"
+
+#: gitk:3644
+msgid "Command to generate more commits to include:"
+msgstr "Дополнительная команда для списка состояний:"
+
+#: gitk:3753
+msgid "Name"
+msgstr "Имя"
+
+#: gitk:3801
+msgid "Enter files and directories to include, one per line:"
+msgstr "Файлы и каталоги для ограничения истории, по одному на строку:"
+
+#: gitk:3815
+msgid "Apply (F5)"
+msgstr "Применить (F5)"
+
+#: gitk:3853
+msgid "Error in commit selection arguments:"
+msgstr "Ошибка в параметрах выбора состояний:"
+
+#: gitk:3906 gitk:3958 gitk:4403 gitk:4417 gitk:5675 gitk:10867 gitk:10868
+msgid "None"
+msgstr "Ни одного"
+
+#: gitk:4351 gitk:6195 gitk:7925 gitk:7940
+msgid "Date"
+msgstr "Дата"
+
+#: gitk:4351 gitk:6195
+msgid "CDate"
+msgstr "Дата ввода"
+
+#: gitk:4500 gitk:4505
+msgid "Descendant"
+msgstr "Порождённое"
+
+#: gitk:4501
+msgid "Not descendant"
+msgstr "Не порождённое"
+
+#: gitk:4508 gitk:4513
+msgid "Ancestor"
+msgstr "Предок"
+
+#: gitk:4509
+msgid "Not ancestor"
+msgstr "Не предок"
+
+#: gitk:4799
+msgid "Local changes checked in to index but not committed"
+msgstr "Изменения зарегистрированные в индексе, но не сохранённые"
+
+#: gitk:4835
+msgid "Local uncommitted changes, not checked in to index"
+msgstr "Изменения в рабочем каталоге, не зарегистрированные в индексе"
+
+#: gitk:6676
+msgid "Tags:"
+msgstr "Таги:"
+
+#: gitk:6693 gitk:6699 gitk:7918
+msgid "Parent"
+msgstr "Предок"
+
+#: gitk:6704
+msgid "Child"
+msgstr "Потомок"
+
+#: gitk:6713
+msgid "Branch"
+msgstr "Ветвь"
+
+#: gitk:6716
+msgid "Follows"
+msgstr "Следует за"
+
+#: gitk:6719
+msgid "Precedes"
+msgstr "Предшествует"
+
+#: gitk:7212
+#, tcl-format
+msgid "Error getting diffs: %s"
+msgstr "Ошибка получения изменений: %s"
+
+#: gitk:7751
+msgid "Goto:"
+msgstr "Перейти к:"
+
+#: gitk:7753
+msgid "SHA1 ID:"
+msgstr "SHA1 ID:"
+
+#: gitk:7772
+#, tcl-format
+msgid "Short SHA1 id %s is ambiguous"
+msgstr "Сокращённый SHA1 идентификатор %s неоднозначен"
+
+#: gitk:7784
+#, tcl-format
+msgid "SHA1 id %s is not known"
+msgstr "SHA1 идентификатор %s не найден"
+
+#: gitk:7786
+#, tcl-format
+msgid "Tag/Head %s is not known"
+msgstr "Метка или ветвь %s не найдена"
+
+#: gitk:7928
+msgid "Children"
+msgstr "Потомки"
+
+#: gitk:7985
+#, tcl-format
+msgid "Reset %s branch to here"
+msgstr "Установить ветвь %s на это состояние"
+
+#: gitk:7987
+msgid "Detached head: can't reset"
+msgstr "Состояние не принадлежит ни одной ветви, переход невозможен"
+
+#: gitk:8019
+msgid "Top"
+msgstr "Верх"
+
+#: gitk:8020
+msgid "From"
+msgstr "От"
+
+#: gitk:8025
+msgid "To"
+msgstr "До"
+
+#: gitk:8049
+msgid "Generate patch"
+msgstr "Создать патч"
+
+#: gitk:8051
+msgid "From:"
+msgstr "От:"
+
+#: gitk:8060
+msgid "To:"
+msgstr "До:"
+
+#: gitk:8069
+msgid "Reverse"
+msgstr "В обратном порядке"
+
+#: gitk:8071 gitk:8253
+msgid "Output file:"
+msgstr "Файл для сохранения:"
+
+#: gitk:8077
+msgid "Generate"
+msgstr "Создать"
+
+#: gitk:8115
+msgid "Error creating patch:"
+msgstr "Ошибка создания патча:"
+
+#: gitk:8138 gitk:8241 gitk:8298
+msgid "ID:"
+msgstr "ID:"
+
+#: gitk:8147
+msgid "Tag name:"
+msgstr "Имя метки:"
+
+#: gitk:8151 gitk:8307
+msgid "Create"
+msgstr "Создать"
+
+#: gitk:8168
+msgid "No tag name specified"
+msgstr "Не задано имя метки"
+
+#: gitk:8172
+#, tcl-format
+msgid "Tag \"%s\" already exists"
+msgstr "Метка \"%s\" уже существует"
+
+#: gitk:8178
+msgid "Error creating tag:"
+msgstr "Ошибка создания метки:"
+
+#: gitk:8250
+msgid "Command:"
+msgstr "Команда:"
+
+#: gitk:8258
+msgid "Write"
+msgstr "Запись"
+
+#: gitk:8276
+msgid "Error writing commit:"
+msgstr "Ошибка сохранения состояния:"
+
+#: gitk:8303
+msgid "Name:"
+msgstr "Имя:"
+
+#: gitk:8326
+msgid "Please specify a name for the new branch"
+msgstr "Укажите имя для новой ветви"
+
+#: gitk:8331
+#, tcl-format
+msgid "Branch '%s' already exists. Overwrite?"
+msgstr "Ветвь '%s' уже существует. Переписать?"
+
+#: gitk:8397
+#, tcl-format
+msgid "Commit %s is already included in branch %s -- really re-apply it?"
+msgstr ""
+"Состояние %s уже принадлежит ветви %s. Продолжить операцию?"
+
+#: gitk:8402
+msgid "Cherry-picking"
+msgstr "Копирование изменений"
+
+#: gitk:8411
+#, tcl-format
+msgid ""
+"Cherry-pick failed because of local changes to file '%s'.\n"
+"Please commit, reset or stash your changes and try again."
+msgstr ""
+"Копирование невозможно из-за изменений в файле '%s'.\n"
+"Сохраните или отмените изменения и повторите операцию."
+
+#: gitk:8417
+msgid ""
+"Cherry-pick failed because of merge conflict.\n"
+"Do you wish to run git citool to resolve it?"
+msgstr ""
+"Копирование изменений невозможно из-за незавершённой операции "
+"слияния.\nЗапустить git citool для завершения этой операции?"
+
+#: gitk:8433
+msgid "No changes committed"
+msgstr "Изменения не сохранены"
+
+#: gitk:8459
+msgid "Confirm reset"
+msgstr "Подтвердите операцию перехода"
+
+#: gitk:8461
+#, tcl-format
+msgid "Reset branch %s to %s?"
+msgstr "Установить ветвь %s на состояние %s?"
+
+#: gitk:8465
+msgid "Reset type:"
+msgstr "Тип операции перехода:"
+
+#: gitk:8469
+msgid "Soft: Leave working tree and index untouched"
+msgstr "Лёгкий: оставить рабочий каталог и индекс неизменными"
+
+#: gitk:8472
+msgid "Mixed: Leave working tree untouched, reset index"
+msgstr ""
+"Смешаный: оставить рабочий каталог неизменным, установить индекс"
+
+#: gitk:8475
+msgid ""
+"Hard: Reset working tree and index\n"
+"(discard ALL local changes)"
+msgstr ""
+"Жесткий: переписать индекс и рабочий каталог\n"
+"(все изменения в рабочем каталоги будут потеряны)"
+
+#: gitk:8492
+msgid "Resetting"
+msgstr "Установка"
+
+#: gitk:8549
+msgid "Checking out"
+msgstr "Переход"
+
+#: gitk:8602
+msgid "Cannot delete the currently checked-out branch"
+msgstr "Активная ветвь не может быть удалена"
+
+#: gitk:8608
+#, tcl-format
+msgid ""
+"The commits on branch %s aren't on any other branch.\n"
+"Really delete branch %s?"
+msgstr ""
+"Состояния ветви %s больше не принадлежат никакой другой ветви.\n"
+"Действительно удалить ветвь %s?"
+
+#: gitk:8639
+#, tcl-format
+msgid "Tags and heads: %s"
+msgstr "Метки и ветви: %s"
+
+#: gitk:8654
+msgid "Filter"
+msgstr "Фильтровать"
+
+#: gitk:8949
+msgid ""
+"Error reading commit topology information; branch and preceding/following "
+"tag information will be incomplete."
+msgstr ""
+"Ошибка чтения истории проекта; информация о ветвях и состояниях "
+"вокруг меток (до/после) может быть неполной."
+
+#: gitk:9935
+msgid "Tag"
+msgstr "Метка"
+
+#: gitk:9935
+msgid "Id"
+msgstr "Id"
+
+#: gitk:9983
+msgid "Gitk font chooser"
+msgstr "Шрифт Gitk"
+
+#: gitk:10000
+msgid "B"
+msgstr "Ж"
+
+#: gitk:10003
+msgid "I"
+msgstr "К"
+
+#: gitk:10098
+msgid "Gitk preferences"
+msgstr "Настройки Gitk"
+
+#: gitk:10100
+msgid "Commit list display options"
+msgstr "Параметры показа списка состояний"
+
+#: gitk:10103
+msgid "Maximum graph width (lines)"
+msgstr "Макс. ширина графа (строк)"
+
+#: gitk:10107
+#, tcl-format
+msgid "Maximum graph width (% of pane)"
+msgstr "Макс. ширина графа (% ширины панели)"
+
+#: gitk:10111
+msgid "Show local changes"
+msgstr "Показывать изменения в рабочем каталоге"
+
+#: gitk:10114
+msgid "Auto-select SHA1"
+msgstr "Выделить SHA1"
+
+#: gitk:10118
+msgid "Diff display options"
+msgstr "Параметры показа изменений"
+
+#: gitk:10120
+msgid "Tab spacing"
+msgstr "Ширина табуляции"
+
+#: gitk:10123
+msgid "Display nearby tags"
+msgstr "Показывать близкие метки"
+
+#: gitk:10126
+msgid "Limit diffs to listed paths"
+msgstr "Ограничить показ изменений выбраными файлами"
+
+#: gitk:10129
+msgid "Support per-file encodings"
+msgstr "Поддержка кодировок в отдельных файлах"
+
+#: gitk:10135
+msgid "External diff tool"
+msgstr "Программа для показа изменений"
+
+#: gitk:10137
+msgid "Choose..."
+msgstr "Выберите..."
+
+#: gitk:10142
+msgid "Colors: press to choose"
+msgstr "Цвета: нажмите для выбора"
+
+#: gitk:10145
+msgid "Background"
+msgstr "Фон"
+
+#: gitk:10146 gitk:10176
+msgid "background"
+msgstr "фон"
+
+#: gitk:10149
+msgid "Foreground"
+msgstr "Передний план"
+
+#: gitk:10150
+msgid "foreground"
+msgstr "передний план"
+
+#: gitk:10153
+msgid "Diff: old lines"
+msgstr "Изменения: старый текст"
+
+#: gitk:10154
+msgid "diff old lines"
+msgstr "старый текст изменения"
+
+#: gitk:10158
+msgid "Diff: new lines"
+msgstr "Изменения: новый текст"
+
+#: gitk:10159
+msgid "diff new lines"
+msgstr "новый текст изменения"
+
+#: gitk:10163
+msgid "Diff: hunk header"
+msgstr "Изменения: заголовок блока"
+
+#: gitk:10165
+msgid "diff hunk header"
+msgstr "заголовок блока изменений"
+
+#: gitk:10169
+msgid "Marked line bg"
+msgstr "Фон выбраной строки"
+
+#: gitk:10171
+msgid "marked line background"
+msgstr "фон выбраной строки"
+
+#: gitk:10175
+msgid "Select bg"
+msgstr "Выберите фон"
+
+#: gitk:10179
+msgid "Fonts: press to choose"
+msgstr "Шрифт: нажмите для выбора"
+
+#: gitk:10181
+msgid "Main font"
+msgstr "Основной шрифт"
+
+#: gitk:10182
+msgid "Diff display font"
+msgstr "Шрифт показа изменений"
+
+#: gitk:10183
+msgid "User interface font"
+msgstr "Шрифт интерфейса"
+
+#: gitk:10210
+#, tcl-format
+msgid "Gitk: choose color for %s"
+msgstr "Gitk: выберите цвет для %s"
+
+#: gitk:10656
+msgid ""
+"Sorry, gitk cannot run with this version of Tcl/Tk.\n"
+" Gitk requires at least Tcl/Tk 8.4."
+msgstr ""
+"К сожалению gitk не может работать с этой версий Tcl/Tk.\n"
+"Требуется как минимум Tcl/Tk 8.4."
+
+#: gitk:10773
+msgid "Cannot find a git repository here."
+msgstr "Git-репозитарий не найден в текущем каталоге."
+
+#: gitk:10777
+#, tcl-format
+msgid "Cannot find the git directory \"%s\"."
+msgstr "Git-репозитарий \"%s\" не найден."
+
+#: gitk:10824
+#, tcl-format
+msgid "Ambiguous argument '%s': both revision and filename"
+msgstr "Неоднозначный аргумент '%s': существует как версия и имя файла"
+
+#: gitk:10836
+msgid "Bad arguments to gitk:"
+msgstr "Неправильные аргументы для gitk:"
+
+#: gitk:10896
+msgid "Command line"
+msgstr "Командная строка"
+
index 19ae28ef9b5de046e003c02b9258dd576d8064f7..ccda890c0ef1b2d1fb1c451d3f9a10d97817c8f6 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
@@ -208,7 +206,7 @@ not include variables usually directly set during build):
  * $fallback_encoding
    Gitweb assumes this charset if line contains non-UTF-8 characters.
    Fallback decoding is used without error checking, so it can be even
-   'utf-8'. Value mist be valid encodig; see Encoding::Supported(3pm) man
+   'utf-8'. Value must be valid encoding; see Encoding::Supported(3pm) man
    page for a list.   By default 'latin1', aka. 'iso-8859-1'.
  * @diff_opts
    Rename detection options for git-diff and git-diff-tree. By default
@@ -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..3f99361ed03b8d5202cb17e894c65678364fc1ed 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,
@@ -668,10 +688,10 @@ sub evaluate_path_info {
                # extensions. Allowed extensions are both the defined suffix
                # (which includes the initial dot already) and the snapshot
                # format key itself, with a prepended dot
-               while (my ($fmt, %opt) = each %known_snapshot_formats) {
+               while (my ($fmt, $opt) = each %known_snapshot_formats) {
                        my $hash = $refname;
                        my $sfx;
-                       $hash =~ s/(\Q$opt{'suffix'}\E|\Q.$fmt\E)$//;
+                       $hash =~ s/(\Q$opt->{'suffix'}\E|\Q.$fmt\E)$//;
                        next unless $sfx = $1;
                        # a valid suffix was found, so set the snapshot format
                        # and reset the hash parameter
@@ -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/graph.c b/graph.c
index 162a516ee15cca1f8ab118dd41b803a5d76e42ff..06fbeb6c2416d0c1c5fd7df1a12924656592cdc3 100644 (file)
--- a/graph.c
+++ b/graph.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "commit.h"
+#include "color.h"
 #include "graph.h"
 #include "diff.h"
 #include "revision.h"
@@ -34,7 +35,7 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb);
  * newline.  A new graph line will not be printed after the final newline.
  * If the strbuf is empty, no output will be printed.
  *
- * Since the first line will not include the graph ouput, the caller is
+ * Since the first line will not include the graph output, the caller is
  * responsible for printing this line's graph (perhaps via
  * graph_show_commit() or graph_show_oneline()) before calling
  * graph_show_strbuf().
@@ -43,10 +44,6 @@ static void graph_show_strbuf(struct git_graph *graph, struct strbuf const *sb);
 
 /*
  * TODO:
- * - Add colors to the graph.
- *   Pick a color for each column, and print all characters
- *   in that column with the specified color.
- *
  * - Limit the number of columns, similar to the way gitk does.
  *   If we reach more than a specified number of columns, omit
  *   sections of some columns.
@@ -72,9 +69,10 @@ struct column {
         */
        struct commit *commit;
        /*
-        * XXX: Once we add support for colors, struct column could also
-        * contain the color of its branch line.
+        * The color to (optionally) print this column in.  This is an
+        * index into column_colors.
         */
+       unsigned short color;
 };
 
 enum graph_state {
@@ -86,6 +84,41 @@ enum graph_state {
        GRAPH_COLLAPSING
 };
 
+/*
+ * The list of available column colors.
+ */
+static char column_colors[][COLOR_MAXLEN] = {
+       GIT_COLOR_RED,
+       GIT_COLOR_GREEN,
+       GIT_COLOR_YELLOW,
+       GIT_COLOR_BLUE,
+       GIT_COLOR_MAGENTA,
+       GIT_COLOR_CYAN,
+       GIT_COLOR_BOLD GIT_COLOR_RED,
+       GIT_COLOR_BOLD GIT_COLOR_GREEN,
+       GIT_COLOR_BOLD GIT_COLOR_YELLOW,
+       GIT_COLOR_BOLD GIT_COLOR_BLUE,
+       GIT_COLOR_BOLD GIT_COLOR_MAGENTA,
+       GIT_COLOR_BOLD GIT_COLOR_CYAN,
+};
+
+#define COLUMN_COLORS_MAX (ARRAY_SIZE(column_colors))
+
+static const char *column_get_color_code(const struct column *c)
+{
+       return column_colors[c->color];
+}
+
+static void strbuf_write_column(struct strbuf *sb, const struct column *c,
+                               char col_char)
+{
+       if (c->color < COLUMN_COLORS_MAX)
+               strbuf_addstr(sb, column_get_color_code(c));
+       strbuf_addch(sb, col_char);
+       if (c->color < COLUMN_COLORS_MAX)
+               strbuf_addstr(sb, GIT_COLOR_RESET);
+}
+
 struct git_graph {
        /*
         * The commit currently being processed
@@ -185,6 +218,11 @@ struct git_graph {
         * temporary array each time we have to output a collapsing line.
         */
        int *new_mapping;
+       /*
+        * The current default column color being used.  This is
+        * stored as an index into the array column_colors.
+        */
+       unsigned short default_column_color;
 };
 
 struct git_graph *graph_init(struct rev_info *opt)
@@ -201,6 +239,7 @@ struct git_graph *graph_init(struct rev_info *opt)
        graph->num_columns = 0;
        graph->num_new_columns = 0;
        graph->mapping_size = 0;
+       graph->default_column_color = 0;
 
        /*
         * Allocate a reasonably large default number of columns
@@ -312,6 +351,33 @@ static struct commit_list *first_interesting_parent(struct git_graph *graph)
        return next_interesting_parent(graph, parents);
 }
 
+static unsigned short graph_get_current_column_color(const struct git_graph *graph)
+{
+       if (!DIFF_OPT_TST(&graph->revs->diffopt, COLOR_DIFF))
+               return COLUMN_COLORS_MAX;
+       return graph->default_column_color;
+}
+
+/*
+ * Update the graph's default column color.
+ */
+static void graph_increment_column_color(struct git_graph *graph)
+{
+       graph->default_column_color = (graph->default_column_color + 1) %
+               COLUMN_COLORS_MAX;
+}
+
+static unsigned short graph_find_commit_color(const struct git_graph *graph,
+                                             const struct commit *commit)
+{
+       int i;
+       for (i = 0; i < graph->num_columns; i++) {
+               if (graph->columns[i].commit == commit)
+                       return graph->columns[i].color;
+       }
+       return graph_get_current_column_color(graph);
+}
+
 static void graph_insert_into_new_columns(struct git_graph *graph,
                                          struct commit *commit,
                                          int *mapping_index)
@@ -334,6 +400,7 @@ static void graph_insert_into_new_columns(struct git_graph *graph,
         * This commit isn't already in new_columns.  Add it.
         */
        graph->new_columns[graph->num_new_columns].commit = commit;
+       graph->new_columns[graph->num_new_columns].color = graph_find_commit_color(graph, commit);
        graph->mapping[*mapping_index] = graph->num_new_columns;
        *mapping_index += 2;
        graph->num_new_columns++;
@@ -445,6 +512,12 @@ static void graph_update_columns(struct git_graph *graph)
                        for (parent = first_interesting_parent(graph);
                             parent;
                             parent = next_interesting_parent(graph, parent)) {
+                               /*
+                                * If this is a merge increment the current
+                                * color.
+                                */
+                               if (graph->num_parents > 1)
+                                       graph_increment_column_color(graph);
                                graph_insert_into_new_columns(graph,
                                                              parent->item,
                                                              &mapping_idx);
@@ -560,7 +633,8 @@ static int graph_is_mapping_correct(struct git_graph *graph)
        return 1;
 }
 
-static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
+static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb,
+                                  int chars_written)
 {
        /*
         * Add additional spaces to the end of the strbuf, so that all
@@ -570,10 +644,10 @@ static void graph_pad_horizontally(struct git_graph *graph, struct strbuf *sb)
         * aligned for the entire commit.
         */
        int extra;
-       if (sb->len >= graph->width)
+       if (chars_written >= graph->width)
                return;
 
-       extra = graph->width - sb->len;
+       extra = graph->width - chars_written;
        strbuf_addf(sb, "%*s", (int) extra, "");
 }
 
@@ -596,10 +670,11 @@ static void graph_output_padding_line(struct git_graph *graph,
         * Output a padding row, that leaves all branch lines unchanged
         */
        for (i = 0; i < graph->num_new_columns; i++) {
-               strbuf_addstr(sb, "| ");
+               strbuf_write_column(sb, &graph->new_columns[i], '|');
+               strbuf_addch(sb, ' ');
        }
 
-       graph_pad_horizontally(graph, sb);
+       graph_pad_horizontally(graph, sb, graph->num_new_columns * 2);
 }
 
 static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
@@ -609,7 +684,7 @@ static void graph_output_skip_line(struct git_graph *graph, struct strbuf *sb)
         * of the graph is missing.
         */
        strbuf_addstr(sb, "...");
-       graph_pad_horizontally(graph, sb);
+       graph_pad_horizontally(graph, sb, 3);
 
        if (graph->num_parents >= 3 &&
            graph->commit_index < (graph->num_columns - 1))
@@ -623,6 +698,7 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
 {
        int num_expansion_rows;
        int i, seen_this;
+       int chars_written;
 
        /*
         * This function formats a row that increases the space around a commit
@@ -645,11 +721,14 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
         * Output the row
         */
        seen_this = 0;
+       chars_written = 0;
        for (i = 0; i < graph->num_columns; i++) {
                struct column *col = &graph->columns[i];
                if (col->commit == graph->commit) {
                        seen_this = 1;
-                       strbuf_addf(sb, "| %*s", graph->expansion_row, "");
+                       strbuf_write_column(sb, col, '|');
+                       strbuf_addf(sb, "%*s", graph->expansion_row, "");
+                       chars_written += 1 + graph->expansion_row;
                } else if (seen_this && (graph->expansion_row == 0)) {
                        /*
                         * This is the first line of the pre-commit output.
@@ -662,17 +741,22 @@ static void graph_output_pre_commit_line(struct git_graph *graph,
                         */
                        if (graph->prev_state == GRAPH_POST_MERGE &&
                            graph->prev_commit_index < i)
-                               strbuf_addstr(sb, "\\ ");
+                               strbuf_write_column(sb, col, '\\');
                        else
-                               strbuf_addstr(sb, "| ");
+                               strbuf_write_column(sb, col, '|');
+                       chars_written++;
                } else if (seen_this && (graph->expansion_row > 0)) {
-                       strbuf_addstr(sb, "\\ ");
+                       strbuf_write_column(sb, col, '\\');
+                       chars_written++;
                } else {
-                       strbuf_addstr(sb, "| ");
+                       strbuf_write_column(sb, col, '|');
+                       chars_written++;
                }
+               strbuf_addch(sb, ' ');
+               chars_written++;
        }
 
-       graph_pad_horizontally(graph, sb);
+       graph_pad_horizontally(graph, sb, chars_written);
 
        /*
         * Increment graph->expansion_row,
@@ -714,10 +798,34 @@ static void graph_output_commit_char(struct git_graph *graph, struct strbuf *sb)
        strbuf_addch(sb, '*');
 }
 
+/*
+ * Draw an octopus merge and return the number of characters written.
+ */
+static int graph_draw_octopus_merge(struct git_graph *graph,
+                                   struct strbuf *sb)
+{
+       /*
+        * Here dashless_commits represents the number of parents
+        * which don't need to have dashes (because their edges fit
+        * neatly under the commit).
+        */
+       const int dashless_commits = 2;
+       int col_num, i;
+       int num_dashes =
+               ((graph->num_parents - dashless_commits) * 2) - 1;
+       for (i = 0; i < num_dashes; i++) {
+               col_num = (i / 2) + dashless_commits;
+               strbuf_write_column(sb, &graph->new_columns[col_num], '-');
+       }
+       col_num = (i / 2) + dashless_commits;
+       strbuf_write_column(sb, &graph->new_columns[col_num], '.');
+       return num_dashes + 1;
+}
+
 static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
 {
        int seen_this = 0;
-       int i, j;
+       int i, chars_written;
 
        /*
         * Output the row containing this commit
@@ -727,7 +835,9 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
         * children that we have already processed.)
         */
        seen_this = 0;
+       chars_written = 0;
        for (i = 0; i <= graph->num_columns; i++) {
+               struct column *col = &graph->columns[i];
                struct commit *col_commit;
                if (i == graph->num_columns) {
                        if (seen_this)
@@ -740,18 +850,14 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
                if (col_commit == graph->commit) {
                        seen_this = 1;
                        graph_output_commit_char(graph, sb);
+                       chars_written++;
 
-                       if (graph->num_parents < 3)
-                               strbuf_addch(sb, ' ');
-                       else {
-                               int num_dashes =
-                                       ((graph->num_parents - 2) * 2) - 1;
-                               for (j = 0; j < num_dashes; j++)
-                                       strbuf_addch(sb, '-');
-                               strbuf_addstr(sb, ". ");
-                       }
+                       if (graph->num_parents > 2)
+                               chars_written += graph_draw_octopus_merge(graph,
+                                                                         sb);
                } else if (seen_this && (graph->num_parents > 2)) {
-                       strbuf_addstr(sb, "\\ ");
+                       strbuf_write_column(sb, col, '\\');
+                       chars_written++;
                } else if (seen_this && (graph->num_parents == 2)) {
                        /*
                         * This is a 2-way merge commit.
@@ -768,15 +874,19 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
                         */
                        if (graph->prev_state == GRAPH_POST_MERGE &&
                            graph->prev_commit_index < i)
-                               strbuf_addstr(sb, "\\ ");
+                               strbuf_write_column(sb, col, '\\');
                        else
-                               strbuf_addstr(sb, "| ");
+                               strbuf_write_column(sb, col, '|');
+                       chars_written++;
                } else {
-                       strbuf_addstr(sb, "| ");
+                       strbuf_write_column(sb, col, '|');
+                       chars_written++;
                }
+               strbuf_addch(sb, ' ');
+               chars_written++;
        }
 
-       graph_pad_horizontally(graph, sb);
+       graph_pad_horizontally(graph, sb, chars_written);
 
        /*
         * Update graph->state
@@ -789,37 +899,75 @@ static void graph_output_commit_line(struct git_graph *graph, struct strbuf *sb)
                graph_update_state(graph, GRAPH_COLLAPSING);
 }
 
+static struct column *find_new_column_by_commit(struct git_graph *graph,
+                                               struct commit *commit)
+{
+       int i;
+       for (i = 0; i < graph->num_new_columns; i++) {
+               if (graph->new_columns[i].commit == commit)
+                       return &graph->new_columns[i];
+       }
+       return 0;
+}
+
 static void graph_output_post_merge_line(struct git_graph *graph, struct strbuf *sb)
 {
        int seen_this = 0;
-       int i, j;
+       int i, j, chars_written;
 
        /*
         * Output the post-merge row
         */
+       chars_written = 0;
        for (i = 0; i <= graph->num_columns; i++) {
+               struct column *col = &graph->columns[i];
                struct commit *col_commit;
                if (i == graph->num_columns) {
                        if (seen_this)
                                break;
                        col_commit = graph->commit;
                } else {
-                       col_commit = graph->columns[i].commit;
+                       col_commit = col->commit;
                }
 
                if (col_commit == graph->commit) {
+                       /*
+                        * Since the current commit is a merge find
+                        * the columns for the parent commits in
+                        * new_columns and use those to format the
+                        * edges.
+                        */
+                       struct commit_list *parents = NULL;
+                       struct column *par_column;
                        seen_this = 1;
-                       strbuf_addch(sb, '|');
-                       for (j = 0; j < graph->num_parents - 1; j++)
-                               strbuf_addstr(sb, "\\ ");
+                       parents = first_interesting_parent(graph);
+                       assert(parents);
+                       par_column = find_new_column_by_commit(graph, parents->item);
+                       assert(par_column);
+
+                       strbuf_write_column(sb, par_column, '|');
+                       chars_written++;
+                       for (j = 0; j < graph->num_parents - 1; j++) {
+                               parents = next_interesting_parent(graph, parents);
+                               assert(parents);
+                               par_column = find_new_column_by_commit(graph, parents->item);
+                               assert(par_column);
+                               strbuf_write_column(sb, par_column, '\\');
+                               strbuf_addch(sb, ' ');
+                       }
+                       chars_written += j * 2;
                } else if (seen_this) {
-                       strbuf_addstr(sb, "\\ ");
+                       strbuf_write_column(sb, col, '\\');
+                       strbuf_addch(sb, ' ');
+                       chars_written += 2;
                } else {
-                       strbuf_addstr(sb, "| ");
+                       strbuf_write_column(sb, col, '|');
+                       strbuf_addch(sb, ' ');
+                       chars_written += 2;
                }
        }
 
-       graph_pad_horizontally(graph, sb);
+       graph_pad_horizontally(graph, sb, chars_written);
 
        /*
         * Update graph->state
@@ -912,12 +1060,12 @@ static void graph_output_collapsing_line(struct git_graph *graph, struct strbuf
                if (target < 0)
                        strbuf_addch(sb, ' ');
                else if (target * 2 == i)
-                       strbuf_addch(sb, '|');
+                       strbuf_write_column(sb, &graph->new_columns[target], '|');
                else
-                       strbuf_addch(sb, '/');
+                       strbuf_write_column(sb, &graph->new_columns[target], '/');
        }
 
-       graph_pad_horizontally(graph, sb);
+       graph_pad_horizontally(graph, sb, graph->mapping_size);
 
        /*
         * Swap mapping and new_mapping
@@ -979,9 +1127,10 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
         * children that we have already processed.)
         */
        for (i = 0; i < graph->num_columns; i++) {
-               struct commit *col_commit = graph->columns[i].commit;
+               struct column *col = &graph->columns[i];
+               struct commit *col_commit = col->commit;
                if (col_commit == graph->commit) {
-                       strbuf_addch(sb, '|');
+                       strbuf_write_column(sb, col, '|');
 
                        if (graph->num_parents < 3)
                                strbuf_addch(sb, ' ');
@@ -991,11 +1140,12 @@ static void graph_padding_line(struct git_graph *graph, struct strbuf *sb)
                                        strbuf_addch(sb, ' ');
                        }
                } else {
-                       strbuf_addstr(sb, "| ");
+                       strbuf_write_column(sb, col, '|');
+                       strbuf_addch(sb, ' ');
                }
        }
 
-       graph_pad_horizontally(graph, sb);
+       graph_pad_horizontally(graph, sb, graph->num_columns);
 
        /*
         * Update graph->prev_state since we have output a padding line
diff --git a/grep.c b/grep.c
index 704facf996657433f333a58381836e7e5df22bc6..a649f063cf28baa5b0ddce72d7d6e4d9fca6f360 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -28,9 +28,27 @@ 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;
+
+       p->word_regexp = opt->word_regexp;
+
+       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];
@@ -161,8 +179,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;
@@ -177,7 +194,8 @@ void compile_grep_patterns(struct grep_opt *opt)
         * A classic recursive descent parser would do.
         */
        p = opt->pattern_list;
-       opt->pattern_expression = compile_pattern_expr(&p);
+       if (p)
+               opt->pattern_expression = compile_pattern_expr(&p);
        if (p)
                die("incomplete pattern expression: %s", p->pattern);
 }
@@ -238,18 +256,6 @@ static int word_char(char ch)
        return isalnum(ch) || ch == '_';
 }
 
-static void show_line(struct grep_opt *opt, const char *bol, const char *eol,
-                     const char *name, unsigned lno, char sign)
-{
-       if (opt->null_following_name)
-               sign = '\0';
-       if (opt->pathname)
-               printf("%s%c", name, sign);
-       if (opt->linenum)
-               printf("%d%c", lno, sign);
-       printf("%.*s\n", (int)(eol-bol), bol);
-}
-
 static void show_name(struct grep_opt *opt, const char *name)
 {
        printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
@@ -293,12 +299,13 @@ static struct {
        { "committer ", 10 },
 };
 
-static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol, char *eol, enum grep_context ctx)
+static int match_one_pattern(struct grep_pat *p, char *bol, char *eol,
+                            enum grep_context ctx,
+                            regmatch_t *pmatch, int eflags)
 {
        int hit = 0;
-       int at_true_bol = 1;
        int saved_ch = 0;
-       regmatch_t pmatch[10];
+       const char *start = bol;
 
        if ((p->token != GREP_PATTERN) &&
            ((p->token == GREP_PATTERN_HEAD) != (ctx == GREP_CONTEXT_HEAD)))
@@ -317,16 +324,12 @@ static int match_one_pattern(struct grep_opt *opt, struct grep_pat *p, char *bol
        }
 
  again:
-       if (!opt->fixed) {
-               regex_t *exp = &p->regexp;
-               hit = !regexec(exp, bol, ARRAY_SIZE(pmatch),
-                              pmatch, 0);
-       }
-       else {
+       if (p->fixed)
                hit = !fixmatch(p->pattern, bol, pmatch);
-       }
+       else
+               hit = !regexec(&p->regexp, bol, 1, pmatch, eflags);
 
-       if (hit && opt->word_regexp) {
+       if (hit && p->word_regexp) {
                if ((pmatch[0].rm_so < 0) ||
                    (eol - bol) <= pmatch[0].rm_so ||
                    (pmatch[0].rm_eo < 0) ||
@@ -339,7 +342,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])) )
@@ -351,55 +354,57 @@ 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)
                *eol = saved_ch;
+       if (hit) {
+               pmatch[0].rm_so += bol - start;
+               pmatch[0].rm_eo += bol - start;
+       }
        return hit;
 }
 
-static int match_expr_eval(struct grep_opt *o,
-                          struct grep_expr *x,
-                          char *bol, char *eol,
-                          enum grep_context ctx,
-                          int collect_hits)
+static int match_expr_eval(struct grep_expr *x, char *bol, char *eol,
+                          enum grep_context ctx, int collect_hits)
 {
        int h = 0;
+       regmatch_t match;
 
        if (!x)
                die("Not a valid grep expression");
        switch (x->node) {
        case GREP_NODE_ATOM:
-               h = match_one_pattern(o, x->u.atom, bol, eol, ctx);
+               h = match_one_pattern(x->u.atom, bol, eol, ctx, &match, 0);
                break;
        case GREP_NODE_NOT:
-               h = !match_expr_eval(o, x->u.unary, bol, eol, ctx, 0);
+               h = !match_expr_eval(x->u.unary, bol, eol, ctx, 0);
                break;
        case GREP_NODE_AND:
-               if (!collect_hits)
-                       return (match_expr_eval(o, x->u.binary.left,
-                                               bol, eol, ctx, 0) &&
-                               match_expr_eval(o, x->u.binary.right,
-                                               bol, eol, ctx, 0));
-               h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
-               h &= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 0);
+               if (!match_expr_eval(x->u.binary.left, bol, eol, ctx, 0))
+                       return 0;
+               h = match_expr_eval(x->u.binary.right, bol, eol, ctx, 0);
                break;
        case GREP_NODE_OR:
                if (!collect_hits)
-                       return (match_expr_eval(o, x->u.binary.left,
+                       return (match_expr_eval(x->u.binary.left,
                                                bol, eol, ctx, 0) ||
-                               match_expr_eval(o, x->u.binary.right,
+                               match_expr_eval(x->u.binary.right,
                                                bol, eol, ctx, 0));
-               h = match_expr_eval(o, x->u.binary.left, bol, eol, ctx, 0);
+               h = match_expr_eval(x->u.binary.left, bol, eol, ctx, 0);
                x->u.binary.left->hit |= h;
-               h |= match_expr_eval(o, x->u.binary.right, bol, eol, ctx, 1);
+               h |= match_expr_eval(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;
@@ -410,24 +415,104 @@ static int match_expr(struct grep_opt *opt, char *bol, char *eol,
                      enum grep_context ctx, int collect_hits)
 {
        struct grep_expr *x = opt->pattern_expression;
-       return match_expr_eval(opt, x, bol, eol, ctx, collect_hits);
+       return match_expr_eval(x, bol, eol, ctx, collect_hits);
 }
 
 static int match_line(struct grep_opt *opt, char *bol, char *eol,
                      enum grep_context ctx, int collect_hits)
 {
        struct grep_pat *p;
+       regmatch_t match;
+
        if (opt->extended)
                return match_expr(opt, bol, eol, ctx, collect_hits);
 
        /* we do not call with collect_hits without being extended */
        for (p = opt->pattern_list; p; p = p->next) {
-               if (match_one_pattern(opt, p, bol, eol, ctx))
+               if (match_one_pattern(p, bol, eol, ctx, &match, 0))
                        return 1;
        }
        return 0;
 }
 
+static int match_next_pattern(struct grep_pat *p, char *bol, char *eol,
+                             enum grep_context ctx,
+                             regmatch_t *pmatch, int eflags)
+{
+       regmatch_t match;
+
+       if (!match_one_pattern(p, bol, eol, ctx, &match, eflags))
+               return 0;
+       if (match.rm_so < 0 || match.rm_eo < 0)
+               return 0;
+       if (pmatch->rm_so >= 0 && pmatch->rm_eo >= 0) {
+               if (match.rm_so > pmatch->rm_so)
+                       return 1;
+               if (match.rm_so == pmatch->rm_so && match.rm_eo < pmatch->rm_eo)
+                       return 1;
+       }
+       pmatch->rm_so = match.rm_so;
+       pmatch->rm_eo = match.rm_eo;
+       return 1;
+}
+
+static int next_match(struct grep_opt *opt, char *bol, char *eol,
+                     enum grep_context ctx, regmatch_t *pmatch, int eflags)
+{
+       struct grep_pat *p;
+       int hit = 0;
+
+       pmatch->rm_so = pmatch->rm_eo = -1;
+       if (bol < eol) {
+               for (p = opt->pattern_list; p; p = p->next) {
+                       switch (p->token) {
+                       case GREP_PATTERN: /* atom */
+                       case GREP_PATTERN_HEAD:
+                       case GREP_PATTERN_BODY:
+                               hit |= match_next_pattern(p, bol, eol, ctx,
+                                                         pmatch, eflags);
+                               break;
+                       default:
+                               break;
+                       }
+               }
+       }
+       return hit;
+}
+
+static void show_line(struct grep_opt *opt, char *bol, char *eol,
+                     const char *name, unsigned lno, char sign)
+{
+       int rest = eol - bol;
+
+       if (opt->null_following_name)
+               sign = '\0';
+       if (opt->pathname)
+               printf("%s%c", name, sign);
+       if (opt->linenum)
+               printf("%d%c", lno, sign);
+       if (opt->color) {
+               regmatch_t match;
+               enum grep_context ctx = GREP_CONTEXT_BODY;
+               int ch = *eol;
+               int eflags = 0;
+
+               *eol = '\0';
+               while (next_match(opt, bol, eol, ctx, &match, eflags)) {
+                       printf("%.*s%s%.*s%s",
+                              (int)match.rm_so, bol,
+                              opt->color_match,
+                              (int)(match.rm_eo - match.rm_so), bol + match.rm_so,
+                              GIT_COLOR_RESET);
+                       bol += match.rm_eo;
+                       rest -= match.rm_eo;
+                       eflags = REG_NOTBOL;
+               }
+               *eol = ch;
+       }
+       printf("%.*s\n", rest, bol);
+}
+
 static int grep_buffer_1(struct grep_opt *opt, const char *name,
                         char *buf, unsigned long size, int collect_hits)
 {
diff --git a/grep.h b/grep.h
index 45a222d904b5898758c3c84f6ef1e0119c81f6b2..a67005de62d1442e7ba6a8dc27320225a0d55819 100644 (file)
--- a/grep.h
+++ b/grep.h
@@ -1,5 +1,6 @@
 #ifndef GREP_H
 #define GREP_H
+#include "color.h"
 
 enum grep_pat_token {
        GREP_PATTERN,
@@ -30,6 +31,8 @@ struct grep_pat {
        const char *pattern;
        enum grep_header_field field;
        regex_t regexp;
+       unsigned fixed:1;
+       unsigned word_regexp:1;
 };
 
 enum grep_expr_node {
@@ -75,6 +78,9 @@ struct grep_opt {
        unsigned relative:1;
        unsigned pathname:1;
        unsigned null_following_name:1;
+       int color;
+       char color_match[COLOR_MAXLEN];
+       const char *color_external;
        int regflags;
        unsigned pre_context;
        unsigned post_context;
index adfd5336a31412681415c47d2420aba8ed299ae3..ebb3bedb074202a29e2356845012bd1ffae0dc19 100644 (file)
@@ -8,6 +8,7 @@
 #include "blob.h"
 #include "quote.h"
 #include "parse-options.h"
+#include "exec_cmd.h"
 
 static void hash_fd(int fd, const char *type, int write_object, const char *path)
 {
@@ -81,6 +82,8 @@ int main(int argc, const char **argv)
 
        type = blob_type;
 
+       git_extract_argv0_path(argv[0]);
+
        argc = parse_options(argc, argv, hash_object_options, hash_object_usage, 0);
 
        if (write_object) {
index cb5bf95a736c571259aef51884cc20e4169bf707..e16a0ad3f97ef49930a599d3a800a0c7fad317e0 100644 (file)
@@ -10,6 +10,7 @@
 #include "exec_cmd.h"
 #include "remote.h"
 #include "list-objects.h"
+#include "sigchain.h"
 
 #include <expat.h>
 
@@ -96,7 +97,7 @@ struct repo
        struct remote_lock *locks;
 };
 
-static struct repo *remote;
+static struct repo *repo;
 
 enum transfer_state {
        NEED_FETCH,
@@ -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,73 @@ 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 char *xml_entities(char *s)
+{
+       struct strbuf buf = STRBUF_INIT;
+       while (*s) {
+               size_t len = strcspn(s, "\"<>&");
+               strbuf_add(&buf, s, len);
+               s += len;
+               switch (*s) {
+               case '"':
+                       strbuf_addstr(&buf, "&quot;");
+                       break;
+               case '<':
+                       strbuf_addstr(&buf, "&lt;");
+                       break;
+               case '>':
+                       strbuf_addstr(&buf, "&gt;");
+                       break;
+               case '&':
+                       strbuf_addstr(&buf, "&amp;");
+                       break;
+               }
+               s++;
+       }
+       return strbuf_detach(&buf, NULL);
+}
+
+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 +258,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 +301,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;
@@ -238,9 +315,9 @@ static void start_fetch_loose(struct transfer_request *request)
                 "%s.temp", filename);
 
        snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename);
-       unlink(prevfile);
+       unlink_or_warn(prevfile);
        rename(request->tmpfile, prevfile);
-       unlink(request->tmpfile);
+       unlink_or_warn(request->tmpfile);
 
        if (request->local_fileno != -1)
                error("fd leakage in start: %d", request->local_fileno);
@@ -273,17 +350,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(repo->url, hex, 0);
+       request->url = xstrdup(url);
 
        /* If a previous temp file is present, process what was already
           fetched. */
@@ -304,7 +372,7 @@ static void start_fetch_loose(struct transfer_request *request)
                } while (prev_read > 0);
                close(prevlocal);
        }
-       unlink(prevfile);
+       unlink_or_warn(prevfile);
 
        /* Reset inflate/SHA1 if there was an error reading the previous temp
           file; also rewind to the beginning of the local file. */
@@ -347,7 +415,7 @@ static void start_fetch_loose(struct transfer_request *request)
        request->state = RUN_FETCH_LOOSE;
        if (!start_active_slot(slot)) {
                fprintf(stderr, "Unable to start GET request\n");
-               remote->can_update_info_refs = 0;
+               repo->can_update_info_refs = 0;
                release_request(request);
        }
 }
@@ -356,16 +424,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(repo->url, hex, 1);
 
        slot = get_active_slot();
        slot->callback_func = process_response;
@@ -400,10 +460,10 @@ static void start_fetch_packed(struct transfer_request *request)
        struct transfer_request *check_request = request_queue_head;
        struct active_request_slot *slot;
 
-       target = find_sha1_pack(request->obj->sha1, remote->packs);
+       target = find_sha1_pack(request->obj->sha1, repo->packs);
        if (!target) {
                fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", sha1_to_hex(request->obj->sha1));
-               remote->can_update_info_refs = 0;
+               repo->can_update_info_refs = 0;
                release_request(request);
                return;
        }
@@ -416,9 +476,9 @@ static void start_fetch_packed(struct transfer_request *request)
        snprintf(request->tmpfile, sizeof(request->tmpfile),
                 "%s.temp", filename);
 
-       url = xmalloc(strlen(remote->url) + 64);
+       url = xmalloc(strlen(repo->url) + 64);
        sprintf(url, "%sobjects/pack/pack-%s.pack",
-               remote->url, sha1_to_hex(target->sha1));
+               repo->url, sha1_to_hex(target->sha1));
 
        /* Make sure there isn't another open request for this pack */
        while (check_request) {
@@ -435,7 +495,7 @@ static void start_fetch_packed(struct transfer_request *request)
        if (!packfile) {
                fprintf(stderr, "Unable to open local file %s for pack",
                        request->tmpfile);
-               remote->can_update_info_refs = 0;
+               repo->can_update_info_refs = 0;
                free(url);
                return;
        }
@@ -471,7 +531,7 @@ static void start_fetch_packed(struct transfer_request *request)
        request->state = RUN_FETCH_PACKED;
        if (!start_active_slot(slot)) {
                fprintf(stderr, "Unable to start GET request\n");
-               remote->can_update_info_refs = 0;
+               repo->can_update_info_refs = 0;
                release_request(request);
        }
 }
@@ -480,7 +540,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 +579,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, repo->url, hex, 0);
+       request->dest = strbuf_detach(&buf, NULL);
+
+       append_remote_object_url(&buf, repo->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;
@@ -541,6 +593,10 @@ static void start_put(struct transfer_request *request)
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer);
+#endif
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
        curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
@@ -588,18 +644,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,14 +672,13 @@ static int refresh_lock(struct remote_lock *lock)
 
        lock->refreshing = 0;
        curl_slist_free_all(dav_headers);
-       free(if_header);
 
        return rc;
 }
 
 static void check_locks(void)
 {
-       struct remote_lock *lock = remote->locks;
+       struct remote_lock *lock = repo->locks;
        time_t current_time = time(NULL);
        int time_remaining;
 
@@ -729,25 +778,24 @@ static void finish_request(struct transfer_request *request)
                        aborted = 1;
                }
        } else if (request->state == RUN_FETCH_LOOSE) {
-               fchmod(request->local_fileno, 0444);
                close(request->local_fileno); request->local_fileno = -1;
 
                if (request->curl_result != CURLE_OK &&
                    request->http_code != 416) {
                        if (stat(request->tmpfile, &st) == 0) {
                                if (st.st_size == 0)
-                                       unlink(request->tmpfile);
+                                       unlink_or_warn(request->tmpfile);
                        }
                } else {
                        if (request->http_code == 416)
-                               fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n");
+                               warning("requested range invalid; we may already have all the data.");
 
                        git_inflate_end(&request->stream);
                        git_SHA1_Final(request->real_sha1, &request->c);
                        if (request->zret != Z_STREAM_END) {
-                               unlink(request->tmpfile);
+                               unlink_or_warn(request->tmpfile);
                        } else if (hashcmp(request->obj->sha1, request->real_sha1)) {
-                               unlink(request->tmpfile);
+                               unlink_or_warn(request->tmpfile);
                        } else {
                                request->rename =
                                        move_temp_to_file(
@@ -769,7 +817,7 @@ static void finish_request(struct transfer_request *request)
                if (request->curl_result != CURLE_OK) {
                        fprintf(stderr, "Unable to get pack file %s\n%s",
                                request->url, curl_errorstr);
-                       remote->can_update_info_refs = 0;
+                       repo->can_update_info_refs = 0;
                } else {
                        off_t pack_size = ftell(request->local_stream);
 
@@ -779,7 +827,7 @@ static void finish_request(struct transfer_request *request)
                                               request->filename)) {
                                target = (struct packed_git *)request->userData;
                                target->pack_size = pack_size;
-                               lst = &remote->packs;
+                               lst = &repo->packs;
                                while (*lst != target)
                                        lst = &((*lst)->next);
                                *lst = (*lst)->next;
@@ -787,7 +835,7 @@ static void finish_request(struct transfer_request *request)
                                if (!verify_pack(target))
                                        install_packed_git(target);
                                else
-                                       remote->can_update_info_refs = 0;
+                                       repo->can_update_info_refs = 0;
                        }
                }
                release_request(request);
@@ -797,7 +845,7 @@ static void finish_request(struct transfer_request *request)
 #ifdef USE_CURL_MULTI
 static int fill_active_slot(void *unused)
 {
-       struct transfer_request *request = request_queue_head;
+       struct transfer_request *request;
 
        if (aborted)
                return 0;
@@ -870,7 +918,7 @@ static int add_send_request(struct object *obj, struct remote_lock *lock)
                get_remote_object_list(obj->sha1[0]);
        if (obj->flags & (REMOTE | PUSHING))
                return 0;
-       target = find_sha1_pack(obj->sha1, remote->packs);
+       target = find_sha1_pack(obj->sha1, repo->packs);
        if (target) {
                obj->flags |= REMOTE;
                return 0;
@@ -911,8 +959,8 @@ static int fetch_index(unsigned char *sha1)
        struct slot_results results;
 
        /* Don't use the index if the pack isn't there */
-       url = xmalloc(strlen(remote->url) + 64);
-       sprintf(url, "%sobjects/pack/pack-%s.pack", remote->url, hex);
+       url = xmalloc(strlen(repo->url) + 64);
+       sprintf(url, "%sobjects/pack/pack-%s.pack", repo->url, hex);
        slot = get_active_slot();
        slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
@@ -937,7 +985,7 @@ static int fetch_index(unsigned char *sha1)
        if (push_verbosely)
                fprintf(stderr, "Getting index for pack %s\n", hex);
 
-       sprintf(url, "%sobjects/pack/pack-%s.idx", remote->url, hex);
+       sprintf(url, "%sobjects/pack/pack-%s.idx", repo->url, hex);
 
        filename = sha1_pack_index_name(sha1);
        snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename);
@@ -999,8 +1047,8 @@ static int setup_index(unsigned char *sha1)
                return -1;
 
        new_pack = parse_pack_index(sha1);
-       new_pack->next = remote->packs;
-       remote->packs = new_pack;
+       new_pack->next = repo->packs;
+       repo->packs = new_pack;
        return 0;
 }
 
@@ -1018,8 +1066,8 @@ static int fetch_indices(void)
        if (push_verbosely)
                fprintf(stderr, "Getting pack list\n");
 
-       url = xmalloc(strlen(remote->url) + 20);
-       sprintf(url, "%sobjects/info/packs", remote->url);
+       url = xmalloc(strlen(repo->url) + 20);
+       sprintf(url, "%sobjects/info/packs", repo->url);
 
        slot = get_active_slot();
        slot->results = &results;
@@ -1111,6 +1159,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 +1173,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);
                }
        }
 }
@@ -1194,12 +1251,13 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        struct remote_lock *lock = NULL;
        struct curl_slist *dav_headers = NULL;
        struct xml_ctx ctx;
+       char *escaped;
 
-       url = xmalloc(strlen(remote->url) + strlen(path) + 1);
-       sprintf(url, "%s%s", remote->url, path);
+       url = xmalloc(strlen(repo->url) + strlen(path) + 1);
+       sprintf(url, "%s%s", repo->url, path);
 
        /* Make sure leading directories exist for the remote ref */
-       ep = strchr(url + strlen(remote->url) + 1, '/');
+       ep = strchr(url + strlen(repo->url) + 1, '/');
        while (ep) {
                char saved_character = ep[1];
                ep[1] = '\0';
@@ -1228,7 +1286,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
                ep = strchr(ep + 1, '/');
        }
 
-       strbuf_addf(&out_buffer.buf, LOCK_REQUEST, git_default_email);
+       escaped = xml_entities(git_default_email);
+       strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped);
+       free(escaped);
 
        sprintf(timeout_header, "Timeout: Second-%ld", timeout);
        dav_headers = curl_slist_append(dav_headers, timeout_header);
@@ -1239,6 +1299,10 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
@@ -1291,8 +1355,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        } else {
                lock->url = url;
                lock->start_time = time(NULL);
-               lock->next = remote->locks;
-               remote->locks = lock;
+               lock->next = repo->locks;
+               repo->locks = lock;
        }
 
        return lock;
@@ -1302,15 +1366,11 @@ 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 remote_lock *prev = repo->locks;
+       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,10 +1391,9 @@ 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;
+       if (repo->locks == lock) {
+               repo->locks = lock->next;
        } else {
                while (prev && prev->next != lock)
                        prev = prev->next;
@@ -1352,19 +1411,20 @@ static int unlock_remote(struct remote_lock *lock)
 
 static void remove_locks(void)
 {
-       struct remote_lock *lock = remote->locks;
+       struct remote_lock *lock = repo->locks;
 
        fprintf(stderr, "Removing remote locks...\n");
        while (lock) {
+               struct remote_lock *next = lock->next;
                unlock_remote(lock);
-               lock = lock->next;
+               lock = next;
        }
 }
 
 static void remove_locks_on_signal(int signo)
 {
        remove_locks();
-       signal(signo, SIG_DFL);
+       sigchain_pop(signo);
        raise(signo);
 }
 
@@ -1434,7 +1494,7 @@ static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
                                }
                        }
                        if (path) {
-                               path += remote->path_len;
+                               path += repo->path_len;
                                ls->dentry_name = xstrdup(path);
                        }
                } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
@@ -1457,7 +1517,7 @@ static void remote_ls(const char *path, int flags,
                      void (*userFunc)(struct remote_ls_ctx *ls),
                      void *userData)
 {
-       char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       char *url = xmalloc(strlen(repo->url) + strlen(path) + 1);
        struct active_request_slot *slot;
        struct slot_results results;
        struct strbuf in_buffer = STRBUF_INIT;
@@ -1473,7 +1533,7 @@ static void remote_ls(const char *path, int flags,
        ls.userData = userData;
        ls.userFunc = userFunc;
 
-       sprintf(url, "%s%s", remote->url, path);
+       sprintf(url, "%s%s", repo->url, path);
 
        strbuf_addf(&out_buffer.buf, PROPFIND_ALL_REQUEST);
 
@@ -1485,6 +1545,10 @@ static void remote_ls(const char *path, int flags,
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_URL, url);
@@ -1550,8 +1614,11 @@ static int locking_available(void)
        struct curl_slist *dav_headers = NULL;
        struct xml_ctx ctx;
        int lock_flags = 0;
+       char *escaped;
 
-       strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, remote->url);
+       escaped = xml_entities(repo->url);
+       strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, escaped);
+       free(escaped);
 
        dav_headers = curl_slist_append(dav_headers, "Depth: 0");
        dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
@@ -1561,9 +1628,13 @@ static int locking_available(void)
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
-       curl_easy_setopt(slot->curl, CURLOPT_URL, remote->url);
+       curl_easy_setopt(slot->curl, CURLOPT_URL, repo->url);
        curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
@@ -1593,16 +1664,16 @@ static int locking_available(void)
                        }
                        XML_ParserFree(parser);
                        if (!lock_flags)
-                               error("Error: no DAV locking support on %s",
-                                     remote->url);
+                               error("no DAV locking support on %s",
+                                     repo->url);
 
                } else {
                        error("Cannot access URL %s, return code %d",
-                             remote->url, results.curl_result);
+                             repo->url, results.curl_result);
                        lock_flags = 0;
                }
        } else {
-               error("Unable to start PROPFIND request on %s", remote->url);
+               error("Unable to start PROPFIND request on %s", repo->url);
        }
 
        strbuf_release(&out_buffer.buf);
@@ -1731,13 +1802,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));
 
@@ -1746,6 +1814,10 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
        curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
        curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+       curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
@@ -1756,7 +1828,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 +1837,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;
        }
@@ -1774,21 +1844,8 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
        return 1;
 }
 
-static struct ref *local_refs, **local_tail;
 static struct ref *remote_refs, **remote_tail;
 
-static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct ref *ref;
-       int len = strlen(refname) + 1;
-       ref = xcalloc(1, sizeof(*ref) + len);
-       hashcpy(ref->new_sha1, sha1);
-       memcpy(ref->name, refname, len);
-       *local_tail = ref;
-       local_tail = &ref->next;
-       return 0;
-}
-
 static void one_remote_ref(char *refname)
 {
        struct ref *ref;
@@ -1796,10 +1853,10 @@ static void one_remote_ref(char *refname)
 
        ref = alloc_ref(refname);
 
-       if (http_fetch_ref(remote->url, ref) != 0) {
+       if (http_fetch_ref(repo->url, ref) != 0) {
                fprintf(stderr,
                        "Unable to fetch ref %s from %s\n",
-                       refname, remote->url);
+                       refname, repo->url);
                free(ref);
                return;
        }
@@ -1808,7 +1865,7 @@ static void one_remote_ref(char *refname)
         * Fetch a copy of the object if it doesn't exist locally - it
         * may be required for updating server info later.
         */
-       if (remote->can_update_info_refs && !has_sha1_file(ref->old_sha1)) {
+       if (repo->can_update_info_refs && !has_sha1_file(ref->old_sha1)) {
                obj = lookup_unknown_object(ref->old_sha1);
                if (obj) {
                        fprintf(stderr, "  fetch %s for %s\n",
@@ -1821,12 +1878,6 @@ static void one_remote_ref(char *refname)
        remote_tail = &ref->next;
 }
 
-static void get_local_heads(void)
-{
-       local_tail = &local_refs;
-       for_each_ref(one_local_ref, NULL);
-}
-
 static void get_dav_remote_heads(void)
 {
        remote_tail = &remote_refs;
@@ -1844,55 +1895,6 @@ static int is_zero_sha1(const unsigned char *sha1)
        return 1;
 }
 
-static void unmark_and_free(struct commit_list *list, unsigned int mark)
-{
-       while (list) {
-               struct commit_list *temp = list;
-               temp->item->object.flags &= ~mark;
-               list = temp->next;
-               free(temp);
-       }
-}
-
-static int ref_newer(const unsigned char *new_sha1,
-                    const unsigned char *old_sha1)
-{
-       struct object *o;
-       struct commit *old, *new;
-       struct commit_list *list, *used;
-       int found = 0;
-
-       /* Both new and old must be commit-ish and new is descendant of
-        * old.  Otherwise we require --force.
-        */
-       o = deref_tag(parse_object(old_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       old = (struct commit *) o;
-
-       o = deref_tag(parse_object(new_sha1), NULL, 0);
-       if (!o || o->type != OBJ_COMMIT)
-               return 0;
-       new = (struct commit *) o;
-
-       if (parse_commit(new) < 0)
-               return 0;
-
-       used = list = NULL;
-       commit_list_insert(new, &list);
-       while (list) {
-               new = pop_most_recent_commit(&list, TMP_MARK);
-               commit_list_insert(new, &used);
-               if (new == old) {
-                       found = 1;
-                       break;
-               }
-       }
-       unmark_and_free(list, TMP_MARK);
-       unmark_and_free(used, TMP_MARK);
-       return found;
-}
-
 static void add_remote_info_ref(struct remote_ls_ctx *ls)
 {
        struct strbuf *buf = (struct strbuf *)ls->userData;
@@ -1903,10 +1905,10 @@ static void add_remote_info_ref(struct remote_ls_ctx *ls)
 
        ref = alloc_ref(ls->dentry_name);
 
-       if (http_fetch_ref(remote->url, ref) != 0) {
+       if (http_fetch_ref(repo->url, ref) != 0) {
                fprintf(stderr,
                        "Unable to fetch ref %s from %s\n",
-                       ls->dentry_name, remote->url);
+                       ls->dentry_name, repo->url);
                aborted = 1;
                free(ref);
                return;
@@ -1948,21 +1950,22 @@ 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;
                curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer);
                curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len);
                curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+               curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+               curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &buffer);
+#endif
                curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
                curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
                curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
@@ -1978,19 +1981,18 @@ static void update_remote_info_refs(struct remote_lock *lock)
                                        results.curl_result, results.http_code);
                        }
                }
-               free(if_header);
        }
        strbuf_release(&buffer.buf);
 }
 
 static int remote_exists(const char *path)
 {
-       char *url = xmalloc(strlen(remote->url) + strlen(path) + 1);
+       char *url = xmalloc(strlen(repo->url) + strlen(path) + 1);
        struct active_request_slot *slot;
        struct slot_results results;
        int ret = -1;
 
-       sprintf(url, "%s%s", remote->url, path);
+       sprintf(url, "%s%s", repo->url, path);
 
        slot = get_active_slot();
        slot->results = &results;
@@ -2020,8 +2022,8 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
        struct active_request_slot *slot;
        struct slot_results results;
 
-       url = xmalloc(strlen(remote->url) + strlen(path) + 1);
-       sprintf(url, "%s%s", remote->url, path);
+       url = xmalloc(strlen(repo->url) + strlen(path) + 1);
+       sprintf(url, "%s%s", repo->url, path);
 
        slot = get_active_slot();
        slot->results = &results;
@@ -2136,7 +2138,7 @@ static int delete_remote_branch(char *pattern, int force)
                                     "of your current HEAD.\n"
                                     "If you are sure you want to delete it,"
                                     " run:\n\t'git http-push -D %s %s'",
-                                    remote_ref->name, remote->url, pattern);
+                                    remote_ref->name, repo->url, pattern);
                }
        }
 
@@ -2144,8 +2146,8 @@ static int delete_remote_branch(char *pattern, int force)
        fprintf(stderr, "Removing remote branch '%s'\n", remote_ref->name);
        if (dry_run)
                return 0;
-       url = xmalloc(strlen(remote->url) + strlen(remote_ref->name) + 1);
-       sprintf(url, "%s%s", remote->url, remote_ref->name);
+       url = xmalloc(strlen(repo->url) + strlen(remote_ref->name) + 1);
+       sprintf(url, "%s%s", repo->url, remote_ref->name);
        slot = get_active_slot();
        slot->results = &results;
        curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1);
@@ -2181,12 +2183,15 @@ int main(int argc, char **argv)
        int rc = 0;
        int i;
        int new_refs;
-       struct ref *ref;
+       struct ref *ref, *local_refs;
+       struct remote *remote;
        char *rewritten_url = NULL;
 
+       git_extract_argv0_path(argv[0]);
+
        setup_git_directory();
 
-       remote = xcalloc(sizeof(*remote), 1);
+       repo = xcalloc(sizeof(*repo), 1);
 
        argv++;
        for (i = 1; i < argc; i++, argv++) {
@@ -2219,14 +2224,14 @@ int main(int argc, char **argv)
                                continue;
                        }
                }
-               if (!remote->url) {
+               if (!repo->url) {
                        char *path = strstr(arg, "//");
-                       remote->url = arg;
-                       remote->path_len = strlen(arg);
+                       repo->url = arg;
+                       repo->path_len = strlen(arg);
                        if (path) {
-                               remote->path = strchr(path+2, '/');
-                               if (remote->path)
-                                       remote->path_len = strlen(remote->path);
+                               repo->path = strchr(path+2, '/');
+                               if (repo->path)
+                                       repo->path_len = strlen(repo->path);
                        }
                        continue;
                }
@@ -2239,7 +2244,7 @@ int main(int argc, char **argv)
        die("git-push is not available for http/https repository when not compiled with USE_CURL_MULTI");
 #endif
 
-       if (!remote->url)
+       if (!repo->url)
                usage(http_push_usage);
 
        if (delete_branch && nr_refspec != 1)
@@ -2247,17 +2252,24 @@ int main(int argc, char **argv)
 
        memset(remote_dir_exists, -1, 256);
 
-       http_init(NULL);
+       /*
+        * Create a minimum remote by hand to give to http_init(),
+        * primarily to allow it to look at the URL.
+        */
+       remote = xcalloc(sizeof(*remote), 1);
+       ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
+       remote->url[remote->url_nr++] = repo->url;
+       http_init(remote);
 
        no_pragma_header = curl_slist_append(no_pragma_header, "Pragma:");
 
-       if (remote->url && remote->url[strlen(remote->url)-1] != '/') {
-               rewritten_url = xmalloc(strlen(remote->url)+2);
-               strcpy(rewritten_url, remote->url);
+       if (repo->url && repo->url[strlen(repo->url)-1] != '/') {
+               rewritten_url = xmalloc(strlen(repo->url)+2);
+               strcpy(rewritten_url, repo->url);
                strcat(rewritten_url, "/");
-               remote->path = rewritten_url + (remote->path - remote->url);
-               remote->path_len++;
-               remote->url = rewritten_url;
+               repo->path = rewritten_url + (repo->path - repo->url);
+               repo->path_len++;
+               repo->url = rewritten_url;
        }
 
        /* Verify DAV compliance/lock support */
@@ -2266,30 +2278,27 @@ 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;
-       remote->has_info_refs = remote_exists("info/refs");
-       remote->has_info_packs = remote_exists("objects/info/packs");
-       if (remote->has_info_refs) {
+       repo->can_update_info_refs = 0;
+       repo->has_info_refs = remote_exists("info/refs");
+       repo->has_info_packs = remote_exists("objects/info/packs");
+       if (repo->has_info_refs) {
                info_ref_lock = lock_remote("info/refs", LOCK_TIME);
                if (info_ref_lock)
-                       remote->can_update_info_refs = 1;
+                       repo->can_update_info_refs = 1;
                else {
-                       fprintf(stderr, "Error: cannot lock existing info/refs\n");
+                       error("cannot lock existing info/refs");
                        rc = 1;
                        goto cleanup;
                }
        }
-       if (remote->has_info_packs)
+       if (repo->has_info_packs)
                fetch_indices();
 
        /* Get a list of all local and remote heads to validate refspecs */
-       get_local_heads();
+       local_refs = get_local_heads();
        fprintf(stderr, "Fetching remote heads...\n");
        get_dav_remote_heads();
 
@@ -2443,8 +2452,8 @@ int main(int argc, char **argv)
        }
 
        /* Update remote server info if appropriate */
-       if (remote->has_info_refs && new_refs) {
-               if (info_ref_lock && remote->can_update_info_refs) {
+       if (repo->has_info_refs && new_refs) {
+               if (info_ref_lock && repo->can_update_info_refs) {
                        fprintf(stderr, "Updating remote server info\n");
                        if (!dry_run)
                                update_remote_info_refs(info_ref_lock);
@@ -2457,7 +2466,7 @@ int main(int argc, char **argv)
        free(rewritten_url);
        if (info_ref_lock)
                unlock_remote(info_ref_lock);
-       free(remote);
+       free(repo);
 
        curl_slist_free_all(no_pragma_header);
 
index 0dbad3c888c6c9441af4d9550fd147ecb5b1aaf3..7321ccc9fe751a1e608c6620bcce401eac7ff98c 100644 (file)
@@ -111,9 +111,9 @@ static void start_object_request(struct walker *walker,
        struct walker_data *data = walker->data;
 
        snprintf(prevfile, sizeof(prevfile), "%s.prev", obj_req->filename);
-       unlink(prevfile);
+       unlink_or_warn(prevfile);
        rename(obj_req->tmpfile, prevfile);
-       unlink(obj_req->tmpfile);
+       unlink_or_warn(obj_req->tmpfile);
 
        if (obj_req->local != -1)
                error("fd leakage in start: %d", obj_req->local);
@@ -177,7 +177,7 @@ static void start_object_request(struct walker *walker,
                } while (prev_read > 0);
                close(prevlocal);
        }
-       unlink(prevfile);
+       unlink_or_warn(prevfile);
 
        /* Reset inflate/SHA1 if there was an error reading the previous temp
           file; also rewind to the beginning of the local file. */
@@ -231,7 +231,6 @@ static void finish_object_request(struct object_request *obj_req)
 {
        struct stat st;
 
-       fchmod(obj_req->local, 0444);
        close(obj_req->local); obj_req->local = -1;
 
        if (obj_req->http_code == 416) {
@@ -239,18 +238,18 @@ static void finish_object_request(struct object_request *obj_req)
        } else if (obj_req->curl_result != CURLE_OK) {
                if (stat(obj_req->tmpfile, &st) == 0)
                        if (st.st_size == 0)
-                               unlink(obj_req->tmpfile);
+                               unlink_or_warn(obj_req->tmpfile);
                return;
        }
 
        git_inflate_end(&obj_req->stream);
        git_SHA1_Final(obj_req->real_sha1, &obj_req->c);
        if (obj_req->zret != Z_STREAM_END) {
-               unlink(obj_req->tmpfile);
+               unlink_or_warn(obj_req->tmpfile);
                return;
        }
        if (hashcmp(obj_req->sha1, obj_req->real_sha1)) {
-               unlink(obj_req->tmpfile);
+               unlink_or_warn(obj_req->tmpfile);
                return;
        }
        obj_req->rename =
@@ -810,7 +809,7 @@ static void abort_object_request(struct object_request *obj_req)
                close(obj_req->local);
                obj_req->local = -1;
        }
-       unlink(obj_req->tmpfile);
+       unlink_or_warn(obj_req->tmpfile);
        if (obj_req->slot) {
                release_active_slot(obj_req->slot);
                obj_req->slot = NULL;
diff --git a/http.c b/http.c
index ee58799ca8cd433218ca7d40946580732a9010e7..2e3d6493ef40e1b34fea8d166d760f0b1fcccafc 100644 (file)
--- a/http.c
+++ b/http.c
@@ -1,7 +1,7 @@
 #include "http.h"
 
 int data_received;
-int active_requests = 0;
+int active_requests;
 
 #ifdef USE_CURL_MULTI
 static int max_requests = -1;
@@ -13,22 +13,23 @@ static CURL *curl_default;
 char curl_errorstr[CURL_ERROR_SIZE];
 
 static int curl_ssl_verify = -1;
-static const char *ssl_cert = NULL;
+static const char *ssl_cert;
 #if LIBCURL_VERSION_NUM >= 0x070902
-static const char *ssl_key = NULL;
+static const char *ssl_key;
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
-static const char *ssl_capath = NULL;
+static const char *ssl_capath;
 #endif
-static const char *ssl_cainfo = NULL;
+static const char *ssl_cainfo;
 static long curl_low_speed_limit = -1;
 static long curl_low_speed_time = -1;
-static int curl_ftp_no_epsv = 0;
-static const char *curl_http_proxy = NULL;
+static int curl_ftp_no_epsv;
+static const char *curl_http_proxy;
+static char *user_name, *user_pass;
 
 static struct curl_slist *pragma_header;
 
-static struct active_request_slot *active_queue_head = NULL;
+static struct active_request_slot *active_queue_head;
 
 size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
 {
@@ -43,6 +44,25 @@ size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
        return size;
 }
 
+#ifndef NO_CURL_IOCTL
+curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
+{
+       struct buffer *buffer = clientp;
+
+       switch (cmd) {
+       case CURLIOCMD_NOP:
+               return CURLIOE_OK;
+
+       case CURLIOCMD_RESTARTREAD:
+               buffer->posn = 0;
+               return CURLIOE_OK;
+
+       default:
+               return CURLIOE_UNKNOWNCMD;
+       }
+}
+#endif
+
 size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
 {
        size_t size = eltsize * nmemb;
@@ -94,53 +114,33 @@ static void process_curl_messages(void)
 static int http_options(const char *var, const char *value, void *cb)
 {
        if (!strcmp("http.sslverify", var)) {
-               if (curl_ssl_verify == -1) {
-                       curl_ssl_verify = git_config_bool(var, value);
-               }
-               return 0;
-       }
-
-       if (!strcmp("http.sslcert", var)) {
-               if (ssl_cert == NULL)
-                       return git_config_string(&ssl_cert, var, value);
+               curl_ssl_verify = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp("http.sslcert", var))
+               return git_config_string(&ssl_cert, var, value);
 #if LIBCURL_VERSION_NUM >= 0x070902
-       if (!strcmp("http.sslkey", var)) {
-               if (ssl_key == NULL)
-                       return git_config_string(&ssl_key, var, value);
-               return 0;
-       }
+       if (!strcmp("http.sslkey", var))
+               return git_config_string(&ssl_key, var, value);
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
-       if (!strcmp("http.sslcapath", var)) {
-               if (ssl_capath == NULL)
-                       return git_config_string(&ssl_capath, var, value);
-               return 0;
-       }
+       if (!strcmp("http.sslcapath", var))
+               return git_config_string(&ssl_capath, var, value);
 #endif
-       if (!strcmp("http.sslcainfo", var)) {
-               if (ssl_cainfo == NULL)
-                       return git_config_string(&ssl_cainfo, var, value);
-               return 0;
-       }
-
+       if (!strcmp("http.sslcainfo", var))
+               return git_config_string(&ssl_cainfo, var, value);
 #ifdef USE_CURL_MULTI
        if (!strcmp("http.maxrequests", var)) {
-               if (max_requests == -1)
-                       max_requests = git_config_int(var, value);
+               max_requests = git_config_int(var, value);
                return 0;
        }
 #endif
-
        if (!strcmp("http.lowspeedlimit", var)) {
-               if (curl_low_speed_limit == -1)
-                       curl_low_speed_limit = (long)git_config_int(var, value);
+               curl_low_speed_limit = (long)git_config_int(var, value);
                return 0;
        }
        if (!strcmp("http.lowspeedtime", var)) {
-               if (curl_low_speed_time == -1)
-                       curl_low_speed_time = (long)git_config_int(var, value);
+               curl_low_speed_time = (long)git_config_int(var, value);
                return 0;
        }
 
@@ -148,19 +148,28 @@ static int http_options(const char *var, const char *value, void *cb)
                curl_ftp_no_epsv = git_config_bool(var, value);
                return 0;
        }
-       if (!strcmp("http.proxy", var)) {
-               if (curl_http_proxy == NULL)
-                       return git_config_string(&curl_http_proxy, var, value);
-               return 0;
-       }
+       if (!strcmp("http.proxy", var))
+               return git_config_string(&curl_http_proxy, var, value);
 
        /* Fall back on the default ones */
        return git_default_config(var, value, cb);
 }
 
-static CURL* get_curl_handle(void)
+static void init_curl_http_auth(CURL *result)
+{
+       if (user_name) {
+               struct strbuf up = STRBUF_INIT;
+               if (!user_pass)
+                       user_pass = xstrdup(getpass("Password: "));
+               strbuf_addf(&up, "%s:%s", user_name, user_pass);
+               curl_easy_setopt(result, CURLOPT_USERPWD,
+                                strbuf_detach(&up, NULL));
+       }
+}
+
+static CURL *get_curl_handle(void)
 {
-       CURLresult = curl_easy_init();
+       CURL *result = curl_easy_init();
 
        if (!curl_ssl_verify) {
                curl_easy_setopt(result, CURLOPT_SSL_VERIFYPEER, 0);
@@ -176,6 +185,8 @@ static CURL* get_curl_handle(void)
        curl_easy_setopt(result, CURLOPT_NETRC, CURL_NETRC_OPTIONAL);
 #endif
 
+       init_curl_http_auth(result);
+
        if (ssl_cert != NULL)
                curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
 #if LIBCURL_VERSION_NUM >= 0x070902
@@ -213,11 +224,60 @@ static CURL* get_curl_handle(void)
        return result;
 }
 
+static void http_auth_init(const char *url)
+{
+       char *at, *colon, *cp, *slash;
+       int len;
+
+       cp = strstr(url, "://");
+       if (!cp)
+               return;
+
+       /*
+        * Ok, the URL looks like "proto://something".  Which one?
+        * "proto://<user>:<pass>@<host>/...",
+        * "proto://<user>@<host>/...", or just
+        * "proto://<host>/..."?
+        */
+       cp += 3;
+       at = strchr(cp, '@');
+       colon = strchr(cp, ':');
+       slash = strchrnul(cp, '/');
+       if (!at || slash <= at)
+               return; /* No credentials */
+       if (!colon || at <= colon) {
+               /* Only username */
+               len = at - cp;
+               user_name = xmalloc(len + 1);
+               memcpy(user_name, cp, len);
+               user_name[len] = '\0';
+               user_pass = NULL;
+       } else {
+               len = colon - cp;
+               user_name = xmalloc(len + 1);
+               memcpy(user_name, cp, len);
+               user_name[len] = '\0';
+               len = at - (colon + 1);
+               user_pass = xmalloc(len + 1);
+               memcpy(user_pass, colon + 1, len);
+               user_pass[len] = '\0';
+       }
+}
+
+static void set_from_env(const char **var, const char *envname)
+{
+       const char *val = getenv(envname);
+       if (val)
+               *var = val;
+}
+
 void http_init(struct remote *remote)
 {
        char *low_speed_limit;
        char *low_speed_time;
 
+       git_config(http_options, NULL);
+
        curl_global_init(CURL_GLOBAL_ALL);
 
        if (remote && remote->http_proxy)
@@ -242,14 +302,14 @@ void http_init(struct remote *remote)
        if (getenv("GIT_SSL_NO_VERIFY"))
                curl_ssl_verify = 0;
 
-       ssl_cert = getenv("GIT_SSL_CERT");
+       set_from_env(&ssl_cert, "GIT_SSL_CERT");
 #if LIBCURL_VERSION_NUM >= 0x070902
-       ssl_key = getenv("GIT_SSL_KEY");
+       set_from_env(&ssl_key, "GIT_SSL_KEY");
 #endif
 #if LIBCURL_VERSION_NUM >= 0x070908
-       ssl_capath = getenv("GIT_SSL_CAPATH");
+       set_from_env(&ssl_capath, "GIT_SSL_CAPATH");
 #endif
-       ssl_cainfo = getenv("GIT_SSL_CAINFO");
+       set_from_env(&ssl_cainfo, "GIT_SSL_CAINFO");
 
        low_speed_limit = getenv("GIT_HTTP_LOW_SPEED_LIMIT");
        if (low_speed_limit != NULL)
@@ -258,8 +318,6 @@ void http_init(struct remote *remote)
        if (low_speed_time != NULL)
                curl_low_speed_time = strtol(low_speed_time, NULL, 10);
 
-       git_config(http_options, NULL);
-
        if (curl_ssl_verify == -1)
                curl_ssl_verify = 1;
 
@@ -271,6 +329,9 @@ void http_init(struct remote *remote)
        if (getenv("GIT_CURL_FTP_NO_EPSV"))
                curl_ftp_no_epsv = 1;
 
+       if (remote && remote->url && remote->url[0])
+               http_auth_init(remote->url[0]);
+
 #ifndef NO_CURL_EASY_DUPHANDLE
        curl_default = get_curl_handle();
 #endif
@@ -322,15 +383,14 @@ struct active_request_slot *get_active_slot(void)
        /* Wait for a slot to open up if the queue is full */
        while (active_requests >= max_requests) {
                curl_multi_perform(curlm, &num_transfers);
-               if (num_transfers < active_requests) {
+               if (num_transfers < active_requests)
                        process_curl_messages();
-               }
        }
 #endif
 
-       while (slot != NULL && slot->in_use) {
+       while (slot != NULL && slot->in_use)
                slot = slot->next;
-       }
+
        if (slot == NULL) {
                newslot = xmalloc(sizeof(*newslot));
                newslot->curl = NULL;
@@ -341,9 +401,8 @@ struct active_request_slot *get_active_slot(void)
                if (slot == NULL) {
                        active_queue_head = newslot;
                } else {
-                       while (slot->next != NULL) {
+                       while (slot->next != NULL)
                                slot = slot->next;
-                       }
                        slot->next = newslot;
                }
                slot = newslot;
@@ -404,7 +463,7 @@ struct fill_chain {
        struct fill_chain *next;
 };
 
-static struct fill_chain *fill_cfg = NULL;
+static struct fill_chain *fill_cfg;
 
 void add_fill_function(void *data, int (*fill)(void *))
 {
@@ -535,9 +594,8 @@ static void finish_active_slot(struct active_request_slot *slot)
        }
 
        /* Run callback if appropriate */
-       if (slot->callback_func != NULL) {
+       if (slot->callback_func != NULL)
                slot->callback_func(slot->callback_data);
-       }
 }
 
 void finish_all_active_slots(void)
@@ -567,37 +625,29 @@ static inline int needs_quote(int ch)
 
 static inline int hex(int v)
 {
-       if (v < 10) return '0' + v;
-       else return 'A' + v - 10;
+       if (v < 10)
+               return '0' + v;
+       else
+               return 'A' + v - 10;
 }
 
 static char *quote_ref_url(const char *base, const char *ref)
 {
+       struct strbuf buf = STRBUF_INIT;
        const char *cp;
-       char *dp, *qref;
-       int len, baselen, ch;
+       int ch;
 
-       baselen = strlen(base);
-       len = baselen + 2; /* '/' after base and terminating NUL */
-       for (cp = ref; (ch = *cp) != 0; cp++, len++)
+       strbuf_addstr(&buf, base);
+       if (buf.len && buf.buf[buf.len - 1] != '/' && *ref != '/')
+               strbuf_addstr(&buf, "/");
+
+       for (cp = ref; (ch = *cp) != 0; cp++)
                if (needs_quote(ch))
-                       len += 2; /* extra two hex plus replacement % */
-       qref = xmalloc(len);
-       memcpy(qref, base, baselen);
-       dp = qref + baselen;
-       *(dp++) = '/';
-       for (cp = ref; (ch = *cp) != 0; cp++) {
-               if (needs_quote(ch)) {
-                       *dp++ = '%';
-                       *dp++ = hex((ch >> 4) & 0xF);
-                       *dp++ = hex(ch & 0xF);
-               }
+                       strbuf_addf(&buf, "%%%02x", ch);
                else
-                       *dp++ = ch;
-       }
-       *dp = 0;
+                       strbuf_addch(&buf, *cp);
 
-       return qref;
+       return strbuf_detach(&buf, NULL);
 }
 
 int http_fetch_ref(const char *base, struct ref *ref)
diff --git a/http.h b/http.h
index 905b4629a47789705c13745fd56ce0c91adea41b..26abebed1f35db971e423f79c433bed8c5338789 100644 (file)
--- a/http.h
+++ b/http.h
 #define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND
 #endif
 
+#if LIBCURL_VERSION_NUM < 0x070c03
+#define NO_CURL_IOCTL
+#endif
+
 struct slot_results
 {
        CURLcode curl_result;
@@ -67,6 +71,9 @@ struct buffer
 extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
 extern size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
 extern size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+#ifndef NO_CURL_IOCTL
+extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
+#endif
 
 /* Slot lifecycle functions */
 extern struct active_request_slot *get_active_slot(void);
index 3703dbd1af65c30806a98f0d2d3dc14b8b0e9798..8154cb2116da9257ecf286c46f77fc1ed2a62afc 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 #include "cache.h"
+#include "exec_cmd.h"
 #ifdef NO_OPENSSL
 typedef void *SSL;
 #endif
@@ -115,9 +116,9 @@ static int nfvasprintf(char **strp, const char *fmt, va_list ap)
 
        len = vsnprintf(tmp, sizeof(tmp), fmt, ap);
        if (len < 0)
-               die("Fatal: Out of memory\n");
+               die("Fatal: Out of memory");
        if (len >= sizeof(tmp))
-               die("imap command overflow !\n");
+               die("imap command overflow!");
        *strp = xmemdupz(tmp, len);
        return len;
 }
@@ -134,6 +135,7 @@ struct imap_server_conf {
        char *pass;
        int use_ssl;
        int ssl_verify;
+       int use_html;
 };
 
 struct imap_store_conf {
@@ -482,7 +484,7 @@ static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
 
        va_start(va, fmt);
        if (blen <= 0 || (unsigned)(ret = vsnprintf(buf, blen, fmt, va)) >= (unsigned)blen)
-               die("Fatal: buffer too small. Please report a bug.\n");
+               die("Fatal: buffer too small. Please report a bug.");
        va_end(va);
        return ret;
 }
@@ -577,7 +579,7 @@ static struct imap_cmd *v_issue_imap_cmd(struct imap_store *ctx,
                        n = socket_write(&imap->buf.sock, cmd->cb.data, cmd->cb.dlen);
                        free(cmd->cb.data);
                        if (n != cmd->cb.dlen ||
-                           (n = socket_write(&imap->buf.sock, "\r\n", 2)) != 2) {
+                           socket_write(&imap->buf.sock, "\r\n", 2) != 2) {
                                free(cmd->cmd);
                                free(cmd);
                                return NULL;
@@ -1262,6 +1264,53 @@ static int imap_store_msg(struct store *gctx, struct msg_data *data, int *uid)
        return DRV_OK;
 }
 
+static void encode_html_chars(struct strbuf *p)
+{
+       int i;
+       for (i = 0; i < p->len; i++) {
+               if (p->buf[i] == '&')
+                       strbuf_splice(p, i, 1, "&amp;", 5);
+               if (p->buf[i] == '<')
+                       strbuf_splice(p, i, 1, "&lt;", 4);
+               if (p->buf[i] == '>')
+                       strbuf_splice(p, i, 1, "&gt;", 4);
+               if (p->buf[i] == '"')
+                       strbuf_splice(p, i, 1, "&quot;", 6);
+       }
+}
+static void wrap_in_html(struct msg_data *msg)
+{
+       struct strbuf buf = STRBUF_INIT;
+       struct strbuf **lines;
+       struct strbuf **p;
+       static char *content_type = "Content-Type: text/html;\n";
+       static char *pre_open = "<pre>\n";
+       static char *pre_close = "</pre>\n";
+       int added_header = 0;
+
+       strbuf_attach(&buf, msg->data, msg->len, msg->len);
+       lines = strbuf_split(&buf, '\n');
+       strbuf_release(&buf);
+       for (p = lines; *p; p++) {
+               if (! added_header) {
+                       if ((*p)->len == 1 && *((*p)->buf) == '\n') {
+                               strbuf_addstr(&buf, content_type);
+                               strbuf_addbuf(&buf, *p);
+                               strbuf_addstr(&buf, pre_open);
+                               added_header = 1;
+                               continue;
+                       }
+               }
+               else
+                       encode_html_chars(*p);
+               strbuf_addbuf(&buf, *p);
+       }
+       strbuf_addstr(&buf, pre_close);
+       strbuf_list_free(lines);
+       msg->len  = buf.len;
+       msg->data = strbuf_detach(&buf, NULL);
+}
+
 #define CHUNKSIZE 0x1000
 
 static int read_message(FILE *f, struct msg_data *msg)
@@ -1338,6 +1387,7 @@ static struct imap_server_conf server = {
        NULL,   /* pass */
        0,      /* use_ssl */
        1,      /* ssl_verify */
+       0,      /* use_html */
 };
 
 static char *imap_folder;
@@ -1376,6 +1426,8 @@ static int git_imap_config(const char *key, const char *val, void *cb)
                server.tunnel = xstrdup(val);
        else if (!strcmp("sslverify", key))
                server.ssl_verify = git_config_bool(key, val);
+       else if (!strcmp("preformattedHTML", key))
+               server.use_html = git_config_bool(key, val);
        return 0;
 }
 
@@ -1389,6 +1441,8 @@ int main(int argc, char **argv)
        int total, n = 0;
        int nongit_ok;
 
+       git_extract_argv0_path(argv[0]);
+
        /* init the random number generator */
        arc4_init();
 
@@ -1436,6 +1490,8 @@ int main(int argc, char **argv)
                fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
                if (!split_msg(&all_msgs, &msg, &ofs))
                        break;
+               if (server.use_html)
+                       wrap_in_html(&msg);
                r = imap_store_msg(ctx, &msg, &uid);
                if (r != DRV_OK)
                        break;
index 7697b1dfe378183dc47636fa18671722198b8045..6e93ee6af64593937ee9b078e599e81d40b74303 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>] }";
@@ -177,7 +178,7 @@ static char *open_pack_file(char *pack_name)
                } 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);
@@ -231,7 +232,7 @@ static void free_base_data(struct base_data *c)
 
 static void prune_base_data(struct base_data *retain)
 {
-       struct base_data *b = base_cache;
+       struct base_data *b;
        for (b = base_cache;
             base_cache_used > delta_base_cache_limit && b;
             b = b->child) {
@@ -822,8 +823,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                }
                if (move_temp_to_file(curr_pack_name, final_pack_name))
                        die("cannot store pack file");
-       }
-       if (from_stdin)
+       } else if (from_stdin)
                chmod(final_pack_name, 0444);
 
        if (final_index_name != curr_index_name) {
@@ -834,8 +834,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                }
                if (move_temp_to_file(curr_index_name, final_index_name))
                        die("cannot store index file");
-       }
-       chmod(final_index_name, 0444);
+       } else
+               chmod(final_index_name, 0444);
 
        if (!from_stdin) {
                printf("%s\n", sha1_to_hex(sha1));
@@ -881,6 +881,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 a32f4cdc458f823f7e8754742d2f8e21b2aabff3..fc281597fd2fd5ad2299eab0848da99355e383fc 100644 (file)
@@ -27,7 +27,7 @@
  *
  * It does so by calculating the costs of the path ending in characters
  * i (in string1) and j (in string2), respectively, given that the last
- * operation is a substition, a swap, a deletion, or an insertion.
+ * operation is a substitution, a swap, a deletion, or an insertion.
  *
  * This implementation allows the costs to be weighted:
  *
index dd243c7c662c2f3fe9463b616bb00bed2cc503a7..8953548c07bb36f20798c7ca344d07960c22618c 100644 (file)
@@ -10,7 +10,7 @@
 
 static void process_blob(struct rev_info *revs,
                         struct blob *blob,
-                        struct object_array *p,
+                        show_object_fn show,
                         struct name_path *path,
                         const char *name)
 {
@@ -23,7 +23,7 @@ static void process_blob(struct rev_info *revs,
        if (obj->flags & (UNINTERESTING | SEEN))
                return;
        obj->flags |= SEEN;
-       add_object(obj, p, path, name);
+       show(obj, path, name);
 }
 
 /*
@@ -50,7 +50,7 @@ static void process_blob(struct rev_info *revs,
  */
 static void process_gitlink(struct rev_info *revs,
                            const unsigned char *sha1,
-                           struct object_array *p,
+                           show_object_fn show,
                            struct name_path *path,
                            const char *name)
 {
@@ -59,7 +59,7 @@ static void process_gitlink(struct rev_info *revs,
 
 static void process_tree(struct rev_info *revs,
                         struct tree *tree,
-                        struct object_array *p,
+                        show_object_fn show,
                         struct name_path *path,
                         const char *name)
 {
@@ -77,7 +77,7 @@ static void process_tree(struct rev_info *revs,
        if (parse_tree(tree) < 0)
                die("bad tree object %s", sha1_to_hex(obj->sha1));
        obj->flags |= SEEN;
-       add_object(obj, p, path, name);
+       show(obj, path, name);
        me.up = path;
        me.elem = name;
        me.elem_len = strlen(name);
@@ -88,14 +88,14 @@ static void process_tree(struct rev_info *revs,
                if (S_ISDIR(entry.mode))
                        process_tree(revs,
                                     lookup_tree(entry.sha1),
-                                    p, &me, entry.path);
+                                    show, &me, entry.path);
                else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.sha1,
-                                       p, &me, entry.path);
+                                       show, &me, entry.path);
                else
                        process_blob(revs,
                                     lookup_blob(entry.sha1),
-                                    p, &me, entry.path);
+                                    show, &me, entry.path);
        }
        free(tree->buffer);
        tree->buffer = NULL;
@@ -134,17 +134,22 @@ void mark_edges_uninteresting(struct commit_list *list,
        }
 }
 
+static void add_pending_tree(struct rev_info *revs, struct tree *tree)
+{
+       add_pending_object(revs, &tree->object, "");
+}
+
 void traverse_commit_list(struct rev_info *revs,
-                         void (*show_commit)(struct commit *),
-                         void (*show_object)(struct object_array_entry *))
+                         show_commit_fn show_commit,
+                         show_object_fn show_object,
+                         void *data)
 {
        int i;
        struct commit *commit;
-       struct object_array objects = { 0, 0, NULL };
 
        while ((commit = get_revision(revs)) != NULL) {
-               process_tree(revs, commit->tree, &objects, NULL, "");
-               show_commit(commit);
+               add_pending_tree(revs, commit->tree);
+               show_commit(commit, data);
        }
        for (i = 0; i < revs->pending.nr; i++) {
                struct object_array_entry *pending = revs->pending.objects + i;
@@ -154,25 +159,22 @@ void traverse_commit_list(struct rev_info *revs,
                        continue;
                if (obj->type == OBJ_TAG) {
                        obj->flags |= SEEN;
-                       add_object_array(obj, name, &objects);
+                       show_object(obj, NULL, name);
                        continue;
                }
                if (obj->type == OBJ_TREE) {
-                       process_tree(revs, (struct tree *)obj, &objects,
+                       process_tree(revs, (struct tree *)obj, show_object,
                                     NULL, name);
                        continue;
                }
                if (obj->type == OBJ_BLOB) {
-                       process_blob(revs, (struct blob *)obj, &objects,
+                       process_blob(revs, (struct blob *)obj, show_object,
                                     NULL, name);
                        continue;
                }
                die("unknown pending object %s (%s)",
                    sha1_to_hex(obj->sha1), name);
        }
-       for (i = 0; i < objects.nr; i++)
-               show_object(&objects.objects[i]);
-       free(objects.objects);
        if (revs->pending.nr) {
                free(revs->pending.objects);
                revs->pending.nr = 0;
index 0f41391ecc00eac324ea76de7654781c4fce094e..d65dbf03e657facb29a2846144eda2fa3687bc2f 100644 (file)
@@ -1,11 +1,11 @@
 #ifndef LIST_OBJECTS_H
 #define LIST_OBJECTS_H
 
-typedef void (*show_commit_fn)(struct commit *);
-typedef void (*show_object_fn)(struct object_array_entry *);
+typedef void (*show_commit_fn)(struct commit *, void *);
+typedef void (*show_object_fn)(struct object *, const struct name_path *, const char *);
 typedef void (*show_edge_fn)(struct commit *);
 
-void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn);
+void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);
 
 void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn);
 
index fa2ca5250c75a0d335570a3a3c71b20ebd0b42fa..81c02ad0531e98379d8cd11dc7c2d70214d67fb4 100644 (file)
@@ -219,7 +219,7 @@ static int ll_ext_merge(const struct ll_merge_driver *fn,
        close(fd);
  bad:
        for (i = 0; i < 3; i++)
-               unlink(temp[i]);
+               unlink_or_warn(temp[i]);
        strbuf_release(&cmd);
        return status;
 }
index 8e556ff8c9671864db44dc8b6f4a861bd35142a6..eb931eded5a6ed20f1d80dadf08cbb8009d85767 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;
@@ -15,7 +16,7 @@ static void remove_lock_file(void)
                    lock_file_list->filename[0]) {
                        if (lock_file_list->fd >= 0)
                                close(lock_file_list->fd);
-                       unlink(lock_file_list->filename);
+                       unlink_or_warn(lock_file_list->filename);
                }
                lock_file_list = lock_file_list->next;
        }
@@ -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);
 }
 
@@ -108,7 +109,7 @@ static char *resolve_symlink(char *p, size_t s)
                         * link is a relative path, so I must replace the
                         * last element of p with it.
                         */
-                       char *r = (char*)last_path_elm(p);
+                       char *r = (char *)last_path_elm(p);
                        if (r - p + link_len < s)
                                strcpy(r, link);
                        else {
@@ -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;
        }
 
@@ -262,7 +259,7 @@ void rollback_lock_file(struct lock_file *lk)
        if (lk->filename[0]) {
                if (lk->fd >= 0)
                        close(lk->fd);
-               unlink(lk->filename);
+               unlink_or_warn(lk->filename);
        }
        lk->filename[0] = 0;
 }
index 194ddb13da09668334cd9e1f6eeef517c3346ee9..5bd29e6994c92268ec576671bb8564b57d1a5c9d 100644 (file)
@@ -6,6 +6,7 @@
 #include "log-tree.h"
 #include "reflog-walk.h"
 #include "refs.h"
+#include "string-list.h"
 
 struct decoration name_decoration = { "object names" };
 
@@ -48,7 +49,7 @@ static void show_parents(struct commit *commit, int abbrev)
        struct commit_list *p;
        for (p = commit->parents; p ; p = p->next) {
                struct commit *parent = p->item;
-               printf(" %s", diff_unique_abbrev(parent->object.sha1, abbrev));
+               printf(" %s", find_unique_abbrev(parent->object.sha1, abbrev));
        }
 }
 
@@ -79,18 +80,18 @@ void show_decorations(struct rev_info *opt, struct commit *commit)
  */
 static int detect_any_signoff(char *letter, int size)
 {
-       char ch, *cp;
+       char *cp;
        int seen_colon = 0;
        int seen_at = 0;
        int seen_name = 0;
        int seen_head = 0;
 
        cp = letter + size;
-       while (letter <= --cp && (ch = *cp) == '\n')
+       while (letter <= --cp && *cp == '\n')
                continue;
 
        while (letter <= cp) {
-               ch = *cp--;
+               char ch = *cp--;
                if (ch == '\n')
                        break;
 
@@ -178,13 +179,31 @@ static int has_non_ascii(const char *s)
        return 0;
 }
 
-void log_write_email_headers(struct rev_info *opt, const char *name,
+void get_patch_filename(struct commit *commit, int nr, const char *suffix,
+                       struct strbuf *buf)
+{
+       int suffix_len = strlen(suffix) + 1;
+       int start_len = buf->len;
+
+       strbuf_addf(buf, commit ? "%04d-" : "%d", nr);
+       if (commit) {
+               int max_len = start_len + FORMAT_PATCH_NAME_MAX - suffix_len;
+
+               format_commit_message(commit, "%f", buf, DATE_NORMAL);
+               if (max_len < buf->len)
+                       strbuf_setlen(buf, max_len);
+               strbuf_addstr(buf, suffix);
+       }
+}
+
+void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **subject_p,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p)
 {
        const char *subject = NULL;
        const char *extra_headers = opt->extra_headers;
+       const char *name = sha1_to_hex(commit->object.sha1);
 
        *need_8bit_cte_p = 0; /* unknown */
        if (opt->total > 0) {
@@ -211,14 +230,19 @@ void log_write_email_headers(struct rev_info *opt, const char *name,
                printf("Message-Id: <%s>\n", opt->message_id);
                graph_show_oneline(opt->graph);
        }
-       if (opt->ref_message_id) {
-               printf("In-Reply-To: <%s>\nReferences: <%s>\n",
-                      opt->ref_message_id, opt->ref_message_id);
+       if (opt->ref_message_ids && opt->ref_message_ids->nr > 0) {
+               int i, n;
+               n = opt->ref_message_ids->nr;
+               printf("In-Reply-To: <%s>\n", opt->ref_message_ids->items[n-1].string);
+               for (i = 0; i < n; i++)
+                       printf("%s<%s>\n", (i > 0 ? "\t" : "References: "),
+                              opt->ref_message_ids->items[i].string);
                graph_show_oneline(opt->graph);
        }
        if (opt->mime_boundary) {
                static char subject_buffer[1024];
                static char buffer[1024];
+               struct strbuf filename =  STRBUF_INIT;
                *need_8bit_cte_p = -1; /* NEVER */
                snprintf(subject_buffer, sizeof(subject_buffer) - 1,
                         "%s"
@@ -237,18 +261,21 @@ void log_write_email_headers(struct rev_info *opt, const char *name,
                         mime_boundary_leader, opt->mime_boundary);
                extra_headers = subject_buffer;
 
+               get_patch_filename(opt->numbered_files ? NULL : commit, opt->nr,
+                                   opt->patch_suffix, &filename);
                snprintf(buffer, sizeof(buffer) - 1,
                         "\n--%s%s\n"
                         "Content-Type: text/x-patch;"
-                        " name=\"%s.diff\"\n"
+                        " name=\"%s\"\n"
                         "Content-Transfer-Encoding: 8bit\n"
                         "Content-Disposition: %s;"
-                        " filename=\"%s.diff\"\n\n",
+                        " filename=\"%s\"\n\n",
                         mime_boundary_leader, opt->mime_boundary,
-                        name,
+                        filename.buf,
                         opt->no_inline ? "attachment" : "inline",
-                        name);
+                        filename.buf);
                opt->diffopt.stat_sep = buffer;
+               strbuf_release(&filename);
        }
        *subject_p = subject;
        *extra_headers_p = extra_headers;
@@ -280,7 +307,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);
@@ -328,8 +355,7 @@ void show_log(struct rev_info *opt)
         */
 
        if (opt->commit_format == CMIT_FMT_EMAIL) {
-               log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
-                                       &subject, &extra_headers,
+               log_write_email_headers(opt, commit, &subject, &extra_headers,
                                        &need_8bit_cte);
        } else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
                fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
@@ -348,13 +374,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 f2a90084ae1874632318c7880e95426eca2682ea..20b5caf1aa45aa0ba076ec60320d252c42abfb64 100644 (file)
@@ -13,10 +13,14 @@ int log_tree_commit(struct rev_info *, struct commit *);
 int log_tree_opt_parse(struct rev_info *, const char **, int);
 void show_log(struct rev_info *opt);
 void show_decorations(struct rev_info *opt, struct commit *commit);
-void log_write_email_headers(struct rev_info *opt, const char *name,
+void log_write_email_headers(struct rev_info *opt, struct commit *commit,
                             const char **subject_p,
                             const char **extra_headers_p,
                             int *need_8bit_cte_p);
 void load_ref_decorations(void);
 
+#define FORMAT_PATCH_NAME_MAX 64
+void get_patch_filename(struct commit *commit, int nr, const char *suffix,
+                       struct strbuf *buf);
+
 #endif
index 88fc6f394684436967002ca477eac1e084537348..bb1f2fb711a588d2af0d61decbd4b3eb2f2aebbe 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;
+       char *p;
+
+       if (old_email)
+               for (p = old_email; *p; p++)
+                       *p = tolower(*p);
+       if (new_email)
+               for (p = new_email; *p; p++)
+                       *p = tolower(*p);
+
+       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, int allow_empty_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 (!allow_empty_email && (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 +160,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, 0)) != NULL)
+                       parse_name_and_email(name2, &name2, &email2, 1);
+
+               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 +212,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..f5df9b961b2999e4824b900d61d28c254b2ef858 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)
@@ -520,8 +520,12 @@ static void update_file_flags(struct merge_options *o,
                unsigned long size;
 
                if (S_ISGITLINK(mode))
-                       die("cannot read object %s '%s': It is a submodule!",
-                           sha1_to_hex(sha), path);
+                       /*
+                        * We may later decide to recursively descend into
+                        * the submodule directory and update its index
+                        * and/or work tree, but we do not do that now.
+                        */
+                       goto update_index;
 
                buf = read_sha1_file(sha, &type, &size);
                if (!buf)
@@ -801,22 +805,19 @@ static int process_renames(struct merge_options *o,
        }
 
        for (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {
-               int compare;
                char *src;
-               struct string_list *renames1, *renames2, *renames2Dst;
+               struct string_list *renames1, *renames2Dst;
                struct rename *ren1 = NULL, *ren2 = NULL;
                const char *branch1, *branch2;
                const char *ren1_src, *ren1_dst;
 
                if (i >= a_renames->nr) {
-                       compare = 1;
                        ren2 = b_renames->items[j++].util;
                } else if (j >= b_renames->nr) {
-                       compare = -1;
                        ren1 = a_renames->items[i++].util;
                } else {
-                       compare = strcmp(a_renames->items[i].string,
-                                       b_renames->items[j].string);
+                       int compare = strcmp(a_renames->items[i].string,
+                                            b_renames->items[j].string);
                        if (compare <= 0)
                                ren1 = a_renames->items[i++].util;
                        if (compare >= 0)
@@ -826,14 +827,12 @@ static int process_renames(struct merge_options *o,
                /* TODO: refactor, so that 1/2 are not needed */
                if (ren1) {
                        renames1 = a_renames;
-                       renames2 = b_renames;
                        renames2Dst = &b_by_dst;
                        branch1 = o->branch1;
                        branch2 = o->branch2;
                } else {
                        struct rename *tmp;
                        renames1 = b_renames;
-                       renames2 = a_renames;
                        renames2Dst = &a_by_dst;
                        branch1 = o->branch2;
                        branch2 = o->branch1;
@@ -934,11 +933,12 @@ static int process_renames(struct merge_options *o,
                                       ren1_src, ren1_dst, branch1,
                                       branch2);
                                update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);
-                               update_stages(ren1_dst, NULL,
-                                               branch1 == o->branch1 ?
-                                               ren1->pair->two : NULL,
-                                               branch1 == o->branch1 ?
-                                               NULL : ren1->pair->two, 1);
+                               if (!o->call_depth)
+                                       update_stages(ren1_dst, NULL,
+                                                       branch1 == o->branch1 ?
+                                                       ren1->pair->two : NULL,
+                                                       branch1 == o->branch1 ?
+                                                       NULL : ren1->pair->two, 1);
                        } else if (!sha_eq(dst_other.sha1, null_sha1)) {
                                const char *new_path;
                                clean_merge = 0;
@@ -1121,21 +1121,13 @@ static int process_entry(struct merge_options *o,
                                 o->branch1, o->branch2);
 
                clean_merge = mfi.clean;
-               if (mfi.clean)
-                       update_file(o, 1, mfi.sha, mfi.mode, path);
-               else if (S_ISGITLINK(mfi.mode))
-                       output(o, 1, "CONFLICT (submodule): Merge conflict in %s "
-                              "- needs %s", path, sha1_to_hex(b.sha1));
-               else {
+               if (!mfi.clean) {
+                       if (S_ISGITLINK(mfi.mode))
+                               reason = "submodule";
                        output(o, 1, "CONFLICT (%s): Merge conflict in %s",
                                        reason, path);
-
-                       if (o->call_depth)
-                               update_file(o, 0, mfi.sha, mfi.mode, path);
-                       else
-                               update_file_flags(o, mfi.sha, mfi.mode, path,
-                                             0 /* update_cache */, 1 /* update_working_directory */);
                }
+               update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
        } else if (!o_sha && !a_sha && !b_sha) {
                /*
                 * this entry was deleted altogether. a_mode == 0 means
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 7e6a92c88e7b139ec03e0ff26e97e1559a06a220..e1feef9c3329e0370e7caff612b4f6c8684cbaef 100644 (file)
--- a/object.c
+++ b/object.c
@@ -45,7 +45,8 @@ int type_from_string(const char *str)
 
 static unsigned int hash_obj(struct object *obj, unsigned int n)
 {
-       unsigned int hash = *(unsigned int *)obj->sha1;
+       unsigned int hash;
+       memcpy(&hash, obj->sha1, sizeof(unsigned int));
        return hash % n;
 }
 
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 2c76fb181f64e10c65517224b0c09cb648db81ac..301fc60eae1ad53721851a272a0fbd0192881801 100644 (file)
@@ -66,7 +66,7 @@ static void prune_ref(struct ref_to_prune *r)
        struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
 
        if (lock) {
-               unlink(git_path("%s", r->name));
+               unlink_or_warn(git_path("%s", r->name));
                unlock_ref(lock);
        }
 }
diff --git a/pager.c b/pager.c
index f19ddbc87df04f117cd5e39189c8322fd5f29d68..4921843577e42b774457a61277b9bc3441d3ab6b 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -1,5 +1,6 @@
 #include "cache.h"
 #include "run-command.h"
+#include "sigchain.h"
 
 /*
  * This is split up from the rest of git so that we can do
@@ -38,6 +39,13 @@ static void wait_for_pager(void)
        finish_command(&pager_process);
 }
 
+static void wait_for_pager_signal(int signo)
+{
+       wait_for_pager();
+       sigchain_pop(signo);
+       raise(signo);
+}
+
 void setup_pager(void)
 {
        const char *pager = getenv("GIT_PAGER");
@@ -75,6 +83,7 @@ void setup_pager(void)
        close(pager_process.in);
 
        /* this makes sure that the parent terminates after the pager */
+       sigchain_push_common(wait_for_pager_signal);
        atexit(wait_for_pager);
 }
 
index 9eb55cc8b5182495b687e133a938937db1bc3949..cf71bcffd2e1e9aeacb44df488b0825f09d54255 100644 (file)
@@ -1,6 +1,7 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
 #include "cache.h"
+#include "commit.h"
 
 #define OPT_SHORT 1
 #define OPT_UNSET 2
@@ -243,6 +244,9 @@ void parse_options_start(struct parse_opt_ctx_t *ctx,
        ctx->out  = argv;
        ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
        ctx->flags = flags;
+       if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
+           (flags & PARSE_OPT_STOP_AT_NON_OPTION))
+               die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
 }
 
 static int usage_with_options_internal(const char * const *,
@@ -252,6 +256,8 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                       const struct option *options,
                       const char * const usagestr[])
 {
+       int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
+
        /* we must reset ->opt, unknown short option leave it dangling */
        ctx->opt = NULL;
 
@@ -267,18 +273,18 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
 
                if (arg[1] != '-') {
                        ctx->opt = arg + 1;
-                       if (*ctx->opt == 'h')
+                       if (internal_help && *ctx->opt == 'h')
                                return parse_options_usage(usagestr, options);
                        switch (parse_short_opt(ctx, options)) {
                        case -1:
                                return parse_options_usage(usagestr, options);
                        case -2:
-                               return PARSE_OPT_UNKNOWN;
+                               goto unknown;
                        }
                        if (ctx->opt)
                                check_typos(arg + 1, options);
                        while (ctx->opt) {
-                               if (*ctx->opt == 'h')
+                               if (internal_help && *ctx->opt == 'h')
                                        return parse_options_usage(usagestr, options);
                                switch (parse_short_opt(ctx, options)) {
                                case -1:
@@ -291,7 +297,7 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                                         */
                                        ctx->argv[0] = xstrdup(ctx->opt - 1);
                                        *(char *)ctx->argv[0] = '-';
-                                       return PARSE_OPT_UNKNOWN;
+                                       goto unknown;
                                }
                        }
                        continue;
@@ -305,16 +311,22 @@ int parse_options_step(struct parse_opt_ctx_t *ctx,
                        break;
                }
 
-               if (!strcmp(arg + 2, "help-all"))
+               if (internal_help && !strcmp(arg + 2, "help-all"))
                        return usage_with_options_internal(usagestr, options, 1);
-               if (!strcmp(arg + 2, "help"))
+               if (internal_help && !strcmp(arg + 2, "help"))
                        return parse_options_usage(usagestr, options);
                switch (parse_long_opt(ctx, arg + 2, options)) {
                case -1:
                        return parse_options_usage(usagestr, options);
                case -2:
-                       return PARSE_OPT_UNKNOWN;
+                       goto unknown;
                }
+               continue;
+unknown:
+               if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
+                       return PARSE_OPT_UNKNOWN;
+               ctx->out[ctx->cpidx++] = ctx->argv[0];
+               ctx->opt = NULL;
        }
        return PARSE_OPT_DONE;
 }
@@ -355,6 +367,9 @@ int parse_options(int argc, const char **argv, const struct option *options,
 int usage_with_options_internal(const char * const *usagestr,
                                const struct option *opts, int full)
 {
+       if (!usagestr)
+               return PARSE_OPT_HELP;
+
        fprintf(stderr, "usage: %s\n", *usagestr++);
        while (*usagestr && **usagestr)
                fprintf(stderr, "   or: %s\n", *usagestr++);
@@ -506,6 +521,22 @@ int parse_opt_verbosity_cb(const struct option *opt, const char *arg,
        return 0;
 }
 
+int parse_opt_with_commit(const struct option *opt, const char *arg, int unset)
+{
+       unsigned char sha1[20];
+       struct commit *commit;
+
+       if (!arg)
+               return -1;
+       if (get_sha1(arg, sha1))
+               return error("malformed object name %s", arg);
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               return error("no such commit %s", arg);
+       commit_list_insert(commit, opt->value);
+       return 0;
+}
+
 /*
  * This should really be OPTION_FILENAME type as a part of
  * parse_options that take prefix to do this while parsing.
index 034162ec6975cfca8dedadc4eb541212b0d97e43..b54eec128bcda1a8b4c4a2c33f2ce799c506ac23 100644 (file)
@@ -21,6 +21,8 @@ enum parse_opt_flags {
        PARSE_OPT_KEEP_DASHDASH = 1,
        PARSE_OPT_STOP_AT_NON_OPTION = 2,
        PARSE_OPT_KEEP_ARGV0 = 4,
+       PARSE_OPT_KEEP_UNKNOWN = 8,
+       PARSE_OPT_NO_INTERNAL_HELP = 16,
 };
 
 enum parse_opt_option_flags {
@@ -50,7 +52,7 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
  *
  * `argh`::
  *   token to explain the kind of argument this option wants. Keep it
- *   homogenous across the repository.
+ *   homogeneous across the repository.
  *
  * `help`::
  *   the short help associated to what the option does.
@@ -59,7 +61,7 @@ typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
  *
  * `flags`::
  *   mask of parse_opt_option_flags.
- *   PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs)
+ *   PARSE_OPT_OPTARG: says that the argument is optional (not for BOOLEANs)
  *   PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs
  *   PARSE_OPT_NONEG: says that this option cannot be negated
  *   PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in
@@ -103,7 +105,7 @@ struct option {
        { OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }
 
 /* parse_options() will filter out the processed options and leave the
- * non-option argments in argv[].
+ * non-option arguments in argv[].
  * Returns the number of arguments left in argv[].
  */
 extern int parse_options(int argc, const char **argv,
@@ -113,7 +115,7 @@ extern int parse_options(int argc, const char **argv,
 extern NORETURN void usage_with_options(const char * const *usagestr,
                                         const struct option *options);
 
-/*----- incremantal advanced APIs -----*/
+/*----- incremental advanced APIs -----*/
 
 enum {
        PARSE_OPT_HELP = -1,
@@ -151,6 +153,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx);
 extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
 extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
 extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
+extern int parse_opt_with_commit(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var)  OPT_BOOLEAN('v', "verbose", (var), "be verbose")
 #define OPT__QUIET(var)    OPT_BOOLEAN('q', "quiet",   (var), "be quiet")
index 871f1d20c0e364220d23035b34685ced8737cda8..0df4cb086ba26d1f4d56b8347a6a7bcf219832f5 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "exec_cmd.h"
 
 static void flush_current_id(int patchlen, unsigned char *id, git_SHA_CTX *c)
 {
@@ -72,13 +73,15 @@ static void generate_id_list(void)
        flush_current_id(patchlen, sha1, &ctx);
 }
 
-static const char patch_id_usage[] = "git-patch-id < patch";
+static const char patch_id_usage[] = "git patch-id < patch";
 
 int main(int argc, char **argv)
 {
        if (argc != 1)
                usage(patch_id_usage);
 
+       git_extract_argv0_path(argv[0]);
+
        generate_id_list();
        return 0;
 }
index 3be5d3165e0009761a0ca69e15e4a9132c6cfaff..5717257051aceff129a4d0777c0a11bc156cae54 100644 (file)
@@ -1,6 +1,7 @@
 #include "cache.h"
 #include "diff.h"
 #include "commit.h"
+#include "sha1-lookup.h"
 #include "patch-ids.h"
 
 static int commit_patch_id(struct commit *commit, struct diff_options *options,
@@ -15,99 +16,15 @@ static int commit_patch_id(struct commit *commit, struct diff_options *options,
        return diff_flush_patch_id(options, sha1);
 }
 
-static uint32_t take2(const unsigned char *id)
+static const unsigned char *patch_id_access(size_t index, void *table)
 {
-       return ((id[0] << 8) | id[1]);
+       struct patch_id **id_table = table;
+       return id_table[index]->patch_id;
 }
 
-/*
- * Conventional binary search loop looks like this:
- *
- *      do {
- *              int mi = (lo + hi) / 2;
- *              int cmp = "entry pointed at by mi" minus "target";
- *              if (!cmp)
- *                      return (mi is the wanted one)
- *              if (cmp > 0)
- *                      hi = mi; "mi is larger than target"
- *              else
- *                      lo = mi+1; "mi is smaller than target"
- *      } while (lo < hi);
- *
- * The invariants are:
- *
- * - When entering the loop, lo points at a slot that is never
- *   above the target (it could be at the target), hi points at a
- *   slot that is guaranteed to be above the target (it can never
- *   be at the target).
- *
- * - We find a point 'mi' between lo and hi (mi could be the same
- *   as lo, but never can be the same as hi), and check if it hits
- *   the target.  There are three cases:
- *
- *    - if it is a hit, we are happy.
- *
- *    - if it is strictly higher than the target, we update hi with
- *      it.
- *
- *    - if it is strictly lower than the target, we update lo to be
- *      one slot after it, because we allow lo to be at the target.
- *
- * When choosing 'mi', we do not have to take the "middle" but
- * anywhere in between lo and hi, as long as lo <= mi < hi is
- * satisfied.  When we somehow know that the distance between the
- * target and lo is much shorter than the target and hi, we could
- * pick mi that is much closer to lo than the midway.
- */
 static int patch_pos(struct patch_id **table, int nr, const unsigned char *id)
 {
-       int hi = nr;
-       int lo = 0;
-       int mi = 0;
-
-       if (!nr)
-               return -1;
-
-       if (nr != 1) {
-               unsigned lov, hiv, miv, ofs;
-
-               for (ofs = 0; ofs < 18; ofs += 2) {
-                       lov = take2(table[0]->patch_id + ofs);
-                       hiv = take2(table[nr-1]->patch_id + ofs);
-                       miv = take2(id + ofs);
-                       if (miv < lov)
-                               return -1;
-                       if (hiv < miv)
-                               return -1 - nr;
-                       if (lov != hiv) {
-                               /*
-                                * At this point miv could be equal
-                                * to hiv (but id could still be higher);
-                                * the invariant of (mi < hi) should be
-                                * kept.
-                                */
-                               mi = (nr-1) * (miv - lov) / (hiv - lov);
-                               if (lo <= mi && mi < hi)
-                                       break;
-                               die("oops");
-                       }
-               }
-               if (18 <= ofs)
-                       die("cannot happen -- lo and hi are identical");
-       }
-
-       do {
-               int cmp;
-               cmp = hashcmp(table[mi]->patch_id, id);
-               if (!cmp)
-                       return mi;
-               if (cmp > 0)
-                       hi = mi;
-               else
-                       lo = mi + 1;
-               mi = (hi + lo) / 2;
-       } while (lo < hi);
-       return -lo-1;
+       return sha1_pos(id, table, nr, patch_id_access);
 }
 
 #define BUCKET_SIZE 190 /* 190 * 21 = 3990, with slop close enough to 4K */
diff --git a/path.c b/path.c
index 4b9107fed10c1f3551acf1f14d2ba5d1ba8a0b84..8a0a6741fd664f98f2883348c0a755d60616035b 100644 (file)
--- a/path.c
+++ b/path.c
@@ -311,36 +311,49 @@ char *enter_repo(char *path, int strict)
        return NULL;
 }
 
-int adjust_shared_perm(const char *path)
+int set_shared_perm(const char *path, int mode)
 {
        struct stat st;
-       int mode;
+       int tweak, shared, orig_mode;
 
-       if (!shared_repository)
+       if (!shared_repository) {
+               if (mode)
+                       return chmod(path, mode & ~S_IFMT);
                return 0;
-       if (lstat(path, &st) < 0)
-               return -1;
-       mode = st.st_mode;
-
-       if (shared_repository) {
-               int tweak = shared_repository;
-               if (!(mode & S_IWUSR))
-                       tweak &= ~0222;
-               mode |= tweak;
-       } else {
-               /* Preserve old PERM_UMASK behaviour */
-               if (mode & S_IWUSR)
-                       mode |= S_IWGRP;
        }
+       if (!mode) {
+               if (lstat(path, &st) < 0)
+                       return -1;
+               mode = st.st_mode;
+               orig_mode = mode;
+       } else
+               orig_mode = 0;
+       if (shared_repository < 0)
+               shared = -shared_repository;
+       else
+               shared = shared_repository;
+       tweak = shared;
+
+       if (!(mode & S_IWUSR))
+               tweak &= ~0222;
+       if (mode & S_IXUSR)
+               /* Copy read bits to execute bits */
+               tweak |= (tweak & 0444) >> 2;
+       if (shared_repository < 0)
+               mode = (mode & ~0777) | tweak;
+       else
+               mode |= tweak;
 
        if (S_ISDIR(mode)) {
-               mode |= FORCE_DIR_SET_GID;
-
                /* Copy read bits to execute bits */
-               mode |= (shared_repository & 0444) >> 2;
+               mode |= (shared & 0444) >> 2;
+               mode |= FORCE_DIR_SET_GID;
        }
 
-       if ((mode & st.st_mode) != mode && chmod(path, mode) < 0)
+       if (((shared_repository < 0
+             ? (orig_mode & (FORCE_DIR_SET_GID | 0777))
+             : (orig_mode & mode)) != mode) &&
+           chmod(path, (mode & ~S_IFMT)) < 0)
                return -2;
        return 0;
 }
@@ -499,3 +512,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 7d7f2b1d367b505676032878615b2843eb64ed7b..291ff5b53c1883ee8fce67fbb7e2b32393ef0800 100644 (file)
@@ -56,7 +56,7 @@ require Exporter;
 @EXPORT_OK = qw(command command_oneline command_noisy
                 command_output_pipe command_input_pipe command_close_pipe
                 command_bidi_pipe command_close_bidi_pipe
-                version exec_path hash_object git_cmd_try
+                version exec_path html_path hash_object git_cmd_try
                 remote_refs
                 temp_acquire temp_release temp_reset temp_path);
 
@@ -492,6 +492,16 @@ C<git --exec-path>). Useful mostly only internally.
 sub exec_path { command_oneline('--exec-path') }
 
 
+=item html_path ()
+
+Return path to the Git html documentation (the same as
+C<git --html-path>). Useful mostly only internally.
+
+=cut
+
+sub html_path { command_oneline('--html-path') }
+
+
 =item repo_path ()
 
 Return path to the git repository. Must be called on a repository instance.
index 421d9c5bca2224777808ccc06fe4912ba8793229..a0ef356558f4cdb148010f1b47dbd3fcc363d8ba 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -6,9 +6,19 @@
 #include "string-list.h"
 #include "mailmap.h"
 #include "log-tree.h"
+#include "color.h"
 
 static char *user_format;
 
+static void save_user_format(struct rev_info *rev, const char *cp, int is_tformat)
+{
+       free(user_format);
+       user_format = xstrdup(cp);
+       if (is_tformat)
+               rev->use_terminator = 1;
+       rev->commit_format = CMIT_FMT_USERFORMAT;
+}
+
 void get_commit_format(const char *arg, struct rev_info *rev)
 {
        int i;
@@ -32,12 +42,7 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                return;
        }
        if (!prefixcmp(arg, "format:") || !prefixcmp(arg, "tformat:")) {
-               const char *cp = strchr(arg, ':') + 1;
-               free(user_format);
-               user_format = xstrdup(cp);
-               if (arg[0] == 't')
-                       rev->use_terminator = 1;
-               rev->commit_format = CMIT_FMT_USERFORMAT;
+               save_user_format(rev, strchr(arg, ':') + 1, arg[0] == 't');
                return;
        }
        for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
@@ -49,6 +54,10 @@ void get_commit_format(const char *arg, struct rev_info *rev)
                        return;
                }
        }
+       if (strchr(arg, '%')) {
+               save_user_format(rev, arg, 1);
+               return;
+       }
 
        die("invalid --pretty format: %s", arg);
 }
@@ -74,8 +83,7 @@ static int get_one_line(const char *msg)
 /* High bit set, or ISO-2022-INT */
 int non_ascii(int ch)
 {
-       ch = (ch & 0xff);
-       return ((ch & 0x80) || (ch == 0x1b));
+       return !isascii(ch) || ch == '\033';
 }
 
 static int is_rfc2047_special(char ch)
@@ -127,7 +135,6 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
        int namelen;
        unsigned long time;
        int tz;
-       const char *filler = "    ";
 
        if (fmt == CMIT_FMT_ONELINE)
                return;
@@ -146,7 +153,6 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
                while (line < name_tail && isspace(name_tail[-1]))
                        name_tail--;
                display_name_length = name_tail - line;
-               filler = "";
                strbuf_addstr(sb, "From: ");
                add_rfc2047(sb, line, display_name_length, encoding);
                strbuf_add(sb, name_tail, namelen - display_name_length);
@@ -154,7 +160,7 @@ void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
        } else {
                strbuf_addf(sb, "%s: %.*s%.*s\n", what,
                              (fmt == CMIT_FMT_FULLER) ? 4 : 0,
-                             filler, namelen, line);
+                             "    ", namelen, line);
        }
        switch (fmt) {
        case CMIT_FMT_MEDIUM:
@@ -209,15 +215,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 +308,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 +326,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 +342,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;
        }
 
@@ -486,6 +493,40 @@ static void parse_commit_header(struct format_commit_context *context)
        context->commit_header_parsed = 1;
 }
 
+static int istitlechar(char c)
+{
+       return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ||
+               (c >= '0' && c <= '9') || c == '.' || c == '_';
+}
+
+static void format_sanitized_subject(struct strbuf *sb, const char *msg)
+{
+       size_t trimlen;
+       size_t start_len = sb->len;
+       int space = 2;
+
+       for (; *msg && *msg != '\n'; msg++) {
+               if (istitlechar(*msg)) {
+                       if (space == 1)
+                               strbuf_addch(sb, '-');
+                       space = 0;
+                       strbuf_addch(sb, *msg);
+                       if (*msg == '.')
+                               while (*(msg+1) == '.')
+                                       msg++;
+               } else
+                       space |= 1;
+       }
+
+       /* trim any trailing '.' or '-' characters */
+       trimlen = 0;
+       while (sb->len - trimlen > start_len &&
+               (sb->buf[sb->len - 1 - trimlen] == '.'
+               || sb->buf[sb->len - 1 - trimlen] == '-'))
+               trimlen++;
+       strbuf_remove(sb, sb->len - trimlen, trimlen);
+}
+
 const char *format_subject(struct strbuf *sb, const char *msg,
                           const char *line_separator)
 {
@@ -554,17 +595,28 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        /* these are independent of the commit */
        switch (placeholder[0]) {
        case 'C':
+               if (placeholder[1] == '(') {
+                       const char *end = strchr(placeholder + 2, ')');
+                       char color[COLOR_MAXLEN];
+                       if (!end)
+                               return 0;
+                       color_parse_mem(placeholder + 2,
+                                       end - (placeholder + 2),
+                                       "--pretty format", color);
+                       strbuf_addstr(sb, color);
+                       return end - placeholder + 1;
+               }
                if (!prefixcmp(placeholder + 1, "red")) {
-                       strbuf_addstr(sb, "\033[31m");
+                       strbuf_addstr(sb, GIT_COLOR_RED);
                        return 4;
                } else if (!prefixcmp(placeholder + 1, "green")) {
-                       strbuf_addstr(sb, "\033[32m");
+                       strbuf_addstr(sb, GIT_COLOR_GREEN);
                        return 6;
                } else if (!prefixcmp(placeholder + 1, "blue")) {
-                       strbuf_addstr(sb, "\033[34m");
+                       strbuf_addstr(sb, GIT_COLOR_BLUE);
                        return 5;
                } else if (!prefixcmp(placeholder + 1, "reset")) {
-                       strbuf_addstr(sb, "\033[m");
+                       strbuf_addstr(sb, GIT_COLOR_RESET);
                        return 6;
                } else
                        return 0;
@@ -665,6 +717,9 @@ static size_t format_commit_item(struct strbuf *sb, const char *placeholder,
        case 's':       /* subject */
                format_subject(sb, msg + c->subject_off, " ");
                return 1;
+       case 'f':       /* sanitized subject */
+               format_sanitized_subject(sb, msg + c->subject_off);
+               return 1;
        case 'b':       /* body */
                strbuf_addstr(sb, msg + c->body_off);
                return 1;
index 55a8687ad15788f8ea5a5beb463d216908f618b2..621c34edc2a3a6a9bceb0f708ed5b0df05145fb5 100644 (file)
@@ -121,13 +121,13 @@ static void throughput_string(struct throughput *tp, off_t total,
                              (int)(total >> 30),
                              (int)(total & ((1 << 30) - 1)) / 10737419);
        } else if (total > 1 << 20) {
+               int x = total + 5243;  /* for rounding */
                l -= snprintf(tp->display, l, ", %u.%2.2u MiB",
-                             (int)(total >> 20),
-                             ((int)(total & ((1 << 20) - 1)) * 100) >> 20);
+                             x >> 20, ((x & ((1 << 20) - 1)) * 100) >> 20);
        } else if (total > 1 << 10) {
+               int x = total + 5;  /* for rounding */
                l -= snprintf(tp->display, l, ", %u.%2.2u KiB",
-                             (int)(total >> 10),
-                             ((int)(total & ((1 << 10) - 1)) * 100) >> 10);
+                             x >> 10, ((x & ((1 << 10) - 1)) * 100) >> 10);
        } else {
                l -= snprintf(tp->display, l, ", %u bytes", (int)total);
        }
diff --git a/quote.c b/quote.c
index 6a520855d6c418ecb1384ef9571b122b134af1af..7a49fcf69671646a0d3ba6de6478cfc6767c31fe 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
        }
 }
 
-char *sq_dequote(char *arg)
+char *sq_dequote_step(char *arg, char **next)
 {
        char *dst = arg;
        char *src = arg;
@@ -92,6 +92,8 @@ char *sq_dequote(char *arg)
                switch (*++src) {
                case '\0':
                        *dst = 0;
+                       if (next)
+                               *next = NULL;
                        return arg;
                case '\\':
                        c = *++src;
@@ -101,11 +103,40 @@ char *sq_dequote(char *arg)
                        }
                /* Fallthrough */
                default:
-                       return NULL;
+                       if (!next || !isspace(*src))
+                               return NULL;
+                       do {
+                               c = *++src;
+                       } while (isspace(c));
+                       *dst = 0;
+                       *next = src;
+                       return arg;
                }
        }
 }
 
+char *sq_dequote(char *arg)
+{
+       return sq_dequote_step(arg, NULL);
+}
+
+int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
+{
+       char *next = arg;
+
+       if (!*arg)
+               return 0;
+       do {
+               char *dequoted = sq_dequote_step(next, &next);
+               if (!dequoted)
+                       return -1;
+               ALLOC_GROW(*argv, *nr + 1, *alloc);
+               (*argv)[(*nr)++] = dequoted;
+       } while (next);
+
+       return 0;
+}
+
 /* 1 means: quote as octal
  * 0 means: quote as octal if (quote_path_fully)
  * -1 means: never quote
diff --git a/quote.h b/quote.h
index c5eea6f18e2dfabd071b73e6507c34c2b7b5e39f..66730f2bff3cee42bc7c670e2a6d7da240db1d08 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -39,6 +39,15 @@ extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen);
  */
 extern char *sq_dequote(char *);
 
+/*
+ * Same as the above, but can be used to unwrap many arguments in the
+ * same string separated by space. "next" is changed to point to the
+ * next argument that should be passed as first parameter. When there
+ * is no more argument to be dequoted, "next" is updated to point to NULL.
+ */
+extern char *sq_dequote_step(char *arg, char **next);
+extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
+
 extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
 extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq);
 extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);
index 940ec76fdf231ac1345079ca2dc5da88925bcfb6..3f587110cb9d7be1890b7db68a0bdac35d48cd35 100644 (file)
@@ -67,8 +67,10 @@ void rename_index_entry_at(struct index_state *istate, int nr, const char *new_n
  */
 void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
 {
-       ce->ce_ctime = st->st_ctime;
-       ce->ce_mtime = st->st_mtime;
+       ce->ce_ctime.sec = (unsigned int)st->st_ctime;
+       ce->ce_mtime.sec = (unsigned int)st->st_mtime;
+       ce->ce_ctime.nsec = ST_CTIME_NSEC(*st);
+       ce->ce_mtime.nsec = ST_MTIME_NSEC(*st);
        ce->ce_dev = st->st_dev;
        ce->ce_ino = st->st_ino;
        ce->ce_uid = st->st_uid;
@@ -196,11 +198,18 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
        default:
                die("internal error: ce_mode is %o", ce->ce_mode);
        }
-       if (ce->ce_mtime != (unsigned int) st->st_mtime)
+       if (ce->ce_mtime.sec != (unsigned int)st->st_mtime)
                changed |= MTIME_CHANGED;
-       if (trust_ctime && ce->ce_ctime != (unsigned int) st->st_ctime)
+       if (trust_ctime && ce->ce_ctime.sec != (unsigned int)st->st_ctime)
                changed |= CTIME_CHANGED;
 
+#ifdef USE_NSEC
+       if (ce->ce_mtime.nsec != ST_MTIME_NSEC(*st))
+               changed |= MTIME_CHANGED;
+       if (trust_ctime && ce->ce_ctime.nsec != ST_CTIME_NSEC(*st))
+               changed |= CTIME_CHANGED;
+#endif
+
        if (ce->ce_uid != (unsigned int) st->st_uid ||
            ce->ce_gid != (unsigned int) st->st_gid)
                changed |= OWNER_CHANGED;
@@ -232,8 +241,16 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
 static int is_racy_timestamp(const struct index_state *istate, struct cache_entry *ce)
 {
        return (!S_ISGITLINK(ce->ce_mode) &&
-               istate->timestamp &&
-               ((unsigned int)istate->timestamp) <= ce->ce_mtime);
+               istate->timestamp.sec &&
+#ifdef USE_NSEC
+                /* nanosecond timestamped files can also be racy! */
+               (istate->timestamp.sec < ce->ce_mtime.sec ||
+                (istate->timestamp.sec == ce->ce_mtime.sec &&
+                 istate->timestamp.nsec <= ce->ce_mtime.nsec))
+#else
+               istate->timestamp.sec <= ce->ce_mtime.sec
+#endif
+                );
 }
 
 int ie_match_stat(const struct index_state *istate,
@@ -443,6 +460,26 @@ int remove_index_entry_at(struct index_state *istate, int pos)
        return 1;
 }
 
+/*
+ * Remove all cache ententries marked for removal, that is where
+ * CE_REMOVE is set in ce_flags.  This is much more effective than
+ * calling remove_index_entry_at() for each entry to be removed.
+ */
+void remove_marked_cache_entries(struct index_state *istate)
+{
+       struct cache_entry **ce_array = istate->cache;
+       unsigned int i, j;
+
+       for (i = j = 0; i < istate->cache_nr; i++) {
+               if (ce_array[i]->ce_flags & CE_REMOVE)
+                       remove_name_hash(ce_array[i]);
+               else
+                       ce_array[j++] = ce_array[i];
+       }
+       istate->cache_changed = 1;
+       istate->cache_nr = j;
+}
+
 int remove_file_from_index(struct index_state *istate, const char *path)
 {
        int pos = index_name_pos(istate, path, strlen(path));
@@ -1139,8 +1176,10 @@ static void convert_from_disk(struct ondisk_cache_entry *ondisk, struct cache_en
        size_t len;
        const char *name;
 
-       ce->ce_ctime = ntohl(ondisk->ctime.sec);
-       ce->ce_mtime = ntohl(ondisk->mtime.sec);
+       ce->ce_ctime.sec = ntohl(ondisk->ctime.sec);
+       ce->ce_mtime.sec = ntohl(ondisk->mtime.sec);
+       ce->ce_ctime.nsec = ntohl(ondisk->ctime.nsec);
+       ce->ce_mtime.nsec = ntohl(ondisk->mtime.nsec);
        ce->ce_dev   = ntohl(ondisk->dev);
        ce->ce_ino   = ntohl(ondisk->ino);
        ce->ce_mode  = ntohl(ondisk->mode);
@@ -1206,7 +1245,8 @@ int read_index_from(struct index_state *istate, const char *path)
                return istate->cache_nr;
 
        errno = ENOENT;
-       istate->timestamp = 0;
+       istate->timestamp.sec = 0;
+       istate->timestamp.nsec = 0;
        fd = open(path, O_RDONLY);
        if (fd < 0) {
                if (errno == ENOENT)
@@ -1258,7 +1298,9 @@ int read_index_from(struct index_state *istate, const char *path)
                src_offset += ondisk_ce_size(ce);
                dst_offset += ce_size(ce);
        }
-       istate->timestamp = st.st_mtime;
+       istate->timestamp.sec = st.st_mtime;
+       istate->timestamp.nsec = ST_MTIME_NSEC(st);
+
        while (src_offset <= mmap_size - 20 - 8) {
                /* After an array of active_nr index entries,
                 * there can be arbitrary number of extended
@@ -1288,14 +1330,15 @@ unmap:
 
 int is_index_unborn(struct index_state *istate)
 {
-       return (!istate->cache_nr && !istate->alloc && !istate->timestamp);
+       return (!istate->cache_nr && !istate->alloc && !istate->timestamp.sec);
 }
 
 int discard_index(struct index_state *istate)
 {
        istate->cache_nr = 0;
        istate->cache_changed = 0;
-       istate->timestamp = 0;
+       istate->timestamp.sec = 0;
+       istate->timestamp.nsec = 0;
        istate->name_hash_initialized = 0;
        free_hash(&istate->name_hash);
        cache_tree_free(&(istate->cache_tree));
@@ -1441,10 +1484,10 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
        struct ondisk_cache_entry *ondisk = xcalloc(1, size);
        char *name;
 
-       ondisk->ctime.sec = htonl(ce->ce_ctime);
-       ondisk->ctime.nsec = 0;
-       ondisk->mtime.sec = htonl(ce->ce_mtime);
-       ondisk->mtime.nsec = 0;
+       ondisk->ctime.sec = htonl(ce->ce_ctime.sec);
+       ondisk->mtime.sec = htonl(ce->ce_mtime.sec);
+       ondisk->ctime.nsec = htonl(ce->ce_ctime.nsec);
+       ondisk->mtime.nsec = htonl(ce->ce_mtime.nsec);
        ondisk->dev  = htonl(ce->ce_dev);
        ondisk->ino  = htonl(ce->ce_ino);
        ondisk->mode = htonl(ce->ce_mode);
@@ -1466,13 +1509,14 @@ static int ce_write_entry(git_SHA_CTX *c, int fd, struct cache_entry *ce)
        return ce_write(c, fd, ondisk, size);
 }
 
-int write_index(const struct index_state *istate, int newfd)
+int write_index(struct index_state *istate, int newfd)
 {
        git_SHA_CTX c;
        struct cache_header hdr;
        int i, err, removed, extended;
        struct cache_entry **cache = istate->cache;
        int entries = istate->cache_nr;
+       struct stat st;
 
        for (i = removed = extended = 0; i < entries; i++) {
                if (cache[i]->ce_flags & CE_REMOVE)
@@ -1516,7 +1560,12 @@ int write_index(const struct index_state *istate, int newfd)
                if (err)
                        return -1;
        }
-       return ce_flush(&c, newfd);
+
+       if (ce_flush(&c, newfd) || fstat(newfd, &st))
+               return -1;
+       istate->timestamp.sec = (unsigned int)st.st_mtime;
+       istate->timestamp.nsec = ST_MTIME_NSEC(st);
+       return 0;
 }
 
 /*
index f751fdc8d832cae54647c1a70d888e979d324fd8..5623ea6b48a2f355e8866eabb2805b51b4127a4a 100644 (file)
@@ -241,8 +241,8 @@ void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
        commit->object.flags &= ~(ADDED | SEEN | SHOWN);
 }
 
-void show_reflog_message(struct reflog_walk_infoinfo, int oneline,
-       int relative_date)
+void show_reflog_message(struct reflog_walk_info *info, int oneline,
+       enum date_mode dmode)
 {
        if (info && info->last_commit_reflog) {
                struct commit_reflog *commit_reflog = info->last_commit_reflog;
@@ -251,8 +251,10 @@ void show_reflog_message(struct reflog_walk_info* info, int oneline,
                info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
                if (oneline) {
                        printf("%s@{", commit_reflog->reflogs->ref);
-                       if (commit_reflog->flag || relative_date)
-                               printf("%s", show_date(info->timestamp, 0, 1));
+                       if (commit_reflog->flag || dmode)
+                               printf("%s", show_date(info->timestamp,
+                                                      info->tz,
+                                                      dmode));
                        else
                                printf("%d", commit_reflog->reflogs->nr
                                       - 2 - commit_reflog->recno);
@@ -260,10 +262,10 @@ void show_reflog_message(struct reflog_walk_info* info, int oneline,
                }
                else {
                        printf("Reflog: %s@{", commit_reflog->reflogs->ref);
-                       if (commit_reflog->flag || relative_date)
+                       if (commit_reflog->flag || dmode)
                                printf("%s", show_date(info->timestamp,
                                                        info->tz,
-                                                       relative_date));
+                                                       dmode));
                        else
                                printf("%d", commit_reflog->reflogs->nr
                                       - 2 - commit_reflog->recno);
index 7ca1438f4d74b652f962c6bdfddd08fe0d75802d..74c90964bd95dcd97a5acde83c83b2f3b61c2441 100644 (file)
@@ -1,11 +1,14 @@
 #ifndef REFLOG_WALK_H
 #define REFLOG_WALK_H
 
+#include "cache.h"
+
 extern void init_reflog_walk(struct reflog_walk_info** info);
 extern int add_reflog_for_walk(struct reflog_walk_info *info,
                struct commit *commit, const char *name);
 extern void fake_reflog_parent(struct reflog_walk_info *info,
                struct commit *commit);
-extern void show_reflog_message(struct reflog_walk_info *info, int, int);
+extern void show_reflog_message(struct reflog_walk_info *info, int,
+               enum date_mode);
 
 #endif
diff --git a/refs.c b/refs.c
index 33ced65a7801f8653d608a9580e237ce8df39ae2..90163bdc56868151e4fd45ff77aa3bd56db45573 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,33 @@ 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_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_ref(prefix, fn, strlen(prefix), 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 for_each_ref_in("refs/tags/", fn, 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 for_each_ref_in("refs/heads/", fn, 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 for_each_ref_in("refs/remotes/", fn, 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);
 }
 
 /*
@@ -640,6 +681,7 @@ int for_each_remote_ref(each_ref_fn fn, void *cb_data)
  * - it has double dots "..", or
  * - it has ASCII control character, "~", "^", ":" or SP, anywhere, or
  * - it ends with a "/".
+ * - it ends with ".lock"
  */
 
 static inline int bad_ref_char(int ch)
@@ -657,7 +699,8 @@ static inline int bad_ref_char(int ch)
 
 int check_ref_format(const char *ref)
 {
-       int ch, level, bad_type;
+       int ch, level, bad_type, last;
+       int ret = CHECK_REF_FORMAT_OK;
        const char *cp = ref;
 
        level = 0;
@@ -673,33 +716,50 @@ int check_ref_format(const char *ref)
                        return CHECK_REF_FORMAT_ERROR;
                bad_type = bad_ref_char(ch);
                if (bad_type) {
-                       return (bad_type == 2 && !*cp)
-                               ? CHECK_REF_FORMAT_WILDCARD
-                               : CHECK_REF_FORMAT_ERROR;
+                       if (bad_type == 2 && (!*cp || *cp == '/') &&
+                           ret == CHECK_REF_FORMAT_OK)
+                               ret = CHECK_REF_FORMAT_WILDCARD;
+                       else
+                               return CHECK_REF_FORMAT_ERROR;
                }
 
+               last = ch;
                /* scan the rest of the path component */
                while ((ch = *cp++) != 0) {
                        bad_type = bad_ref_char(ch);
-                       if (bad_type) {
-                               return (bad_type == 2 && !*cp)
-                                       ? CHECK_REF_FORMAT_WILDCARD
-                                       : CHECK_REF_FORMAT_ERROR;
-                       }
+                       if (bad_type)
+                               return CHECK_REF_FORMAT_ERROR;
                        if (ch == '/')
                                break;
-                       if (ch == '.' && *cp == '.')
+                       if (last == '.' && ch == '.')
                                return CHECK_REF_FORMAT_ERROR;
+                       if (last == '@' && ch == '{')
+                               return CHECK_REF_FORMAT_ERROR;
+                       last = ch;
                }
                level++;
                if (!ch) {
+                       if (ref <= cp - 2 && cp[-2] == '.')
+                               return CHECK_REF_FORMAT_ERROR;
                        if (level < 2)
                                return CHECK_REF_FORMAT_ONELEVEL;
-                       return CHECK_REF_FORMAT_OK;
+                       if (has_extension(ref, ".lock"))
+                               return CHECK_REF_FORMAT_ERROR;
+                       return ret;
                }
        }
 }
 
+const char *prettify_ref(const struct ref *ref)
+{
+       const char *name = ref->name;
+       return name + (
+               !prefixcmp(name, "refs/heads/") ? 11 :
+               !prefixcmp(name, "refs/tags/") ? 10 :
+               !prefixcmp(name, "refs/remotes/") ? 13 :
+               0);
+}
+
 const char *ref_rev_parse_rules[] = {
        "%.*s",
        "refs/%.*s",
@@ -833,8 +893,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
         * name is a proper prefix of our refname.
         */
        if (missing &&
-            !is_refname_available(ref, NULL, get_packed_refs(), 0))
+            !is_refname_available(ref, NULL, get_packed_refs(), 0)) {
+               last_errno = ENOTDIR;
                goto error_return;
+       }
 
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
@@ -942,12 +1004,10 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
                } else {
                        path = git_path("%s", refname);
                }
-               err = unlink(path);
-               if (err && errno != ENOENT) {
+               err = unlink_or_warn(path);
+               if (err && errno != ENOENT)
                        ret = 1;
-                       error("unlink(%s) failed: %s",
-                             path, strerror(errno));
-               }
+
                if (!(delopt & REF_NODEREF))
                        lock->lk->filename[i] = '.';
        }
@@ -957,10 +1017,7 @@ int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
         */
        ret |= repack_without_ref(refname);
 
-       err = unlink(git_path("logs/%s", lock->ref_name));
-       if (err && errno != ENOENT)
-               fprintf(stderr, "warning: unlink(%s) failed: %s",
-                       git_path("logs/%s", lock->ref_name), strerror(errno));
+       unlink_or_warn(git_path("logs/%s", lock->ref_name));
        invalidate_cached_refs();
        unlock_ref(lock);
        return ret;
@@ -1321,7 +1378,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
        if (adjust_shared_perm(git_HEAD)) {
                error("Unable to fix permissions on %s", lockpath);
        error_unlink_return:
-               unlink(lockpath);
+               unlink_or_warn(lockpath);
        error_free_return:
                free(git_HEAD);
                return -1;
@@ -1401,8 +1458,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (get_sha1_hex(rec + 41, sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       fprintf(stderr,
-                                               "warning: Log %s has gap after %s.\n",
+                                       warning("Log %s has gap after %s.",
                                                logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }
@@ -1414,8 +1470,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (get_sha1_hex(rec + 41, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       fprintf(stderr,
-                                               "warning: Log %s unexpectedly ended on %s.\n",
+                                       warning("Log %s unexpectedly ended on %s.",
                                                logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }
@@ -1453,7 +1508,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 +1519,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 +1562,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));
@@ -1577,10 +1647,121 @@ int update_ref(const char *action, const char *refname,
        return 0;
 }
 
-struct ref *find_ref_by_name(struct ref *list, const char *name)
+struct ref *find_ref_by_name(const struct ref *list, const char *name)
 {
        for ( ; list; list = list->next)
                if (!strcmp(list->name, name))
-                       return list;
+                       return (struct ref *)list;
        return NULL;
 }
+
+/*
+ * generate a format suitable for scanf from a ref_rev_parse_rules
+ * rule, that is replace the "%.*s" spec with a "%s" spec
+ */
+static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
+{
+       char *spec;
+
+       spec = strstr(rule, "%.*s");
+       if (!spec || strstr(spec + 4, "%.*s"))
+               die("invalid rule in ref_rev_parse_rules: %s", rule);
+
+       /* copy all until spec */
+       strncpy(scanf_fmt, rule, spec - rule);
+       scanf_fmt[spec - rule] = '\0';
+       /* copy new spec */
+       strcat(scanf_fmt, "%s");
+       /* copy remaining rule */
+       strcat(scanf_fmt, spec + 4);
+
+       return;
+}
+
+char *shorten_unambiguous_ref(const char *ref, int strict)
+{
+       int i;
+       static char **scanf_fmts;
+       static int nr_rules;
+       char *short_name;
+
+       /* pre generate scanf formats from ref_rev_parse_rules[] */
+       if (!nr_rules) {
+               size_t total_len = 0;
+
+               /* the rule list is NULL terminated, count them first */
+               for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
+                       /* no +1 because strlen("%s") < strlen("%.*s") */
+                       total_len += strlen(ref_rev_parse_rules[nr_rules]);
+
+               scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
+
+               total_len = 0;
+               for (i = 0; i < nr_rules; i++) {
+                       scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
+                                       + total_len;
+                       gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
+                       total_len += strlen(ref_rev_parse_rules[i]);
+               }
+       }
+
+       /* bail out if there are no rules */
+       if (!nr_rules)
+               return xstrdup(ref);
+
+       /* buffer for scanf result, at most ref must fit */
+       short_name = xstrdup(ref);
+
+       /* skip first rule, it will always match */
+       for (i = nr_rules - 1; i > 0 ; --i) {
+               int j;
+               int rules_to_fail = i;
+               int short_name_len;
+
+               if (1 != sscanf(ref, scanf_fmts[i], short_name))
+                       continue;
+
+               short_name_len = strlen(short_name);
+
+               /*
+                * in strict mode, all (except the matched one) rules
+                * must fail to resolve to a valid non-ambiguous ref
+                */
+               if (strict)
+                       rules_to_fail = nr_rules;
+
+               /*
+                * check if the short name resolves to a valid ref,
+                * but use only rules prior to the matched one
+                */
+               for (j = 0; j < rules_to_fail; j++) {
+                       const char *rule = ref_rev_parse_rules[j];
+                       unsigned char short_objectname[20];
+                       char refname[PATH_MAX];
+
+                       /* skip matched rule */
+                       if (i == j)
+                               continue;
+
+                       /*
+                        * the short name is ambiguous, if it resolves
+                        * (with this previous rule) to a valid ref
+                        * read_ref() returns 0 on success
+                        */
+                       mksnpath(refname, sizeof(refname),
+                                rule, short_name_len, short_name);
+                       if (!read_ref(refname, short_objectname))
+                               break;
+               }
+
+               /*
+                * short name is non-ambiguous if all previous rules
+                * haven't resolved to a valid ref
+                */
+               if (j == rules_to_fail)
+                       return short_name;
+       }
+
+       free(short_name);
+       return xstrdup(ref);
+}
diff --git a/refs.h b/refs.h
index 06ad26055661a9b9e475d0f8a7bd6d1cfb42e792..29d17a48e4a2923bc72337deb1ef64cf7b467381 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -20,10 +20,16 @@ struct ref_lock {
 typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
 extern int head_ref(each_ref_fn, void *);
 extern int for_each_ref(each_ref_fn, void *);
+extern int for_each_ref_in(const char *, each_ref_fn, void *);
 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 +66,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,
@@ -73,6 +80,9 @@ extern int for_each_reflog(each_ref_fn, void *);
 #define CHECK_REF_FORMAT_WILDCARD (-3)
 extern int check_ref_format(const char *target);
 
+extern const char *prettify_ref(const struct ref *ref);
+extern char *shorten_unambiguous_ref(const char *ref, int strict);
+
 /** rename ref, return 0 on success **/
 extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
 
index 570e11286ea295e825a23b1d5ed40c9fbd02be57..d66e2f3c93dc72a7112ce101278ae937cc914320 100644 (file)
--- a/remote.c
+++ b/remote.c
@@ -4,13 +4,15 @@
 #include "commit.h"
 #include "diff.h"
 #include "revision.h"
+#include "dir.h"
+#include "tag.h"
 
 static struct refspec s_tag_refspec = {
        0,
        1,
        0,
-       "refs/tags/",
-       "refs/tags/"
+       "refs/tags/*",
+       "refs/tags/*"
 };
 
 const struct refspec *tag_refspec = &s_tag_refspec;
@@ -37,6 +39,7 @@ static int branches_nr;
 
 static struct branch *current_branch;
 static const char *default_remote_name;
+static int explicit_default_remote_name;
 
 static struct rewrite **rewrite;
 static int rewrite_alloc;
@@ -329,8 +332,10 @@ static int handle_config(const char *key, const char *value, void *cb)
                        if (!value)
                                return config_error_nonbool(key);
                        branch->remote_name = xstrdup(value);
-                       if (branch == current_branch)
+                       if (branch == current_branch) {
                                default_remote_name = branch->remote_name;
+                               explicit_default_remote_name = 1;
+                       }
                } else if (!strcmp(subkey, ".merge")) {
                        if (!value)
                                return config_error_nonbool(key);
@@ -361,7 +366,7 @@ static int handle_config(const char *key, const char *value, void *cb)
        }
        subkey = strrchr(name, '.');
        if (!subkey)
-               return error("Config with no key for remote %s", name);
+               return 0;
        remote = make_remote(name, subkey - name);
        remote->origin = REMOTE_CONFIG;
        if (!strcmp(subkey, ".mirror"))
@@ -450,16 +455,11 @@ static void read_config(void)
  */
 static int verify_refname(char *name, int is_glob)
 {
-       int result, len = -1;
+       int result;
 
-       if (is_glob) {
-               len = strlen(name);
-               assert(name[len - 1] == '/');
-               name[len - 1] = '\0';
-       }
        result = check_ref_format(name);
-       if (is_glob)
-               name[len - 1] = '/';
+       if (is_glob && result == CHECK_REF_FORMAT_WILDCARD)
+               result = CHECK_REF_FORMAT_OK;
        return result;
 }
 
@@ -494,7 +494,7 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
                int is_glob;
                const char *lhs, *rhs;
 
-               llen = is_glob = 0;
+               is_glob = 0;
 
                lhs = refspec[i];
                if (*lhs == '+') {
@@ -515,16 +515,15 @@ static struct refspec *parse_refspec_internal(int nr_refspec, const char **refsp
 
                if (rhs) {
                        size_t rlen = strlen(++rhs);
-                       is_glob = (2 <= rlen && !strcmp(rhs + rlen - 2, "/*"));
-                       rs[i].dst = xstrndup(rhs, rlen - is_glob);
+                       is_glob = (1 <= rlen && strchr(rhs, '*'));
+                       rs[i].dst = xstrndup(rhs, rlen);
                }
 
                llen = (rhs ? (rhs - lhs - 1) : strlen(lhs));
-               if (2 <= llen && !memcmp(lhs + llen - 2, "/*", 2)) {
+               if (1 <= llen && memchr(lhs, '*', llen)) {
                        if ((rhs && !is_glob) || (!rhs && fetch))
                                goto invalid;
                        is_glob = 1;
-                       llen--;
                } else if (rhs && is_glob) {
                        goto invalid;
                }
@@ -634,10 +633,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 */
 }
@@ -645,10 +641,16 @@ static int valid_remote_nick(const char *name)
 struct remote *remote_get(const char *name)
 {
        struct remote *ret;
+       int name_given = 0;
 
        read_config();
-       if (!name)
+       if (name)
+               name_given = 1;
+       else {
                name = default_remote_name;
+               name_given = explicit_default_remote_name;
+       }
+
        ret = make_remote(name, 0);
        if (valid_remote_nick(name)) {
                if (!ret->url)
@@ -656,7 +658,7 @@ struct remote *remote_get(const char *name)
                if (!ret->url)
                        read_branches_file(ret);
        }
-       if (!ret->url)
+       if (name_given && !ret->url)
                add_url_alias(ret, name);
        if (!ret->url)
                return NULL;
@@ -665,6 +667,17 @@ struct remote *remote_get(const char *name)
        return ret;
 }
 
+int remote_is_configured(const char *name)
+{
+       int i;
+       read_config();
+
+       for (i = 0; i < remotes_nr; i++)
+               if (!strcmp(name, remotes[i]->name))
+                       return 1;
+       return 0;
+}
+
 int for_each_remote(each_remote_fn fn, void *priv)
 {
        int i, result = 0;
@@ -721,6 +734,41 @@ int remote_has_url(struct remote *remote, const char *url)
        return 0;
 }
 
+static int match_name_with_pattern(const char *key, const char *name,
+                                  const char *value, char **result)
+{
+       const char *kstar = strchr(key, '*');
+       size_t klen;
+       size_t ksuffixlen;
+       size_t namelen;
+       int ret;
+       if (!kstar)
+               die("Key '%s' of pattern had no '*'", key);
+       klen = kstar - key;
+       ksuffixlen = strlen(kstar + 1);
+       namelen = strlen(name);
+       ret = !strncmp(name, key, klen) && namelen >= klen + ksuffixlen &&
+               !memcmp(name + namelen - ksuffixlen, kstar + 1, ksuffixlen);
+       if (ret && value) {
+               const char *vstar = strchr(value, '*');
+               size_t vlen;
+               size_t vsuffixlen;
+               if (!vstar)
+                       die("Value '%s' of pattern has no '*'", value);
+               vlen = vstar - value;
+               vsuffixlen = strlen(vstar + 1);
+               *result = xmalloc(vlen + vsuffixlen +
+                                 strlen(name) -
+                                 klen - ksuffixlen + 1);
+               strncpy(*result, value, vlen);
+               strncpy(*result + vlen,
+                       name + klen, namelen - klen - ksuffixlen);
+               strcpy(*result + vlen + namelen - klen - ksuffixlen,
+                      vstar + 1);
+       }
+       return ret;
+}
+
 int remote_find_tracking(struct remote *remote, struct refspec *refspec)
 {
        int find_src = refspec->src == NULL;
@@ -744,13 +792,7 @@ int remote_find_tracking(struct remote *remote, struct refspec *refspec)
                if (!fetch->dst)
                        continue;
                if (fetch->pattern) {
-                       if (!prefixcmp(needle, key)) {
-                               *result = xmalloc(strlen(value) +
-                                                 strlen(needle) -
-                                                 strlen(key) + 1);
-                               strcpy(*result, value);
-                               strcpy(*result + strlen(value),
-                                      needle + strlen(key));
+                       if (match_name_with_pattern(key, needle, value, result)) {
                                refspec->force = fetch->force;
                                return 0;
                        }
@@ -780,10 +822,18 @@ struct ref *alloc_ref(const char *name)
 
 static struct ref *copy_ref(const struct ref *ref)
 {
-       struct ref *ret = xmalloc(sizeof(struct ref) + strlen(ref->name) + 1);
-       memcpy(ret, ref, sizeof(struct ref) + strlen(ref->name) + 1);
-       ret->next = NULL;
-       return ret;
+       struct ref *cpy;
+       size_t len;
+       if (!ref)
+               return NULL;
+       len = strlen(ref->name);
+       cpy = xmalloc(sizeof(struct ref) + len + 1);
+       memcpy(cpy, ref, sizeof(struct ref) + len + 1);
+       cpy->next = NULL;
+       cpy->symref = ref->symref ? xstrdup(ref->symref) : NULL;
+       cpy->remote_status = ref->remote_status ? xstrdup(ref->remote_status) : NULL;
+       cpy->peer_ref = copy_ref(ref->peer_ref);
+       return cpy;
 }
 
 struct ref *copy_ref_list(const struct ref *ref)
@@ -802,6 +852,7 @@ static void free_ref(struct ref *ref)
 {
        if (!ref)
                return;
+       free_ref(ref->peer_ref);
        free(ref->remote_status);
        free(ref->symref);
        free(ref);
@@ -812,7 +863,6 @@ void free_refs(struct ref *ref)
        struct ref *next;
        while (ref) {
                next = ref->next;
-               free(ref->peer_ref);
                free_ref(ref);
                ref = next;
        }
@@ -929,6 +979,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
                          struct refspec *rs)
 {
        struct ref *matched_src, *matched_dst;
+       int copy_src;
 
        const char *dst_value = rs->dst;
        char *dst_guess;
@@ -939,6 +990,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
        matched_src = matched_dst = NULL;
        switch (count_refspec_match(rs->src, src, &matched_src)) {
        case 1:
+               copy_src = 1;
                break;
        case 0:
                /* The source could be in the get_sha1() format
@@ -948,6 +1000,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
                matched_src = try_explicit_object_name(rs->src);
                if (!matched_src)
                        return error("src refspec %s does not match any.", rs->src);
+               copy_src = 0;
                break;
        default:
                return error("src refspec %s matches more than one.", rs->src);
@@ -993,7 +1046,7 @@ static int match_explicit(struct ref *src, struct ref *dst,
                return error("dst ref %s receives from more than one src.",
                      matched_dst->name);
        else {
-               matched_dst->peer_ref = matched_src;
+               matched_dst->peer_ref = copy_src ? copy_ref(matched_src) : matched_src;
                matched_dst->force = rs->force;
        }
        return 0;
@@ -1022,7 +1075,8 @@ static const struct refspec *check_pattern_match(const struct refspec *rs,
                        continue;
                }
 
-               if (rs[i].pattern && !prefixcmp(src->name, rs[i].src))
+               if (rs[i].pattern && match_name_with_pattern(rs[i].src, src->name,
+                                                            NULL, NULL))
                        return rs + i;
        }
        if (matching_refs != -1)
@@ -1042,6 +1096,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
        struct refspec *rs;
        int send_all = flags & MATCH_REFS_ALL;
        int send_mirror = flags & MATCH_REFS_MIRROR;
+       int errs;
        static const char *default_refspec[] = { ":", 0 };
 
        if (!nr_refspec) {
@@ -1049,8 +1104,7 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                refspec = default_refspec;
        }
        rs = parse_push_refspec(nr_refspec, (const char **) refspec);
-       if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
-               return -1;
+       errs = match_explicit_refs(src, dst, dst_tail, rs, nr_refspec);
 
        /* pick the remainder */
        for ( ; src; src = src->next) {
@@ -1076,11 +1130,9 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
 
                } else {
                        const char *dst_side = pat->dst ? pat->dst : pat->src;
-                       dst_name = xmalloc(strlen(dst_side) +
-                                          strlen(src->name) -
-                                          strlen(pat->src) + 2);
-                       strcpy(dst_name, dst_side);
-                       strcat(dst_name, src->name + strlen(pat->src));
+                       if (!match_name_with_pattern(pat->src, src->name,
+                                                    dst_side, &dst_name))
+                               die("Didn't think it matches any more");
                }
                dst_peer = find_ref_by_name(dst, dst_name);
                if (dst_peer) {
@@ -1101,11 +1153,13 @@ int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
                        dst_peer = make_linked_ref(dst_name, dst_tail);
                        hashcpy(dst_peer->new_sha1, src->new_sha1);
                }
-               dst_peer->peer_ref = src;
+               dst_peer->peer_ref = copy_ref(src);
                dst_peer->force = pat->force;
        free_name:
                free(dst_name);
        }
+       if (errs)
+               return -1;
        return 0;
 }
 
@@ -1127,8 +1181,9 @@ struct branch *branch_get(const char *name)
                        for (i = 0; i < ret->merge_nr; i++) {
                                ret->merge[i] = xcalloc(1, sizeof(**ret->merge));
                                ret->merge[i]->src = xstrdup(ret->merge_name[i]);
-                               remote_find_tracking(ret->remote,
-                                                    ret->merge[i]);
+                               if (remote_find_tracking(ret->remote, ret->merge[i])
+                                   && !strcmp(ret->remote_name, "."))
+                                       ret->merge[i]->dst = xstrdup(ret->merge_name[i]);
                        }
                }
        }
@@ -1156,19 +1211,17 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,
        struct ref *ret = NULL;
        struct ref **tail = &ret;
 
-       int remote_prefix_len = strlen(refspec->src);
-       int local_prefix_len = strlen(refspec->dst);
+       char *expn_name;
 
        for (ref = remote_refs; ref; ref = ref->next) {
                if (strchr(ref->name, '^'))
                        continue; /* a dereference item */
-               if (!prefixcmp(ref->name, refspec->src)) {
-                       const char *match;
+               if (match_name_with_pattern(refspec->src, ref->name,
+                                           refspec->dst, &expn_name)) {
                        struct ref *cpy = copy_ref(ref);
-                       match = ref->name + remote_prefix_len;
 
-                       cpy->peer_ref = alloc_ref_with_prefix(refspec->dst,
-                                       local_prefix_len, match);
+                       cpy->peer_ref = alloc_ref(expn_name);
+                       free(expn_name);
                        if (refspec->force)
                                cpy->peer_ref->force = 1;
                        *tail = cpy;
@@ -1271,6 +1324,54 @@ int resolve_remote_symref(struct ref *ref, struct ref *list)
        return 1;
 }
 
+static void unmark_and_free(struct commit_list *list, unsigned int mark)
+{
+       while (list) {
+               struct commit_list *temp = list;
+               temp->item->object.flags &= ~mark;
+               list = temp->next;
+               free(temp);
+       }
+}
+
+int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1)
+{
+       struct object *o;
+       struct commit *old, *new;
+       struct commit_list *list, *used;
+       int found = 0;
+
+       /* Both new and old must be commit-ish and new is descendant of
+        * old.  Otherwise we require --force.
+        */
+       o = deref_tag(parse_object(old_sha1), NULL, 0);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       old = (struct commit *) o;
+
+       o = deref_tag(parse_object(new_sha1), NULL, 0);
+       if (!o || o->type != OBJ_COMMIT)
+               return 0;
+       new = (struct commit *) o;
+
+       if (parse_commit(new) < 0)
+               return 0;
+
+       used = list = NULL;
+       commit_list_insert(new, &list);
+       while (list) {
+               new = pop_most_recent_commit(&list, TMP_MARK);
+               commit_list_insert(new, &used);
+               if (new == old) {
+                       found = 1;
+                       break;
+               }
+       }
+       unmark_and_free(list, TMP_MARK);
+       unmark_and_free(used, TMP_MARK);
+       return found;
+}
+
 /*
  * Return true if there is anything to report, otherwise false.
  */
@@ -1359,9 +1460,7 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
                return 0;
 
        base = branch->merge[0]->dst;
-       if (!prefixcmp(base, "refs/remotes/")) {
-               base += strlen("refs/remotes/");
-       }
+       base = shorten_unambiguous_ref(base, 0);
        if (!num_theirs)
                strbuf_addf(sb, "Your branch is ahead of '%s' "
                            "by %d commit%s.\n",
@@ -1378,3 +1477,68 @@ int format_tracking_info(struct branch *branch, struct strbuf *sb)
                            base, num_ours, num_theirs);
        return 1;
 }
+
+static int one_local_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct ref ***local_tail = cb_data;
+       struct ref *ref;
+       int len;
+
+       /* we already know it starts with refs/ to get here */
+       if (check_ref_format(refname + 5))
+               return 0;
+
+       len = strlen(refname) + 1;
+       ref = xcalloc(1, sizeof(*ref) + len);
+       hashcpy(ref->new_sha1, sha1);
+       memcpy(ref->name, refname, len);
+       **local_tail = ref;
+       *local_tail = &ref->next;
+       return 0;
+}
+
+struct ref *get_local_heads(void)
+{
+       struct ref *local_refs = NULL, **local_tail = &local_refs;
+       for_each_ref(one_local_ref, &local_tail);
+       return local_refs;
+}
+
+struct ref *guess_remote_head(const struct ref *head,
+                             const struct ref *refs,
+                             int all)
+{
+       const struct ref *r;
+       struct ref *list = NULL;
+       struct ref **tail = &list;
+
+       if (!head)
+               return NULL;
+
+       /*
+        * Some transports support directly peeking at
+        * where HEAD points; if that is the case, then
+        * we don't have to guess.
+        */
+       if (head->symref)
+               return copy_ref(find_ref_by_name(refs, head->symref));
+
+       /* If refs/heads/master could be right, it is. */
+       if (!all) {
+               r = find_ref_by_name(refs, "refs/heads/master");
+               if (r && !hashcmp(r->old_sha1, head->old_sha1))
+                       return copy_ref(r);
+       }
+
+       /* Look for another ref that points there */
+       for (r = refs; r; r = r->next) {
+               if (r != head && !hashcmp(r->old_sha1, head->old_sha1)) {
+                       *tail = copy_ref(r);
+                       tail = &((*tail)->next);
+                       if (!all)
+                               break;
+               }
+       }
+
+       return list;
+}
index a46a5be131caf1d1d71f97cab3c3ba2cebb6386c..99706a89bc6011c01fcd661d8bad4b26f59b0ca7 100644 (file)
--- a/remote.h
+++ b/remote.h
@@ -45,6 +45,7 @@ struct remote {
 };
 
 struct remote *remote_get(const char *name);
+int remote_is_configured(const char *name);
 
 typedef int each_remote_fn(struct remote *remote, void *priv);
 int for_each_remote(each_remote_fn fn, void *priv);
@@ -74,6 +75,7 @@ int check_ref_type(const struct ref *ref, int flags);
 void free_refs(struct ref *ref);
 
 int resolve_remote_symref(struct ref *ref, struct ref *list);
+int ref_newer(const unsigned char *new_sha1, const unsigned char *old_sha1);
 
 /*
  * Removes and frees any duplicate refs in the map.
@@ -137,4 +139,15 @@ enum match_refs_flags {
 int stat_tracking_info(struct branch *branch, int *num_ours, int *num_theirs);
 int format_tracking_info(struct branch *branch, struct strbuf *sb);
 
+struct ref *get_local_heads(void);
+/*
+ * Find refs from a list which are likely to be pointed to by the given HEAD
+ * ref. If 'all' is false, returns the most likely ref; otherwise, returns a
+ * list of all candidate refs. If no match is found (or 'head' is NULL),
+ * returns NULL. All returns are newly allocated and should be freed.
+ */
+struct ref *guess_remote_head(const struct ref *head,
+                             const struct ref *refs,
+                             int all);
+
 #endif
index 718fb526dd1fbbc5de18784586d37d6069807b77..87360dc23e54aabf47b3bda160e58058507d4a2f 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -12,15 +12,15 @@ static int rerere_autoupdate;
 
 static char *merge_rr_path;
 
-static const char *rr_path(const char *name, const char *file)
+const char *rerere_path(const char *hex, const char *file)
 {
-       return git_path("rr-cache/%s/%s", name, file);
+       return git_path("rr-cache/%s/%s", hex, file);
 }
 
-static int has_resolution(const char *name)
+int has_rerere_resolution(const char *hex)
 {
        struct stat st;
-       return !stat(rr_path(name, "postimage"), &st);
+       return !stat(rerere_path(hex, "postimage"), &st);
 }
 
 static void read_rr(struct string_list *rr)
@@ -173,7 +173,7 @@ static int handle_file(const char *path,
                git_SHA1_Final(sha1, &ctx);
        if (hunk != RR_CONTEXT) {
                if (output)
-                       unlink(output);
+                       unlink_or_warn(output);
                return error("Could not parse conflict hunks in %s", path);
        }
        if (wrerror)
@@ -208,12 +208,12 @@ static int merge(const char *name, const char *path)
        mmbuffer_t result = {NULL, 0};
        xpparam_t xpp = {XDF_NEED_MINIMAL};
 
-       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
+       if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
                return 1;
 
-       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
-                       read_mmfile(&base, rr_path(name, "preimage")) ||
-                       read_mmfile(&other, rr_path(name, "postimage")))
+       if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
+                       read_mmfile(&base, rerere_path(name, "preimage")) ||
+                       read_mmfile(&other, rerere_path(name, "postimage")))
                return 1;
        ret = xdl_merge(&base, &cur, "", &other, "",
                        &xpp, XDL_MERGE_ZEALOUS, &result);
@@ -290,8 +290,8 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        hex = xstrdup(sha1_to_hex(sha1));
                        string_list_insert(path, rr)->util = hex;
                        if (mkdir(git_path("rr-cache/%s", hex), 0755))
-                               continue;;
-                       handle_file(path, NULL, rr_path(hex, "preimage"));
+                               continue;
+                       handle_file(path, NULL, rerere_path(hex, "preimage"));
                        fprintf(stderr, "Recorded preimage for '%s'\n", path);
                }
        }
@@ -307,7 +307,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                const char *path = rr->items[i].string;
                const char *name = (const char *)rr->items[i].util;
 
-               if (has_resolution(name)) {
+               if (has_rerere_resolution(name)) {
                        if (!merge(name, path)) {
                                if (rerere_autoupdate)
                                        string_list_insert(path, &update);
@@ -326,7 +326,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        continue;
 
                fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(rr_path(name, "postimage"), path, 0666);
+               copy_file(rerere_path(name, "postimage"), path, 0666);
        mark_resolved:
                rr->items[i].util = NULL;
        }
index f9b03862fe78b560ee606637be3b1ce972a2cc14..13313f3f2b2cc6a8d895305b7ac92c12c1753682 100644 (file)
--- a/rerere.h
+++ b/rerere.h
@@ -5,5 +5,7 @@
 
 extern int setup_rerere(struct string_list *);
 extern int rerere(void);
+extern const char *rerere_path(const char *hex, const char *file);
+extern int has_rerere_resolution(const char *hex);
 
 #endif
index 34ee490ea0181091c375765141bd83e71ab2defe..18b7ebbbd599417462b5c4275c2581b91ebeca6a 100644 (file)
@@ -15,9 +15,9 @@
 
 volatile show_early_output_fn_t show_early_output;
 
-static char *path_name(struct name_path *path, const char *name)
+char *path_name(const struct name_path *path, const char *name)
 {
-       struct name_path *p;
+       const struct name_path *p;
        char *n, *m;
        int nlen = strlen(name);
        int len = nlen + 1;
@@ -209,7 +209,7 @@ static struct commit *handle_commit(struct rev_info *revs, struct object *object
        }
 
        /*
-        * Tree object? Either mark it uniniteresting, or add it
+        * Tree object? Either mark it uninteresting, or add it
         * to the list of objects to look at later..
         */
        if (object->type == OBJ_TREE) {
@@ -1130,9 +1130,13 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
        } else if (!strcmp(arg, "--pretty")) {
                revs->verbose_header = 1;
                get_commit_format(arg+8, revs);
-       } else if (!prefixcmp(arg, "--pretty=")) {
+       } else if (!prefixcmp(arg, "--pretty=") || !prefixcmp(arg, "--format=")) {
                revs->verbose_header = 1;
                get_commit_format(arg+9, revs);
+       } else if (!strcmp(arg, "--oneline")) {
+               revs->verbose_header = 1;
+               get_commit_format("oneline", revs);
+               revs->abbrev_commit = 1;
        } else if (!strcmp(arg, "--graph")) {
                revs->topo_order = 1;
                revs->rewrite_parents = 1;
index 66d211ac2e56be20fec686416dd6a2816b891239..be39e7d38643817cced4e07a89b6db5201f2b747 100644 (file)
@@ -85,8 +85,10 @@ struct rev_info {
        struct log_info *loginfo;
        int             nr, total;
        const char      *mime_boundary;
+       const char      *patch_suffix;
+       int             numbered_files;
        char            *message_id;
-       const char      *ref_message_id;
+       struct string_list *ref_message_ids;
        const char      *add_signoff;
        const char      *extra_headers;
        const char      *log_reencode;
@@ -144,6 +146,8 @@ struct name_path {
        const char *elem;
 };
 
+char *path_name(const struct name_path *path, const char *name);
+
 extern void add_object(struct object *obj,
                       struct object_array *p,
                       struct name_path *path,
index 44fccc9d5ef4d01eb3c73d6ce8cfbb0cfff0362b..eb2efc33073512efd14b65e67db8cf814ca3fa33 100644 (file)
@@ -106,7 +106,7 @@ int start_command(struct child_process *cmd)
                if (cmd->env) {
                        for (; *cmd->env; cmd->env++) {
                                if (strchr(*cmd->env, '='))
-                                       putenv((char*)*cmd->env);
+                                       putenv((char *)*cmd->env);
                                else
                                        unsetenv(*cmd->env);
                        }
@@ -352,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 e90d9282ff5a0a6dde2d1a9813063a7b2c7bcf91..e3455028435eab958d5f86a3e86249f1704b9c1b 100644 (file)
@@ -10,7 +10,7 @@ enum {
        ERR_RUN_COMMAND_WAITPID_SIGNAL,
        ERR_RUN_COMMAND_WAITPID_NOEXIT,
 };
-#define IS_RUN_COMMAND_ERR(x) ((x) <= -ERR_RUN_COMMAND_FORK)
+#define IS_RUN_COMMAND_ERR(x) (-(x) >= ERR_RUN_COMMAND_FORK)
 
 struct child_process {
        const char **argv;
@@ -50,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
index 8ff1dc35390083c3648c4ee5790f35633d956069..1d7b1b3b4f41177dfd6637fcd98a0fc01f310c09 100644 (file)
@@ -2,17 +2,16 @@
 #define SEND_PACK_H
 
 struct send_pack_args {
-       const char *receivepack;
        unsigned verbose:1,
-               send_all:1,
                send_mirror:1,
                force_update:1,
                use_thin_pack:1,
+               use_ofs_delta:1,
                dry_run:1;
 };
 
 int send_pack(struct send_pack_args *args,
-             const char *dest, struct remote *remote,
-             int nr_heads, const char **heads);
+             int fd[], struct child_process *conn,
+             struct ref *remote_refs, struct extra_have_objects *extra_have);
 
 #endif
index 66b0d9d878a011393582b837301eb1fd5caf2e40..4098ca2b5c166c32cfe4aea9d1bff2e6593e9a60 100644 (file)
@@ -132,8 +132,8 @@ static int read_pack_info_file(const char *infofile)
 
 static int compare_info(const void *a_, const void *b_)
 {
-       struct pack_info * const* a = a_;
-       struct pack_info * const* b = b_;
+       struct pack_info *const *a = a_;
+       struct pack_info *const *b = b_;
 
        if (0 <= (*a)->old_num && 0 <= (*b)->old_num)
                /* Keep the order in the original */
@@ -246,7 +246,7 @@ int update_server_info(int force)
        errs = errs | update_info_packs(force);
 
        /* remove leftover rev-cache file if there is any */
-       unlink(git_path("info/rev-cache"));
+       unlink_or_warn(git_path("info/rev-cache"));
 
        return errs;
 }
diff --git a/setup.c b/setup.c
index 6c2deda18492acb5a8597563d6843f9d0dd232c0..ebd60de9ce5b52f348819a6a390c15b8dc08d2ff 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -434,7 +434,7 @@ int git_config_perm(const char *var, const char *value)
 
        /*
         * Treat values 0, 1 and 2 as compatibility cases, otherwise it is
-        * a chmod value.
+        * a chmod value to restrict to.
         */
        switch (i) {
        case PERM_UMASK:               /* 0 */
@@ -456,7 +456,7 @@ int git_config_perm(const char *var, const char *value)
         * Mask filemode value. Others can not get write permission.
         * x flags for directories are handled separately.
         */
-       return i & 0666;
+       return -(i & 0666);
 }
 
 int check_repository_format_version(const char *var, const char *value, void *cb)
index da357479cf19aad4bebc64f874c76fdf8566712b..c4dc55d1f5cd07adcf46865354f841cc587c51f6 100644 (file)
@@ -1,6 +1,107 @@
 #include "cache.h"
 #include "sha1-lookup.h"
 
+static uint32_t take2(const unsigned char *sha1)
+{
+       return ((sha1[0] << 8) | sha1[1]);
+}
+
+/*
+ * Conventional binary search loop looks like this:
+ *
+ *      do {
+ *              int mi = (lo + hi) / 2;
+ *              int cmp = "entry pointed at by mi" minus "target";
+ *              if (!cmp)
+ *                      return (mi is the wanted one)
+ *              if (cmp > 0)
+ *                      hi = mi; "mi is larger than target"
+ *              else
+ *                      lo = mi+1; "mi is smaller than target"
+ *      } while (lo < hi);
+ *
+ * The invariants are:
+ *
+ * - When entering the loop, lo points at a slot that is never
+ *   above the target (it could be at the target), hi points at a
+ *   slot that is guaranteed to be above the target (it can never
+ *   be at the target).
+ *
+ * - We find a point 'mi' between lo and hi (mi could be the same
+ *   as lo, but never can be the same as hi), and check if it hits
+ *   the target.  There are three cases:
+ *
+ *    - if it is a hit, we are happy.
+ *
+ *    - if it is strictly higher than the target, we update hi with
+ *      it.
+ *
+ *    - if it is strictly lower than the target, we update lo to be
+ *      one slot after it, because we allow lo to be at the target.
+ *
+ * When choosing 'mi', we do not have to take the "middle" but
+ * anywhere in between lo and hi, as long as lo <= mi < hi is
+ * satisfied.  When we somehow know that the distance between the
+ * target and lo is much shorter than the target and hi, we could
+ * pick mi that is much closer to lo than the midway.
+ */
+/*
+ * The table should contain "nr" elements.
+ * The sha1 of element i (between 0 and nr - 1) should be returned
+ * by "fn(i, table)".
+ */
+int sha1_pos(const unsigned char *sha1, void *table, size_t nr,
+            sha1_access_fn fn)
+{
+       size_t hi = nr;
+       size_t lo = 0;
+       size_t mi = 0;
+
+       if (!nr)
+               return -1;
+
+       if (nr != 1) {
+               size_t lov, hiv, miv, ofs;
+
+               for (ofs = 0; ofs < 18; ofs += 2) {
+                       lov = take2(fn(0, table) + ofs);
+                       hiv = take2(fn(nr - 1, table) + ofs);
+                       miv = take2(sha1 + ofs);
+                       if (miv < lov)
+                               return -1;
+                       if (hiv < miv)
+                               return -1 - nr;
+                       if (lov != hiv) {
+                               /*
+                                * At this point miv could be equal
+                                * to hiv (but sha1 could still be higher);
+                                * the invariant of (mi < hi) should be
+                                * kept.
+                                */
+                               mi = (nr - 1) * (miv - lov) / (hiv - lov);
+                               if (lo <= mi && mi < hi)
+                                       break;
+                               die("BUG: assertion failed in binary search");
+                       }
+               }
+               if (18 <= ofs)
+                       die("cannot happen -- lo and hi are identical");
+       }
+
+       do {
+               int cmp;
+               cmp = hashcmp(fn(mi, table), sha1);
+               if (!cmp)
+                       return mi;
+               if (cmp > 0)
+                       hi = mi;
+               else
+                       lo = mi + 1;
+               mi = (hi + lo) / 2;
+       } while (lo < hi);
+       return -lo-1;
+}
+
 /*
  * Conventional binary search loop looks like this:
  *
index 3249a81b3d664afc89c98e6d9dd6b512092a82f9..20af2856818ed51b2afb1718a7e317133ee0d7bd 100644 (file)
@@ -1,6 +1,13 @@
 #ifndef SHA1_LOOKUP_H
 #define SHA1_LOOKUP_H
 
+typedef const unsigned char *sha1_access_fn(size_t index, void *table);
+
+extern int sha1_pos(const unsigned char *sha1,
+                   void *table,
+                   size_t nr,
+                   sha1_access_fn fn);
+
 extern int sha1_entry_pos(const void *table,
                          size_t elem_size,
                          size_t key_offset,
index 2320400b7c48dc3f2303eb0c08933cb562155729..e73cd4fc0ba2daac14f604f1973d1b0658212b26 100644 (file)
@@ -720,6 +720,8 @@ static int open_packed_git_1(struct packed_git *p)
                return error("packfile %s index unavailable", p->pack_name);
 
        p->pack_fd = open(p->pack_name, O_RDONLY);
+       while (p->pack_fd < 0 && errno == EMFILE && unuse_one_window(p, -1))
+               p->pack_fd = open(p->pack_name, O_RDONLY);
        if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
                return -1;
 
@@ -791,7 +793,7 @@ static int in_window(struct pack_window *win, off_t offset)
                && (offset + 20) <= (win_off + win->len);
 }
 
-unsigned charuse_pack(struct packed_git *p,
+unsigned char *use_pack(struct packed_git *p,
                struct pack_window **w_cursor,
                off_t offset,
                unsigned int *left)
@@ -801,7 +803,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.
@@ -937,6 +939,8 @@ static void prepare_packed_git_one(char *objdir, int local)
        sprintf(path, "%s/pack", objdir);
        len = strlen(path);
        dir = opendir(path);
+       while (!dir && errno == EMFILE && unuse_one_window(packed_git, -1))
+               dir = opendir(path);
        if (!dir) {
                if (errno != ENOENT)
                        error("unable to open object pack directory: %s: %s",
@@ -1708,6 +1712,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,
@@ -2111,8 +2118,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;
@@ -2213,12 +2220,18 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len,
 }
 
 /*
- * Move the just written object into its final resting place
+ * Move the just written object into its final resting place.
+ * NEEDSWORK: this should be renamed to finalize_temp_file() as
+ * "moving" is only a part of what it does, when no patch between
+ * master to pu changes the call sites of this function.
  */
 int move_temp_to_file(const char *tmpfile, const char *filename)
 {
        int ret = 0;
-       if (link(tmpfile, filename))
+
+       if (object_creation_mode == OBJECT_CREATION_USES_RENAMES)
+               goto try_rename;
+       else if (link(tmpfile, filename))
                ret = errno;
 
        /*
@@ -2229,15 +2242,16 @@ int move_temp_to_file(const char *tmpfile, const char *filename)
         *
         * The same holds for FAT formatted media.
         *
-        * When this succeeds, we just return 0. We have nothing
+        * When this succeeds, we just return We have nothing
         * left to unlink.
         */
        if (ret && ret != EEXIST) {
+       try_rename:
                if (!rename(tmpfile, filename))
-                       return 0;
+                       goto out;
                ret = errno;
        }
-       unlink(tmpfile);
+       unlink_or_warn(tmpfile);
        if (ret) {
                if (ret != EEXIST) {
                        return error("unable to write sha1 filename %s: %s\n", filename, strerror(ret));
@@ -2245,6 +2259,9 @@ int move_temp_to_file(const char *tmpfile, const char *filename)
                /* FIXME!!! Collision check here ? */
        }
 
+out:
+       if (set_shared_perm(filename, (S_IFREG|0444)))
+               return error("unable to set permission to '%s'", filename);
        return 0;
 }
 
@@ -2269,7 +2286,6 @@ static void close_sha1_file(int fd)
 {
        if (fsync_object_files)
                fsync_or_die(fd, "sha1 file");
-       fchmod(fd, 0444);
        if (close(fd) != 0)
                die("error when closing sha1 file (%s)", strerror(errno));
 }
@@ -2327,6 +2343,8 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen,
 
        filename = sha1_file_name(sha1);
        fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
+       while (fd < 0 && errno == EMFILE && unuse_one_window(packed_git, -1))
+               fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename);
        if (fd < 0) {
                if (errno == EACCES)
                        return error("insufficient permission for adding an object to repository database %s\n", get_object_directory());
index 722fc35a6d98e1c260c6e14738ad3b2d41d924aa..904bcd96a54a1cc33386a56a16d07dce34cbb90b 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_branch_name(const char **string, int *len)
+{
+       struct strbuf buf = STRBUF_INIT;
+       int ret = interpret_branch_name(*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_branch_name(&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_branch_name(&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_branch_name(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_branch_name(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/shell.c b/shell.c
index e3393690dd3b79af80e6b95710583e744fd574d4..b968be79f487f8d93e205fd0a844a206e24f21f8 100644 (file)
--- a/shell.c
+++ b/shell.c
@@ -40,6 +40,7 @@ static struct commands {
 } cmd_list[] = {
        { "git-receive-pack", do_generic_cmd },
        { "git-upload-pack", do_generic_cmd },
+       { "git-upload-archive", do_generic_cmd },
        { "cvs", do_cvs_cmd },
        { NULL },
 };
index cca3360546dabf9f018b882f690bd1dea9de534d..899b1ff36619d0077dced9da77f538ab98396a83 100644 (file)
@@ -19,7 +19,7 @@
 
 #define FIX_SIZE 10  /* large enough for any of the above */
 
-int recv_sideband(const char *me, int in_stream, int out, int err)
+int recv_sideband(const char *me, int in_stream, int out)
 {
        unsigned pf = strlen(PREFIX);
        unsigned sf;
@@ -41,8 +41,7 @@ int recv_sideband(const char *me, int in_stream, int out, int err)
                if (len == 0)
                        break;
                if (len < 1) {
-                       len = sprintf(buf, "%s: protocol error: no band designator\n", me);
-                       safe_write(err, buf, len);
+                       fprintf(stderr, "%s: protocol error: no band designator\n", me);
                        return SIDEBAND_PROTOCOL_ERROR;
                }
                band = buf[pf] & 0xff;
@@ -50,8 +49,8 @@ int recv_sideband(const char *me, int in_stream, int out, int err)
                switch (band) {
                case 3:
                        buf[pf] = ' ';
-                       buf[pf+1+len] = '\n';
-                       safe_write(err, buf, pf+1+len+1);
+                       buf[pf+1+len] = '\0';
+                       fprintf(stderr, "%s\n", buf);
                        return SIDEBAND_REMOTE_ERROR;
                case 2:
                        buf[pf] = ' ';
@@ -95,12 +94,12 @@ int recv_sideband(const char *me, int in_stream, int out, int err)
                                        memcpy(save, b + brk, sf);
                                        b[brk + sf - 1] = b[brk - 1];
                                        memcpy(b + brk - 1, suffix, sf);
-                                       safe_write(err, b, brk + sf);
+                                       fprintf(stderr, "%.*s", brk + sf, b);
                                        memcpy(b + brk, save, sf);
                                        len -= brk;
                                } else {
                                        int l = brk ? brk : len;
-                                       safe_write(err, b, l);
+                                       fprintf(stderr, "%.*s", l, b);
                                        len -= l;
                                }
 
@@ -112,10 +111,8 @@ int recv_sideband(const char *me, int in_stream, int out, int err)
                        safe_write(out, buf + pf+1, len);
                        continue;
                default:
-                       len = sprintf(buf,
-                                     "%s: protocol error: bad band #%d\n",
-                                     me, band);
-                       safe_write(err, buf, len);
+                       fprintf(stderr, "%s: protocol error: bad band #%d\n",
+                               me, band);
                        return SIDEBAND_PROTOCOL_ERROR;
                }
        }
index a84b6917c7a17b5f8a922540801e98d46aa24431..d72db35d1e0dc109f75b292762013c11b86426aa 100644 (file)
@@ -7,7 +7,7 @@
 #define DEFAULT_PACKET_MAX 1000
 #define LARGE_PACKET_MAX 65520
 
-int recv_sideband(const char *me, int in_stream, int out, int err);
+int recv_sideband(const char *me, int in_stream, int out);
 ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
 
 #endif
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..a88496030b7053a543173c299bd9f54b923db2ec 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "refs.h"
 
 int prefixcmp(const char *str, const char *prefix)
 {
@@ -139,14 +140,11 @@ void strbuf_list_free(struct strbuf **sbs)
 
 int strbuf_cmp(const struct strbuf *a, const struct strbuf *b)
 {
-       int cmp;
-       if (a->len < b->len) {
-               cmp = memcmp(a->buf, b->buf, a->len);
-               return cmp ? cmp : -1;
-       } else {
-               cmp = memcmp(a->buf, b->buf, b->len);
-               return cmp ? cmp : a->len != b->len;
-       }
+       int len = a->len < b->len ? a->len: b->len;
+       int cmp = memcmp(a->buf, b->buf, len);
+       if (cmp)
+               return cmp;
+       return a->len < b->len ? -1: a->len != b->len;
 }
 
 void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
@@ -256,18 +254,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 +276,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 +296,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 +317,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;
 }
 
@@ -351,3 +358,19 @@ int strbuf_read_file(struct strbuf *sb, const char *path, size_t hint)
 
        return len;
 }
+
+int strbuf_branchname(struct strbuf *sb, const char *name)
+{
+       int len = strlen(name);
+       if (interpret_branch_name(name, sb) == len)
+               return 0;
+       strbuf_add(sb, name, len);
+       return len;
+}
+
+int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
+{
+       strbuf_branchname(sb, name);
+       strbuf_splice(sb, 0, 0, "refs/heads/", 11);
+       return check_ref_format(sb->buf);
+}
index 89bd36e15a541e268117ff023afc6a43137cdd6e..eaa8704d5fa6e85d33953db77dd03de3cb462df4 100644 (file)
--- a/strbuf.h
+++ b/strbuf.h
@@ -11,7 +11,7 @@
  *    build complex strings/buffers whose final size isn't easily known.
  *
  *    It is NOT legal to copy the ->buf pointer away.
- *    `strbuf_detach' is the operation that detachs a buffer from its shell
+ *    `strbuf_detach' is the operation that detaches a buffer from its shell
  *    while keeping the shell valid wrt its invariants.
  *
  * 2. the ->buf member is a byte array that has at least ->len + 1 bytes
@@ -131,4 +131,7 @@ extern int strbuf_getline(struct strbuf *, FILE *, int);
 extern void stripspace(struct strbuf *buf, int skip_comments);
 extern int launch_editor(const char *path, struct strbuf *buffer, const char *const *env);
 
+extern int strbuf_branchname(struct strbuf *sb, const char *name);
+extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
+
 #endif /* STRBUF_H */
index ddd83c8c76112cecd5d23668aaca467601855a72..1ac536e638dbbc02deb2d4e4a607cc52f7f7c108 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);
@@ -76,6 +92,16 @@ struct string_list_item *string_list_lookup(const char *string, struct string_li
        return list->items + i;
 }
 
+int for_each_string_list(string_list_each_func_t fn,
+                        struct string_list *list, void *cb_data)
+{
+       int i, ret = 0;
+       for (i = 0; i < list->nr; i++)
+               if ((ret = fn(&list->items[i], cb_data)))
+                       break;
+       return ret;
+}
+
 void string_list_clear(struct string_list *list, int free_util)
 {
        if (list->items) {
@@ -94,6 +120,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..14bbc477decc32618139e912fda22e585d815159 100644 (file)
@@ -15,9 +15,23 @@ 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 this function to iterate over each item */
+typedef int (*string_list_each_func_t)(struct string_list_item *, void *);
+int for_each_string_list(string_list_each_func_t,
+                        struct string_list *list, void *cb_data);
+
 /* 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..1d6b35b858020300f502e2a9341b82d4fa8a61fd 100644 (file)
 #include "cache.h"
 
-struct pathname {
+/*
+ * Returns the length (on a path component basis) of the longest
+ * common prefix match of 'name_a' and 'name_b'.
+ */
+static int longest_path_match(const char *name_a, int len_a,
+                             const char *name_b, int len_b,
+                             int *previous_slash)
+{
+       int max_len, match_len = 0, match_len_prev = 0, i = 0;
+
+       max_len = len_a < len_b ? len_a : len_b;
+       while (i < max_len && name_a[i] == name_b[i]) {
+               if (name_a[i] == '/') {
+                       match_len_prev = match_len;
+                       match_len = i;
+               }
+               i++;
+       }
+       /*
+        * Is 'name_b' a substring of 'name_a', the other way around,
+        * or is 'name_a' and 'name_b' the exact same string?
+        */
+       if (i >= max_len && ((len_a > len_b && name_a[len_b] == '/') ||
+                            (len_a < len_b && name_b[len_a] == '/') ||
+                            (len_a == len_b))) {
+               match_len_prev = match_len;
+               match_len = i;
+       }
+       *previous_slash = match_len_prev;
+       return match_len;
+}
+
+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)
+static inline void reset_lstat_cache(void)
 {
-       int match_len = match->len;
-       return (len > match_len &&
-               name[match_len] == '/' &&
-               !memcmp(name, match->path, match_len)) ? match_len : 0;
+       cache.path[0] = '\0';
+       cache.len = 0;
+       cache.flags = 0;
+       /*
+        * The track_flags and prefix_len_stat_func members is only
+        * set by the safeguard rule inside lstat_cache()
+        */
 }
 
-static inline void set_pathname(int len, const char *name, struct pathname *match)
+#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(const char *name, int len,
+                      int track_flags, int prefix_len_stat_func)
 {
-       if (len < PATH_MAX) {
-               match->len = len;
-               memcpy(match->path, name, len);
-               match->path[len] = 0;
+       int match_len, last_slash, last_slash_dir, previous_slash;
+       int match_flags, ret_flags, save_flags, max_len, ret;
+       struct stat st;
+
+       if (cache.track_flags != track_flags ||
+           cache.prefix_len_stat_func != prefix_len_stat_func) {
+               /*
+                * As a safeguard rule 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();
+               cache.track_flags = track_flags;
+               cache.prefix_len_stat_func = 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_path_match(name, len, cache.path, cache.len,
+                                          &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;
+       }
+
+       /*
+        * Okay, no match from the cache so far, so now we have to
+        * check the rest of the path components.
+        */
+       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;
+               } else if (S_ISLNK(st.st_mode)) {
+                       ret_flags = FL_SYMLINK;
+               } else {
+                       ret_flags = FL_ERR;
+               }
+               break;
        }
+
+       /*
+        * 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();
+       }
+       return ret_flags;
 }
 
-int has_symlink_leading_path(int len, const char *name)
+/*
+ * Invalidate the given 'name' from the cache, if 'name' matches
+ * completely with the cache.
+ */
+void invalidate_lstat_cache(const char *name, int len)
 {
-       static struct pathname link, nonlink;
+       int match_len, previous_slash;
+
+       match_len = longest_path_match(name, len, cache.path, cache.len,
+                                      &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();
+       }
+}
+
+/*
+ * Completely clear the contents of the cache
+ */
+void clear_lstat_cache(void)
+{
+       reset_lstat_cache();
+}
+
+#define USE_ONLY_LSTAT  0
+
+/*
+ * Return non-zero if path 'name' has a leading symlink component
+ */
+int has_symlink_leading_path(const char *name, int len)
+{
+       return lstat_cache(name, len,
+                          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(const char *name, int len)
+{
+       return lstat_cache(name, len,
+                          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(const char *name, int len, int prefix_len)
+{
+       return lstat_cache(name, len,
+                          FL_DIR|FL_FULLPATH, prefix_len) &
+               FL_DIR;
+}
+
+static struct removal_def {
        char path[PATH_MAX];
-       struct stat st;
-       char *sp;
-       int known_dir;
+       int len;
+} removal;
+
+static void do_remove_scheduled_dirs(int new_len)
+{
+       while (removal.len > new_len) {
+               removal.path[removal.len] = '\0';
+               if (rmdir(removal.path))
+                       break;
+               do {
+                       removal.len--;
+               } while (removal.len > new_len &&
+                        removal.path[removal.len] != '/');
+       }
+       removal.len = new_len;
+       return;
+}
+
+void schedule_dir_for_removal(const char *name, int len)
+{
+       int match_len, last_slash, i, previous_slash;
+
+       match_len = last_slash = i =
+               longest_path_match(name, len, removal.path, removal.len,
+                                  &previous_slash);
+       /* Find last slash inside 'name' */
+       while (i < len) {
+               if (name[i] == '/')
+                       last_slash = i;
+               i++;
+       }
 
        /*
-        * See if the last known symlink cache matches.
+        * If we are about to go down the directory tree, we check if
+        * we must first go upwards the tree, such that we then can
+        * remove possible empty directories as we go upwards.
         */
-       if (match_pathname(len, name, &link))
-               return 1;
-
+       if (match_len < last_slash && match_len < removal.len)
+               do_remove_scheduled_dirs(match_len);
        /*
-        * Get rid of the last known directory part
+        * If we go deeper down the directory tree, we only need to
+        * save the new path components as we go down.
         */
-       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;
-                       continue;
-               }
-               if (S_ISLNK(st.st_mode)) {
-                       set_pathname(thislen, path, &link);
-                       return 1;
-               }
-               break;
+       if (match_len < last_slash) {
+               memcpy(&removal.path[match_len], &name[match_len],
+                      last_slash - match_len);
+               removal.len = last_slash;
        }
-       return 0;
+       return;
+}
+
+void remove_scheduled_dirs(void)
+{
+       do_remove_scheduled_dirs(0);
+       return;
 }
index 9149373032ef8e1403c820cc289728393e5b8aa8..bf816fc8505508c91999175ad6544a67febabb33 100644 (file)
@@ -38,4 +38,7 @@ full-svn-test:
        $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
        $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
 
-.PHONY: pre-clean $(T) aggregate-results clean
+valgrind:
+       GIT_TEST_OPTS=--valgrind $(MAKE)
+
+.PHONY: pre-clean $(T) aggregate-results clean valgrind
index 8f12d48fe8b4ffe4a4b37dcd16ce58e50837433f..d8f6c7de6d27e27a33982e82893baa3bb5ffd7fd 100644 (file)
--- a/t/README
+++ b/t/README
@@ -39,7 +39,8 @@ this:
     * passed all 3 test(s)
 
 You can pass --verbose (or -v), --debug (or -d), and --immediate
-(or -i) command line argument to the test.
+(or -i) command line argument to the test, or by setting GIT_TEST_OPTS
+appropriately before running "make".
 
 --verbose::
        This makes the test more verbose.  Specifically, the
@@ -58,6 +59,21 @@ You can pass --verbose (or -v), --debug (or -d), and --immediate
        This causes additional long-running tests to be run (where
        available), for more exhaustive testing.
 
+--valgrind::
+       Execute all Git binaries with valgrind and exit with status
+       126 on errors (just like regular tests, this will only stop
+       the test script when running under -i).  Valgrind errors
+       go to stderr, so you might want to pass the -v option, too.
+
+       Since it makes no sense to run the tests with --valgrind and
+       not see any output, this option implies --verbose.  For
+       convenience, it also implies --tee.
+
+--tee::
+       In addition to printing the test output to the terminal,
+       write it to files named 't/test-results/$TEST_NAME.out'.
+       As the names depend on the tests' file names, it is safe to
+       run the tests with this option in parallel.
 
 Skipping Tests
 --------------
@@ -212,6 +228,24 @@ library for your script to use.
    is to summarize successes and failures in the test script and
    exit with an appropriate error code.
 
+ - test_tick
+
+   Make commit and tag names consistent by setting the author and
+   committer times to defined stated.  Subsequent calls will
+   advance the times by a fixed amount.
+
+ - test_commit <message> [<filename> [<contents>]]
+
+   Creates a commit with the given message, committing the given
+   file with the given contents (default for both is to reuse the
+   message string), and adds a tag (again reusing the message
+   string as name).  Calls test_tick to make the SHA-1s
+   reproducible.
+
+ - test_merge <message> <commit-or-tag>
+
+   Merges the given rev using the given message.  Like test_commit,
+   creates a tag and calls test_tick before committing.
 
 Tips for Writing Tests
 ----------------------
index cacb273afff1fbddf152bb440451fa141589cf33..396b9653a3ad80490cf360c86a910edff58862a2 100644 (file)
@@ -114,7 +114,10 @@ test_expect_success \
 test_expect_success \
     'some edit' \
     'mv file file.orig &&
-    sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" < file.orig > file &&
+    {
+       cat file.orig &&
+       echo
+    } | sed -e "s/^3A/99/" -e "/^1A/d" -e "/^incomplete/d" > file &&
     echo "incomplete" | tr -d "\\012" >>file &&
     GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
 
index 67c431d4ebbb32fe8d88a83104485b38d746fa62..773d47cf3cb704d7976185738a2b9840c159a33c 100644 (file)
@@ -5,9 +5,12 @@ git_svn_id=git""-svn-id
 
 if test -n "$NO_SVN_TESTS"
 then
-       test_expect_success 'skipping git svn tests, NO_SVN_TESTS defined' :
+       say 'skipping git svn tests, NO_SVN_TESTS defined'
+       test_done
+fi
+if ! test_have_prereq PERL; then
+       say 'skipping git svn tests, perl not available'
        test_done
-       exit
 fi
 
 GIT_DIR=$PWD/.git
@@ -17,9 +20,8 @@ SVN_TREE=$GIT_SVN_DIR/svn-tree
 svn >/dev/null 2>&1
 if test $? -ne 1
 then
-    test_expect_success 'skipping git svn tests, svn not found' :
+    say 'skipping git svn tests, svn not found'
     test_done
-    exit
 fi
 
 svnrepo=$PWD/svnrepo
@@ -41,9 +43,8 @@ then
        else
                err='Perl SVN libraries not found or unusable, skipping test'
        fi
-       test_expect_success "$err" :
+       say "$err"
        test_done
-       exit
 fi
 
 rawsvnrepo="$svnrepo"
@@ -144,7 +145,6 @@ require_svnserve () {
     then
         say 'skipping svnserve test. (set $SVNSERVE_PORT to enable)'
         test_done
-        exit
     fi
 }
 
index 6ac312b9059394b44cd6e106f9da6394674ee54a..cde659d14ac599e78bb4cd12a2ad88eab179549e 100644 (file)
@@ -8,10 +8,23 @@ then
        say "skipping test, network testing disabled by default"
        say "(define GIT_TEST_HTTPD to enable)"
        test_done
-       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 +33,8 @@ 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
 fi
 
 HTTPD_VERSION=`$LIB_HTTPD_PATH -v | \
@@ -36,17 +48,14 @@ then
                then
                        say "skipping test, at least Apache version 2 is required"
                        test_done
-                       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"
 
@@ -82,18 +91,23 @@ prepare_httpd() {
 }
 
 start_httpd() {
-       prepare_httpd
+       prepare_httpd >&3 2>&4
 
-       trap 'stop_httpd; die' exit
+       trap 'stop_httpd; die' EXIT
 
        "$LIB_HTTPD_PATH" -d "$HTTPD_ROOT_PATH" \
                -f "$TEST_PATH/apache.conf" $HTTPD_PARA \
-               -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start
+               -c "Listen 127.0.0.1:$LIB_HTTPD_PORT" -k start \
+               >&3 2>&4
+       if ! test $? = 0; then
+               say "skipping test, web server setup failed"
+               test_done
+       fi
 }
 
 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..21aa42f1c613538ba26c0b613cc0d1b001bc52b9 100644 (file)
@@ -1,9 +1,13 @@
 ServerName dummy
+LockFile accept.lock
 PidFile httpd.pid
 DocumentRoot www
 LogFormat "%h %l %u %t \"%r\" %>s %b" common
 CustomLog access.log common
 ErrorLog error.log
+<IfModule !mod_log_config.c>
+       LoadModule log_config_module modules/mod_log_config.so
+</IfModule>
 
 <IfDefine 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
+}
index 70df15cbd8339b552a56a95ca0c0893138550201..f4ca4fc85c6b52a2ba919528284f2b668e6bd3d2 100755 (executable)
@@ -57,6 +57,21 @@ test_expect_failure 'pretend we have a known breakage' '
 test_expect_failure 'pretend we have fixed a known breakage' '
     :
 '
+test_set_prereq HAVEIT
+haveit=no
+test_expect_success HAVEIT 'test runs if prerequisite is satisfied' '
+    test_have_prereq HAVEIT &&
+    haveit=yes
+'
+donthaveit=yes
+test_expect_success DONTHAVEIT 'unmet prerequisite causes test to be skipped' '
+    donthaveit=no
+'
+if test $haveit$donthaveit != yesyes
+then
+       say "bug in test framework: prerequisite tags do not work reliably"
+       exit 1
+fi
 
 ################################################################
 # Basics of the basics
@@ -100,12 +115,31 @@ test_expect_success \
     'test "$tree" = 4b825dc642cb6eb9a060e54bf8d69288fbee4904'
 
 # Various types of objects
+# Some filesystems do not support symblic links; on such systems
+# some expected values are different
 mkdir path2 path3 path3/subp3
-for p in path0 path2/file2 path3/file3 path3/subp3/file3
+paths='path0 path2/file2 path3/file3 path3/subp3/file3'
+for p in $paths
 do
     echo "hello $p" >$p
-    ln -s "hello $p" ${p}sym
 done
+if test_have_prereq SYMLINKS
+then
+       for p in $paths
+       do
+               ln -s "hello $p" ${p}sym
+       done
+       expectfilter=cat
+       expectedtree=087704a96baf1c2d1c869a8b084481e121c88b5b
+       expectedptree1=21ae8269cacbe57ae09138dcc3a2887f904d02b3
+       expectedptree2=3c5e5399f3a333eddecce7a9b9465b63f65f51e2
+else
+       expectfilter='grep -v sym'
+       expectedtree=8e18edf7d7edcf4371a3ac6ae5f07c2641db7c46
+       expectedptree1=cfb8591b2f65de8b8cc1020cd7d9e67e7793b325
+       expectedptree2=ce580448f0148b985a513b693fdf7d802cacb44f
+fi
+
 test_expect_success \
     'adding various types of objects with git update-index --add.' \
     'find path* ! -type d -print | xargs git update-index --add'
@@ -115,7 +149,7 @@ test_expect_success \
     'showing stage with git ls-files --stage' \
     'git ls-files --stage >current'
 
-cat >expected <<\EOF
+$expectfilter >expected <<\EOF
 100644 f87290f8eb2cbbea7857214459a0739927eab154 0      path0
 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0      path0sym
 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0      path2/file2
@@ -127,14 +161,14 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'validate git ls-files output for a known tree.' \
-    'diff current expected'
+    'test_cmp expected current'
 
 test_expect_success \
     'writing tree out with git write-tree.' \
     'tree=$(git write-tree)'
 test_expect_success \
     'validate object ID for a known tree.' \
-    'test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b'
+    'test "$tree" = "$expectedtree"'
 
 test_expect_success \
     'showing tree with git ls-tree' \
@@ -145,16 +179,16 @@ cat >expected <<\EOF
 040000 tree 58a09c23e2ca152193f2786e06986b7b6712bdbe   path2
 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
 EOF
-test_expect_success \
+test_expect_success SYMLINKS \
     'git ls-tree output for a known tree.' \
-    'diff current expected'
+    'test_cmp expected current'
 
 # This changed in ls-tree pathspec change -- recursive does
 # not show tree nodes anymore.
 test_expect_success \
     'showing tree with git ls-tree -r' \
     'git ls-tree -r $tree >current'
-cat >expected <<\EOF
+$expectfilter >expected <<\EOF
 100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
 100644 blob 3feff949ed00a62d9f7af97c15cd8a30595e7ac7   path2/file2
@@ -166,7 +200,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'git ls-tree -r output for a known tree.' \
-    'diff current expected'
+    'test_cmp expected current'
 
 # But with -r -t we can have both.
 test_expect_success \
@@ -185,23 +219,23 @@ cat >expected <<\EOF
 100644 blob 00fb5908cb97c2564a9783c0c64087333b3b464f   path3/subp3/file3
 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
 EOF
-test_expect_success \
+test_expect_success SYMLINKS \
     'git ls-tree -r output for a known tree.' \
-    'diff current expected'
+    'test_cmp expected current'
 
 test_expect_success \
     'writing partial tree out with git write-tree --prefix.' \
     'ptree=$(git write-tree --prefix=path3)'
 test_expect_success \
     'validate object ID for a known tree.' \
-    'test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3'
+    'test "$ptree" = "$expectedptree1"'
 
 test_expect_success \
     'writing partial tree out with git write-tree --prefix.' \
     'ptree=$(git write-tree --prefix=path3/subp3)'
 test_expect_success \
     'validate object ID for a known tree.' \
-    'test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2'
+    'test "$ptree" = "$expectedptree2"'
 
 cat >badobjects <<EOF
 100644 blob 1000000000000000000000000000000000000000   dir/file1
@@ -234,7 +268,7 @@ test_expect_success \
      newtree=$(git write-tree) &&
      test "$newtree" = "$tree"'
 
-cat >expected <<\EOF
+$expectfilter >expected <<\EOF
 :100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M     path0
 :120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M     path0sym
 :100644 100644 3feff949ed00a62d9f7af97c15cd8a30595e7ac7 0000000000000000000000000000000000000000 M     path2/file2
@@ -257,7 +291,7 @@ test_expect_success \
     'git diff-files >current && cmp -s current /dev/null'
 
 ################################################################
-P=087704a96baf1c2d1c869a8b084481e121c88b5b
+P=$expectedtree
 test_expect_success \
     'git commit-tree records the correct tree in a commit.' \
     'commit0=$(echo NO | git commit-tree $P) &&
@@ -293,7 +327,7 @@ test_expect_success 'update-index D/F conflict' '
        test $numpath0 = 1
 '
 
-test_expect_success 'absolute path works as expected' '
+test_expect_success SYMLINKS 'absolute path works as expected' '
        mkdir first &&
        ln -s ../.git first/.git &&
        mkdir second &&
index 63e1217e7162435c3da8ec7984b5f6a53b3a10e2..2342ac5788a9976b591cb78593279f092d1dc2f6 100755 (executable)
@@ -15,7 +15,7 @@ test_expect_success setup '
 
 '
 
-test_expect_success 'write-tree should notice unwritable repository' '
+test_expect_success POSIXPERM 'write-tree should notice unwritable repository' '
 
        (
                chmod a-w .git/objects .git/objects/?? &&
@@ -27,7 +27,7 @@ test_expect_success 'write-tree should notice unwritable repository' '
 
 '
 
-test_expect_success 'commit should notice unwritable repository' '
+test_expect_success POSIXPERM 'commit should notice unwritable repository' '
 
        (
                chmod a-w .git/objects .git/objects/?? &&
@@ -39,7 +39,7 @@ test_expect_success 'commit should notice unwritable repository' '
 
 '
 
-test_expect_success 'update-index should notice unwritable repository' '
+test_expect_success POSIXPERM 'update-index should notice unwritable repository' '
 
        (
                echo 6O >file &&
@@ -52,7 +52,7 @@ test_expect_success 'update-index should notice unwritable repository' '
 
 '
 
-test_expect_success 'add should notice unwritable repository' '
+test_expect_success POSIXPERM 'add should notice unwritable repository' '
 
        (
                echo b >file &&
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 1be7446d8d9f8a46b463f2474a8c25bdd33044d2..4e72b53140bd35db87a6c873eda9e75e896e1cdd 100755 (executable)
@@ -429,6 +429,37 @@ test_expect_success 'in-tree .gitattributes (4)' '
        }
 '
 
+test_expect_success 'checkout with existing .gitattributes' '
+
+       git config core.autocrlf true &&
+       git config --unset core.safecrlf &&
+       echo ".file2 -crlfQ" | q_to_cr >> .gitattributes &&
+       git add .gitattributes &&
+       git commit -m initial &&
+       echo ".file -crlfQ" | q_to_cr >> .gitattributes &&
+       echo "contents" > .file &&
+       git add .gitattributes .file &&
+       git commit -m second &&
+
+       git checkout master~1 &&
+       git checkout master &&
+       test "$(git diff-files --raw)" = ""
+
+'
+
+test_expect_success 'checkout when deleting .gitattributes' '
+
+       git rm .gitattributes &&
+       echo "contentsQ" | q_to_cr > .file2 &&
+       git add .file2 &&
+       git commit -m third
+
+       git checkout master~1 &&
+       git checkout master &&
+       remove_cr .file2 >/dev/null
+
+'
+
 test_expect_success 'invalid .gitattributes (must not crash)' '
 
        echo "three +crlf" >>.gitattributes &&
index e5330395fc866b2a913b4b20ecb66f1f2a3bbaff..c7d0324374e9df5131a58a9ae0cadf7ee4dc03e7 100755 (executable)
@@ -28,12 +28,12 @@ test_expect_success 'tar archive' '
 
 "$UNZIP" -v >/dev/null 2>&1
 if [ $? -eq 127 ]; then
-       echo "Skipping ZIP test, because unzip was not found"
-       test_done
-       exit
+       say "Skipping ZIP test, because unzip was not found"
+else
+       test_set_prereq UNZIP
 fi
 
-test_expect_success 'zip archive' '
+test_expect_success UNZIP 'zip archive' '
 
        git archive --format=zip HEAD >test.zip &&
 
index 7edf49db3c37982a6d599a39b98ce60ceeb0039b..89282ccf7a1a73d4b5ee085c4236b1204dc502c8 100755 (executable)
@@ -8,7 +8,9 @@ auml=`printf '\xc3\xa4'`
 aumlcdiar=`printf '\x61\xcc\x88'`
 
 case_insensitive=
-test_expect_success 'see if we expect ' '
+unibad=
+no_symlinks=
+test_expect_success 'see what we expect' '
 
        test_case=test_expect_success
        test_unicode=test_expect_success
@@ -19,7 +21,6 @@ test_expect_success 'see if we expect ' '
        then
                test_case=test_expect_failure
                case_insensitive=t
-               say "will test on a case insensitive filesystem"
        fi &&
        rm -fr junk &&
        mkdir junk &&
@@ -27,13 +28,26 @@ test_expect_success 'see if we expect ' '
        case "$(cd junk && echo *)" in
        "$aumlcdiar")
                test_unicode=test_expect_failure
-               say "will test on a unicode corrupting filesystem"
+               unibad=t
                ;;
        *)      ;;
        esac &&
-       rm -fr junk
+       rm -fr junk &&
+       {
+               ln -s x y 2> /dev/null &&
+               test -h y 2> /dev/null ||
+               no_symlinks=1
+               rm -f y
+       }
 '
 
+test "$case_insensitive" &&
+       say "will test on a case insensitive filesystem"
+test "$unibad" &&
+       say "will test on a unicode corrupting filesystem"
+test "$no_symlinks" &&
+       say "will test on a filesystem lacking symbolic links"
+
 if test "$case_insensitive"
 then
 test_expect_success "detection of case insensitive filesystem during repo init" '
@@ -48,6 +62,21 @@ test_expect_success "detection of case insensitive filesystem during repo init"
 '
 fi
 
+if test "$no_symlinks"
+then
+test_expect_success "detection of filesystem w/o symlink support during repo init" '
+
+       v=$(git config --bool core.symlinks) &&
+       test "$v" = false
+'
+else
+test_expect_success "detection of filesystem w/o symlink support during repo init" '
+
+       test_must_fail git config --bool core.symlinks ||
+       test "$(git config --bool core.symlinks)" = true
+'
+fi
+
 test_expect_success "setup case tests" '
 
        git config core.ignorecase true &&
index b29c37a5a4de42239474c4fdf87c86c6f27b6ade..0c6ff567a1d47f52492dd89bd098b25bae737bad 100755 (executable)
@@ -4,7 +4,7 @@ test_description='update-index and add refuse to add beyond symlinks'
 
 . ./test-lib.sh
 
-test_expect_success setup '
+test_expect_success SYMLINKS setup '
        >a &&
        mkdir b &&
        ln -s b c &&
@@ -12,12 +12,12 @@ test_expect_success setup '
        git update-index --add a b/d
 '
 
-test_expect_success 'update-index --add beyond symlinks' '
+test_expect_success SYMLINKS 'update-index --add beyond symlinks' '
        test_must_fail git update-index --add c/d &&
        ! ( git ls-files | grep c/d )
 '
 
-test_expect_success 'add beyond symlinks' '
+test_expect_success SYMLINKS 'add beyond symlinks' '
        test_must_fail git add c/d &&
        ! ( git ls-files | grep c/d )
 '
index 4ed1f0b4dde45e679c04df4b3d833982a0a5b74c..53cf1f8dc4acad959fdec359b738387aeb126b1e 100755 (executable)
@@ -7,41 +7,91 @@ test_description='Test various path utilities'
 
 . ./test-lib.sh
 
-norm_abs() {
-       test_expect_success "normalize absolute: $1 => $2" \
+norm_path() {
+       test_expect_success $3 "normalize path: $1 => $2" \
        "test \"\$(test-path-utils normalize_path_copy '$1')\" = '$2'"
 }
 
+# On Windows, we are using MSYS's bash, which mangles the paths.
+# Absolute paths are anchored at the MSYS installation directory,
+# which means that the path / accounts for this many characters:
+rootoff=$(test-path-utils normalize_path_copy / | wc -c)
+# Account for the trailing LF:
+if test $rootoff = 2; then
+       rootoff=        # we are on Unix
+else
+       rootoff=$(($rootoff-1))
+fi
+
 ancestor() {
-       test_expect_success "longest ancestor: $1 $2 => $3" \
-       "test \"\$(test-path-utils longest_ancestor_length '$1' '$2')\" = '$3'"
+       # We do some math with the expected ancestor length.
+       expected=$3
+       if test -n "$rootoff" && test "x$expected" != x-1; then
+               expected=$(($expected+$rootoff))
+       fi
+       test_expect_success "longest ancestor: $1 $2 => $expected" \
+       "actual=\$(test-path-utils longest_ancestor_length '$1' '$2') &&
+        test \"\$actual\" = '$expected'"
 }
 
-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//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
+# Absolute path tests must be skipped on Windows because due to path mangling
+# the test program never sees a POSIX-style absolute path
+case $(uname -s) in
+*MINGW*)
+       ;;
+*)
+       test_set_prereq POSIX
+       ;;
+esac
+
+norm_path "" ""
+norm_path . ""
+norm_path ./ ""
+norm_path ./. ""
+norm_path ./.. ++failed++
+norm_path ../. ++failed++
+norm_path ./../.// ++failed++
+norm_path dir/.. ""
+norm_path dir/sub/../.. ""
+norm_path dir/sub/../../.. ++failed++
+norm_path dir dir
+norm_path dir// dir/
+norm_path ./dir dir
+norm_path dir/. dir/
+norm_path dir///./ dir/
+norm_path dir//sub/.. dir/
+norm_path dir/sub/../ dir/
+norm_path dir/sub/../. dir/
+norm_path dir/s1/../s2/ dir/s2/
+norm_path d1/s1///s2/..//../s3/ d1/s3/
+norm_path d1/s1//../s2/../../d2 d2
+norm_path d1/.../d2 d1/.../d2
+norm_path d1/..././../d2 d1/d2
+
+norm_path / / POSIX
+norm_path // / POSIX
+norm_path /// / POSIX
+norm_path /. / POSIX
+norm_path /./ / POSIX
+norm_path /./.. ++failed++ POSIX
+norm_path /../. ++failed++ POSIX
+norm_path /./../.// ++failed++ POSIX
+norm_path /dir/.. / POSIX
+norm_path /dir/sub/../.. / POSIX
+norm_path /dir/sub/../../.. ++failed++ POSIX
+norm_path /dir /dir POSIX
+norm_path /dir// /dir/ POSIX
+norm_path /./dir /dir POSIX
+norm_path /dir/. /dir/ POSIX
+norm_path /dir///./ /dir/ POSIX
+norm_path /dir//sub/.. /dir/ POSIX
+norm_path /dir/sub/../ /dir/ POSIX
+norm_path //dir/sub/../. /dir/ POSIX
+norm_path /dir/s1/../s2/ /dir/s2/ POSIX
+norm_path /d1/s1///s2/..//../s3/ /d1/s3/ POSIX
+norm_path /d1/s1//../s2/../../d2 /d2 POSIX
+norm_path /d1/.../d2 /d1/.../d2 POSIX
+norm_path /d1/..././../d2 /d1/d2 POSIX
 
 ancestor / "" -1
 ancestor / / -1
@@ -80,9 +130,13 @@ ancestor /foo/bar /:/foo:/bar/ 4
 ancestor /foo/bar /foo:/:/bar/ 4
 ancestor /foo/bar /:/bar/:/fo 0
 ancestor /foo/bar /:/bar/ 0
-ancestor /foo/bar :://foo/. 4
-ancestor /foo/bar :://foo/.:: 4
-ancestor /foo/bar //foo/./::/bar 4
-ancestor /foo/bar ::/bar -1
+ancestor /foo/bar .:/foo/. 4
+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 570d3729bd2312a8d9cf90f3d2e1121a58f43de6..f19b4a2a4afa89fd1242d1acccb1e999e6a88c6d 100755 (executable)
@@ -157,7 +157,7 @@ test_expect_success '3-way not overwriting local changes (their side)' '
 
 '
 
-test_expect_success 'funny symlink in work tree' '
+test_expect_success SYMLINKS 'funny symlink in work tree' '
 
        git reset --hard &&
        git checkout -b sym-b side-b &&
@@ -177,7 +177,7 @@ test_expect_success 'funny symlink in work tree' '
 
 '
 
-test_expect_success 'funny symlink in work tree, un-unlink-able' '
+test_expect_success SYMLINKS 'funny symlink in work tree, un-unlink-able' '
 
        rm -fr a b &&
        git reset --hard &&
@@ -189,7 +189,7 @@ test_expect_success 'funny symlink in work tree, un-unlink-able' '
 '
 
 # clean-up from the above test
-chmod a+w a
+chmod a+w a 2>/dev/null
 rm -fr a b
 
 test_expect_success 'D/F setup' '
diff --git a/t/t1008-read-tree-overlay.sh b/t/t1008-read-tree-overlay.sh
new file mode 100755 (executable)
index 0000000..f9e0028
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+test_description='test multi-tree read-tree without merging'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo one >a &&
+       git add a &&
+       git commit -m initial &&
+       git tag initial &&
+       echo two >b &&
+       git add b &&
+       git commit -m second &&
+       git checkout -b side initial &&
+       echo three >a &&
+       mkdir b &&
+       echo four >b/c &&
+       git add b/c &&
+       git commit -m third
+'
+
+test_expect_success 'multi-read' '
+       git read-tree initial master side &&
+       (echo a; echo b/c) >expect &&
+       git ls-files >actual &&
+       test_cmp expect actual
+'
+
+test_done
+
index fc386ba033ac165a5f4a9fca0c6c6f5db49a314e..210e594f6f3c83cc1b0c423a0f692380ff57176b 100755 (executable)
@@ -126,7 +126,7 @@ test_expect_success 'no file/rev ambiguity check inside a bare repo' '
        cd foo.git && git show -s HEAD
 '
 
-test_expect_success 'detection should not be fooled by a symlink' '
+test_expect_success SYMLINKS 'detection should not be fooled by a symlink' '
        cd "$HERE" &&
        rm -fr foo.git &&
        git clone -s .git another &&
index 7f7fc36734d96de96801c59af41024db97dc614d..c4414ff576fc03b3234c532bfdcd0f2c8eca9c09 100755 (executable)
@@ -40,6 +40,6 @@ test_expect_success \
 
 test_expect_success \
     'compare commit' \
-    'diff expected commit'
+    'test_cmp expected commit'
 
 test_done
index 11b82f43dd0220c736dd269b2f6531a1381edf3a..43ea283242c4afc25e53d4a8c894140793649717 100755 (executable)
@@ -118,7 +118,14 @@ EOF
 
 test_expect_success 'multiple unset is correct' 'cmp .git/config expect'
 
-mv .git/config2 .git/config
+cp .git/config2 .git/config
+
+test_expect_success '--replace-all missing value' '
+       test_must_fail git config --replace-all beta.haha &&
+       test_cmp .git/config2 .git/config
+'
+
+rm .git/config2
 
 test_expect_success '--replace-all' \
        'git config --replace-all beta.haha gamma'
@@ -336,10 +343,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 +380,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]
@@ -726,7 +733,7 @@ echo >>result
 
 test_expect_success '--null --get-regexp' 'cmp result expect'
 
-test_expect_success 'symlinked configuration' '
+test_expect_success SYMLINKS 'symlinked configuration' '
 
        ln -s notyet myconfig &&
        GIT_CONFIG=myconfig git config test.frotz nitfol &&
index 653362ba221ee017512264c83a216b1ad1723bcd..de42d21c922045415abedf3c81163682d0754eb5 100755 (executable)
@@ -26,7 +26,7 @@ modebits () {
 
 for u in 002 022
 do
-       test_expect_success "shared=1 does not clear bits preset by umask $u" '
+       test_expect_success POSIXPERM "shared=1 does not clear bits preset by umask $u" '
                mkdir sub && (
                        cd sub &&
                        umask $u &&
@@ -54,7 +54,7 @@ test_expect_success 'shared=all' '
        test 2 = $(git config core.sharedrepository)
 '
 
-test_expect_success 'update-server-info honors core.sharedRepository' '
+test_expect_success POSIXPERM 'update-server-info honors core.sharedRepository' '
        : > a1 &&
        git add a1 &&
        test_tick &&
@@ -85,7 +85,7 @@ do
        git config core.sharedrepository "$u" &&
        umask 0277 &&
 
-       test_expect_success "shared = $u ($y) ro" '
+       test_expect_success POSIXPERM "shared = $u ($y) ro" '
 
                rm -f .git/info/refs &&
                git update-server-info &&
@@ -97,7 +97,7 @@ do
        '
 
        umask 077 &&
-       test_expect_success "shared = $u ($x) rw" '
+       test_expect_success POSIXPERM "shared = $u ($x) rw" '
 
                rm -f .git/info/refs &&
                git update-server-info &&
@@ -111,7 +111,7 @@ do
 
 done
 
-test_expect_success 'git reflog expire honors core.sharedRepository' '
+test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '
        git config core.sharedRepository group &&
        git reflog expire --all &&
        actual="$(ls -l .git/logs/refs/heads/master)" &&
@@ -126,4 +126,45 @@ test_expect_success 'git reflog expire honors core.sharedRepository' '
        esac
 '
 
+test_expect_success POSIXPERM 'forced modes' '
+       mkdir -p templates/hooks &&
+       echo update-server-info >templates/hooks/post-update &&
+       chmod +x templates/hooks/post-update &&
+       echo : >random-file &&
+       mkdir new &&
+       (
+               cd new &&
+               umask 002 &&
+               git init --shared=0660 --template=../templates &&
+               >frotz &&
+               git add frotz &&
+               git commit -a -m initial &&
+               git repack
+       ) &&
+       # List repository files meant to be protected; note that
+       # COMMIT_EDITMSG does not matter---0mode is not about a
+       # repository with a work tree.
+       find new/.git -type f -name COMMIT_EDITMSG -prune -o -print |
+       xargs ls -ld >actual &&
+
+       # Everything must be unaccessible to others
+       test -z "$(sed -e "/^.......---/d" actual)" &&
+
+       # All directories must have either 2770 or 770
+       test -z "$(sed -n -e "/^drwxrw[sx]---/d" -e "/^d/p" actual)" &&
+
+       # post-update hook must be 0770
+       test -z "$(sed -n -e "/post-update/{
+               /^-rwxrwx---/d
+               p
+       }" actual)" &&
+
+       # All files inside objects must be accessible by us
+       test -z "$(sed -n -e "/objects\//{
+               /^d/d
+               /^-r.-r.----/d
+               p
+       }" actual)"
+'
+
 test_done
index bd589268fcf459f07ff6c53e43d41e66fe1e7903..54ba3df95f66ecc060adaec6846877c793016aa1 100755 (executable)
@@ -137,7 +137,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000
 EOF
 test_expect_success \
        "verifying $m's log" \
-       "diff expect .git/logs/$m"
+       "test_cmp expect .git/logs/$m"
 rm -rf .git/$m .git/logs expect
 
 test_expect_success \
@@ -168,7 +168,7 @@ $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000
 EOF
 test_expect_success \
        "verifying $m's log" \
-       'diff expect .git/logs/$m'
+       'test_cmp expect .git/logs/$m'
 rm -f .git/$m .git/logs/$m expect
 
 git update-ref $m $D
@@ -272,7 +272,7 @@ $h_FIXED $h_MERGED $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117151100 +0000      c
 EOF
 test_expect_success \
        'git commit logged updates' \
-       "diff expect .git/logs/$m"
+       "test_cmp expect .git/logs/$m"
 unset h_TEST h_OTHER h_FIXED h_MERGED
 
 test_expect_success \
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
index 5b24f05573221afb3dc472e78a443ba718db3c60..80af6b9b7ea50652dff6804b589d134207e9258f 100755 (executable)
@@ -70,9 +70,7 @@ test_expect_success setup '
        E=`git rev-parse --verify HEAD:A/B/E` &&
        check_fsck &&
 
-       chmod +x C &&
-       ( test "`git config --bool core.filemode`" != false ||
-         echo executable >>C ) &&
+       test_chmod +x C &&
        git add C &&
        test_tick && git commit -m dragon &&
        L=`git rev-parse --verify HEAD` &&
diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh
new file mode 100755 (executable)
index 0000000..c18ed8e
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+test_description='Test reflog display routines'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo content >file &&
+       git add file &&
+       test_tick &&
+       git commit -m one
+'
+
+cat >expect <<'EOF'
+Reflog: HEAD@{0} (C O Mitter <committer@example.com>)
+Reflog message: commit (initial): one
+EOF
+test_expect_success 'log -g shows reflog headers' '
+       git log -g -1 >tmp &&
+       grep ^Reflog <tmp >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+e46513e HEAD@{0}: commit (initial): one
+EOF
+test_expect_success 'oneline reflog format' '
+       git log -g -1 --oneline >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Reflog: HEAD@{Thu Apr 7 15:13:13 2005 -0700} (C O Mitter <committer@example.com>)
+Reflog message: commit (initial): one
+EOF
+test_expect_success 'using @{now} syntax shows reflog date (multiline)' '
+       git log -g -1 HEAD@{now} >tmp &&
+       grep ^Reflog <tmp >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+e46513e HEAD@{Thu Apr 7 15:13:13 2005 -0700}: commit (initial): one
+EOF
+test_expect_success 'using @{now} syntax shows reflog date (oneline)' '
+       git log -g -1 --oneline HEAD@{now} >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+Reflog: HEAD@{1112911993 -0700} (C O Mitter <committer@example.com>)
+Reflog message: commit (initial): one
+EOF
+test_expect_success 'using --date= shows reflog date (multiline)' '
+       git log -g -1 --date=raw >tmp &&
+       grep ^Reflog <tmp >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+e46513e HEAD@{1112911993 -0700}: commit (initial): one
+EOF
+test_expect_success 'using --date= shows reflog date (oneline)' '
+       git log -g -1 --oneline --date=raw >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh
new file mode 100755 (executable)
index 0000000..a22632f
--- /dev/null
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+test_description='git fsck random collection of tests'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit A fileA one &&
+       git checkout HEAD^0 &&
+       test_commit B fileB two &&
+       git tag -d A B &&
+       git reflog expire --expire=now --all
+'
+
+test_expect_success 'HEAD is part of refs' '
+       test 0 = $(git fsck | wc -l)
+'
+
+test_expect_success 'loose objects borrowed from alternate are not missing' '
+       mkdir another &&
+       (
+               cd another &&
+               git init &&
+               echo ../../../.git/objects >.git/objects/info/alternates &&
+               test_commit C fileC one &&
+               git fsck >out &&
+               ! grep "missing blob" out
+       )
+'
+
+# Corruption tests follow.  Make sure to remove all traces of the
+# specific corruption you test afterwards, lest a later test trip over
+# it.
+
+test_expect_success 'object with bad sha1' '
+       sha=$(echo blob | git hash-object -w --stdin) &&
+       echo $sha &&
+       old=$(echo $sha | sed "s+^..+&/+") &&
+       new=$(dirname $old)/ffffffffffffffffffffffffffffffffffffff &&
+       sha="$(dirname $new)$(basename $new)"
+       mv .git/objects/$old .git/objects/$new &&
+       git update-index --add --cacheinfo 100644 $sha foo &&
+       tree=$(git write-tree) &&
+       cmt=$(echo bogus | git commit-tree $tree) &&
+       git update-ref refs/heads/bogus $cmt &&
+       (git fsck 2>out; true) &&
+       grep "$sha.*corrupt" out &&
+       rm -f .git/objects/$new &&
+       git update-ref -d refs/heads/bogus &&
+       git read-tree -u --reset HEAD
+'
+
+test_expect_success 'branch pointing to non-commit' '
+       git rev-parse HEAD^{tree} > .git/refs/heads/invalid &&
+       git fsck 2>out &&
+       grep "not a commit" out &&
+       git update-ref -d refs/heads/invalid
+'
+
+cat > invalid-tag <<EOF
+object ffffffffffffffffffffffffffffffffffffffff
+type commit
+tag invalid
+tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+This is an invalid tag.
+EOF
+
+test_expect_failure 'tag pointing to nonexistent' '
+       tag=$(git hash-object -w --stdin < invalid-tag) &&
+       echo $tag > .git/refs/tags/invalid &&
+       git fsck --tags 2>out &&
+       cat out &&
+       grep "could not load tagged object" out &&
+       rm .git/refs/tags/invalid
+'
+
+cat > wrong-tag <<EOF
+object $(echo blob | git hash-object -w --stdin)
+type commit
+tag wrong
+tagger T A Gger <tagger@example.com> 1234567890 -0000
+
+This is an invalid tag.
+EOF
+
+test_expect_failure 'tag pointing to something else than its type' '
+       tag=$(git hash-object -w --stdin < wrong-tag) &&
+       echo $tag > .git/refs/tags/wrong &&
+       git fsck --tags 2>out &&
+       cat out &&
+       grep "some sane error message" out &&
+       rm .git/refs/tags/wrong
+'
+
+
+
+test_done
index 85da4caa7ed1b8bcaca7b21e218f2d1839d2db82..48ee07779d64147c3eb8325a3b3f9579f8ec41d8 100755 (executable)
@@ -26,21 +26,28 @@ test_rev_parse() {
        "test '$1' = \"\$(git rev-parse --show-prefix)\""
        shift
        [ $# -eq 0 ] && return
+
+       test_expect_success "$name: git-dir" \
+       "test '$1' = \"\$(git rev-parse --git-dir)\""
+       shift
+       [ $# -eq 0 ] && return
 }
 
-# label is-bare is-inside-git is-inside-work prefix
+# label is-bare is-inside-git is-inside-work prefix git-dir
+
+ROOT=$(pwd)
 
-test_rev_parse toplevel false false true ''
+test_rev_parse toplevel false false true '' .git
 
 cd .git || exit 1
-test_rev_parse .git/ false true false ''
+test_rev_parse .git/ false true false '' .
 cd objects || exit 1
-test_rev_parse .git/objects/ false true false ''
+test_rev_parse .git/objects/ false true false '' "$ROOT/.git"
 cd ../.. || exit 1
 
 mkdir -p sub/dir || exit 1
 cd sub/dir || exit 1
-test_rev_parse subdirectory false false true sub/dir/
+test_rev_parse subdirectory false false true sub/dir/ "$ROOT/.git"
 cd ../.. || exit 1
 
 git config core.bare true
index 27dc6c55d5f50a7fd30388b60230482bad6be2d8..f6a6f839a18de4c3775ea965f164d0d20f2bbe9b 100755 (executable)
@@ -92,13 +92,6 @@ cd sub/dir || exit 1
 test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
 cd ../../../.. || exit 1
 
-test_expect_success 'detecting gitdir when cwd is in a subdir of gitdir' '
-       (expected=$(pwd)/repo.git &&
-        cd repo.git/refs &&
-        unset GIT_DIR &&
-        test "$expected" = "$(git rev-parse --git-dir)")
-'
-
 test_expect_success 'repo finds its work tree' '
        (cd repo.git &&
         : > work/sub/dir/untracked &&
index e377d48902cd5fd539a0ce4cb1fb32ceb8732632..df5ad8c686a959c9b03355c1ebc325f3c755ed46 100755 (executable)
@@ -13,7 +13,7 @@ test_fail() {
        "git rev-parse --show-prefix"
 }
 
-TRASH_ROOT="$(pwd)"
+TRASH_ROOT="$PWD"
 ROOT_PARENT=$(dirname "$TRASH_ROOT")
 
 
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
+
+
index ef007532b15108d72445f7c95a2906a3039fbbbb..98aa73e8239355eba098253edfd156d4ea254be2 100755 (executable)
@@ -59,10 +59,10 @@ test_expect_success \
     'git read-tree -m $tree1 && git checkout-index -f -a'
 test_debug 'show_files $tree1'
 
-ln -s path0 path1
-test_expect_success \
+test_expect_success SYMLINKS \
     'git update-index --add a symlink.' \
-    'git update-index --add path1'
+    'ln -s path0 path1 &&
+     git update-index --add path1'
 test_expect_success \
     'writing tree out with git write-tree' \
     'tree3=$(git write-tree)'
index 71894b37439bd1b9c72194cbbabe37680d2f9743..02a4fc5d36a08d1046b9384c799f697640da0c4e 100755 (executable)
@@ -19,7 +19,7 @@ test_expect_success \
     echo rezrov >path1/file1 &&
     git update-index --add path0 path1/file1'
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'have symlink in place where dir is expected.' \
     'rm -fr path0 path1 &&
      mkdir path2 &&
@@ -59,7 +59,7 @@ test_expect_success \
      test ! -f path1/file1'
 
 # Linus fix #1
-test_expect_success \
+test_expect_success SYMLINKS \
     'use --prefix=tmp/orary/ where tmp is a symlink' \
     'rm -fr path0 path1 path2 tmp* &&
      mkdir tmp1 tmp1/orary &&
@@ -71,7 +71,7 @@ test_expect_success \
      test -h tmp'
 
 # Linus fix #2
-test_expect_success \
+test_expect_success SYMLINKS \
     'use --prefix=tmp/orary- where tmp is a symlink' \
     'rm -fr path0 path1 path2 tmp* &&
      mkdir tmp1 &&
@@ -82,7 +82,7 @@ test_expect_success \
      test -h tmp'
 
 # Linus fix #3
-test_expect_success \
+test_expect_success SYMLINKS \
     'use --prefix=tmp- where tmp-path1 is a symlink' \
     'rm -fr path0 path1 path2 tmp* &&
      mkdir tmp1 &&
index 39133b8c7a4b56cb7273cec607ea89081a426eff..36cca14d957f85733174d6ce514e22acfff3b1c9 100755 (executable)
@@ -194,7 +194,7 @@ test_expect_success \
  test $(cat ../$s1) = tree1asubdir/path5)
 )'
 
-test_expect_success \
+test_expect_success SYMLINKS \
 'checkout --temp symlink' '
 rm -f path* .merge_* out .git/index &&
 ln -s b a &&
index 0526fce163fc13273daf035a0920a6b53a3acefb..20f33436d00077b64dcc855fc263cd5d0efcca38 100755 (executable)
@@ -6,6 +6,12 @@ test_description='git checkout to switch between branches with symlink<->dir'
 
 . ./test-lib.sh
 
+if ! test_have_prereq SYMLINKS
+then
+       say "symbolic links not supported - skipping tests"
+       test_done
+fi
+
 test_expect_success setup '
 
        mkdir frotz &&
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
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
new file mode 100755 (executable)
index 0000000..fda3f0a
--- /dev/null
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='checkout can handle submodules'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       mkdir submodule &&
+       (cd submodule &&
+        git init &&
+        test_commit first) &&
+       git add submodule &&
+       test_tick &&
+       git commit -m superproject &&
+       (cd submodule &&
+        test_commit second) &&
+       git add submodule &&
+       test_tick &&
+       git commit -m updated.superproject
+'
+
+test_expect_success '"reset <submodule>" updates the index' '
+       git update-index --refresh &&
+       git diff-files --quiet &&
+       git diff-index --quiet --cached HEAD &&
+       test_must_fail git reset HEAD^ submodule &&
+       test_must_fail git diff-files --quiet &&
+       git reset submodule &&
+       git diff-files --quiet
+'
+
+test_expect_success '"checkout <submodule>" updates the index only' '
+       git update-index --refresh &&
+       git diff-files --quiet &&
+       git diff-index --quiet --cached HEAD &&
+       git checkout HEAD^ submodule &&
+       test_must_fail git diff-files --quiet &&
+       git checkout HEAD submodule &&
+       git diff-files --quiet
+'
+
+test_done
diff --git a/t/t2014-switch.sh b/t/t2014-switch.sh
new file mode 100755 (executable)
index 0000000..ccfb147
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+test_description='Peter MacMillan'
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo Hello >file &&
+       git add file &&
+       test_tick &&
+       git commit -m V1 &&
+       echo Hello world >file &&
+       git add file &&
+       git checkout -b other
+'
+
+test_expect_success 'check all changes are staged' '
+       git diff --exit-code
+'
+
+test_expect_success 'second commit' '
+       git commit -m V2
+'
+
+test_expect_success 'check' '
+       git diff --cached --exit-code
+'
+
+test_done
index 6ef2dcfd8afece86aaf6345630179af179eb2ed9..2df3fdde8bf665a2b531dd367b70a7a767ee3dbc 100755 (executable)
@@ -26,7 +26,12 @@ All of the attempts should fail.
 
 mkdir path2 path3
 date >path0
-ln -s xyzzy path1
+if test_have_prereq SYMLINKS
+then
+       ln -s xyzzy path1
+else
+       date > path1
+fi
 date >path2/file2
 date >path3/file3
 
@@ -38,7 +43,12 @@ rm -fr path?
 
 mkdir path0 path1
 date >path2
-ln -s frotz path3
+if test_have_prereq SYMLINKS
+then
+       ln -s frotz path3
+else
+       date > path3
+fi
 date >path0/file0
 date >path1/file1
 
index b2ddf5ace3581bc2a7056c61b9d43499b2657b65..912075063b9946d38a9ff72621cb80bcf2c05399 100755 (executable)
@@ -80,7 +80,7 @@ test_expect_success 'change gets noticed' '
 
 '
 
-test_expect_success 'replace a file with a symlink' '
+test_expect_success SYMLINKS 'replace a file with a symlink' '
 
        rm foo &&
        ln -s top foo &&
@@ -150,7 +150,7 @@ test_expect_success 'add -u resolves unmerged paths' '
        echo 2 >path3 &&
        echo 2 >path5 &&
        git add -u &&
-       git ls-files -s "path?" >actual &&
+       git ls-files -s path1 path2 path3 path4 path5 path6 >actual &&
        {
                echo "100644 $three 0   path1"
                echo "100644 $one 1     path3"
index d24c7d9e5fce0e9c0f8ef5576dab86ffdbc11331..2e8f70245204bd4dc78e67f227e86838e1cdad5b 100755 (executable)
@@ -11,7 +11,13 @@ test_expect_success setup '
        _empty=$(git hash-object --stdin <xyzzy) &&
        >yomin &&
        >caskly &&
-       ln -s frotz nitfol &&
+       if test_have_prereq SYMLINKS; then
+               ln -s frotz nitfol &&
+               T_letter=T
+       else
+               printf %s frotz > nitfol &&
+               T_letter=M
+       fi &&
        mkdir rezrov &&
        >rezrov/bozbar &&
        git add caskly xyzzy yomin nitfol rezrov/bozbar &&
@@ -29,7 +35,11 @@ test_expect_success modify '
        >nitfol &&
        # rezrov/bozbar disappears
        rm -fr rezrov &&
-       ln -s xyzzy rezrov &&
+       if test_have_prereq SYMLINKS; then
+               ln -s xyzzy rezrov
+       else
+               printf %s xyzzy > rezrov
+       fi &&
        # xyzzy disappears (not a submodule)
        mkdir xyzzy &&
        echo gnusto >xyzzy/bozbar &&
@@ -71,7 +81,7 @@ test_expect_success modify '
                                s/blob/000000/
                        }
                        /       nitfol/{
-                               s/      nitfol/ $_z40 T&/
+                               s/      nitfol/ $_z40 $T_letter&/
                                s/blob/100644/
                        }
                        /       rezrov.bozbar/{
index e42cbfe6c61951c6887a363cb668d26a7adcf20c..3b01ad2e4de152d23bbe8267d3b1a5cafd615e88 100755 (executable)
@@ -5,17 +5,17 @@ test_description='cd_to_toplevel'
 . ./test-lib.sh
 
 test_cd_to_toplevel () {
-       test_expect_success "$2" '
+       test_expect_success $3 "$2" '
                (
                        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
@@ -24,14 +24,14 @@ test_cd_to_toplevel repo 'at physical root'
 
 test_cd_to_toplevel repo/sub/dir 'at physical subdir'
 
-ln -s repo symrepo
-test_cd_to_toplevel symrepo 'at symbolic root'
+ln -s repo symrepo 2>/dev/null
+test_cd_to_toplevel symrepo 'at symbolic root' SYMLINKS
 
-ln -s repo/sub/dir subdir-link
-test_cd_to_toplevel subdir-link 'at symbolic subdir'
+ln -s repo/sub/dir subdir-link 2>/dev/null
+test_cd_to_toplevel subdir-link 'at symbolic subdir' SYMLINKS
 
 cd repo
-ln -s sub/dir internal-link
-test_cd_to_toplevel internal-link 'at internal symbolic subdir'
+ln -s sub/dir internal-link 2>/dev/null
+test_cd_to_toplevel internal-link 'at internal symbolic subdir' SYMLINKS
 
 test_done
index bc0a3513920cab41e4335b8c1b5163e25e8354d3..86291e839942e6842bf1d2b40f2d7f7c1d8d4a9f 100755 (executable)
@@ -13,12 +13,18 @@ filesystem.
     path2/file2 - a file in a directory
     path3-junk  - a file to confuse things
     path3/file3 - a file in a directory
+    path4       - an empty directory
 '
 . ./test-lib.sh
 
 date >path0
-ln -s xyzzy path1
-mkdir path2 path3
+if test_have_prereq SYMLINKS
+then
+       ln -s xyzzy path1
+else
+       date > path1
+fi
+mkdir path2 path3 path4
 date >path2/file2
 date >path2-junk
 date >path3/file3
@@ -28,6 +34,7 @@ git update-index --add path3-junk path3/file3
 cat >expected1 <<EOF
 expected1
 expected2
+expected3
 output
 path0
 path1
@@ -35,6 +42,8 @@ path2-junk
 path2/file2
 EOF
 sed -e 's|path2/file2|path2/|' <expected1 >expected2
+cat <expected2 >expected3
+echo path4/ >>expected2
 
 test_expect_success \
     'git ls-files --others to show output.' \
@@ -42,7 +51,7 @@ test_expect_success \
 
 test_expect_success \
     'git ls-files --others should pick up symlinks.' \
-    'diff output expected1'
+    'test_cmp expected1 output'
 
 test_expect_success \
     'git ls-files --others --directory to show output.' \
@@ -51,6 +60,14 @@ test_expect_success \
 
 test_expect_success \
     'git ls-files --others --directory should not get confused.' \
-    'diff output expected2'
+    'test_cmp expected2 output'
+
+test_expect_success \
+    'git ls-files --others --directory --no-empty-directory to show output.' \
+    'git ls-files --others --directory --no-empty-directory >output'
+
+test_expect_success \
+    '--no-empty-directory hides empty directory' \
+    'test_cmp expected3 output'
 
 test_done
index ec1404063701eef04667d5ffbbb4bdc8051c773b..95671c205364a12bea02173b33d0d427d5c546fe 100755 (executable)
@@ -38,7 +38,12 @@ modified without reporting path9 and path10.
 . ./test-lib.sh
 
 date >path0
-ln -s xyzzy path1
+if test_have_prereq SYMLINKS
+then
+       ln -s xyzzy path1
+else
+       date > path1
+fi
 mkdir path2 path3
 date >path2/file2
 date >path3/file3
@@ -52,8 +57,14 @@ test_expect_success \
 
 rm -fr path? ;# leave path10 alone
 date >path2
-ln -s frotz path3
-ln -s nitfol path5
+if test_have_prereq SYMLINKS
+then
+       ln -s frotz path3
+       ln -s nitfol path5
+else
+       date > path3
+       date > path5
+fi
 mkdir path0 path1 path6
 date >path0/file0
 date >path1/file1
@@ -75,7 +86,7 @@ EOF
 
 test_expect_success \
     'validate git ls-files -k output.' \
-    'diff .output .expected'
+    'test_cmp .expected .output'
 
 test_expect_success \
     'git ls-files -m to show modified files.' \
@@ -91,6 +102,6 @@ EOF
 
 test_expect_success \
     'validate git ls-files -m output.' \
-    'diff .output .expected'
+    'test_cmp .expected .output'
 
 test_done
diff --git a/t/t3031-merge-criscross.sh b/t/t3031-merge-criscross.sh
new file mode 100755 (executable)
index 0000000..7f41607
--- /dev/null
@@ -0,0 +1,95 @@
+#!/bin/sh
+
+test_description='merge-recursive backend test'
+
+. ./test-lib.sh
+
+#         A      <- create some files
+#        / \
+#       B   C    <- cause rename/delete conflicts between B and C
+#      /     \
+#     |\     /|
+#     | D   E |
+#     |  \ /  |
+#     |   X   |
+#     |  / \  |
+#     | /   \ |
+#     |/     \|
+#     F       G  <- merge E into B, D into C
+#      \     /
+#       \   /
+#        \ /
+#         H      <- recursive merge crashes
+#
+
+# initialize
+test_expect_success 'setup repo with criss-cross history' '
+       mkdir data &&
+
+       # create a bunch of files
+       n=1 &&
+       while test $n -le 10
+       do
+               echo $n > data/$n &&
+               n=$(($n+1)) ||
+               break
+       done &&
+
+       # check them in
+       git add data &&
+       git commit -m A &&
+       git branch A &&
+
+       # a file in one branch
+       git checkout -b B A &&
+       git rm data/9 &&
+       git add data &&
+       git commit -m B &&
+
+       # with a branch off of it
+       git branch D &&
+
+       # put some commits on D
+       git checkout D &&
+       echo testD > data/testD &&
+       git add data &&
+       git commit -m D &&
+
+       # back up to the top, create another branch and cause
+       # a rename conflict with the file we deleted earlier
+       git checkout -b C A &&
+       git mv data/9 data/new-9 &&
+       git add data &&
+       git commit -m C &&
+
+       # with a branch off of it
+       git branch E &&
+
+       # put a commit on E
+       git checkout E &&
+       echo testE > data/testE &&
+       git add data &&
+       git commit -m E &&
+
+       # now, merge E into B
+       git checkout B &&
+       test_must_fail git merge E &&
+       # force-resolve
+       git add data &&
+       git commit -m F &&
+       git branch F &&
+
+       # and merge D into C
+       git checkout C &&
+       test_must_fail git merge D &&
+       # force-resolve
+       git add data &&
+       git commit -m G &&
+       git branch G
+'
+
+test_expect_success 'recursive merge between F and G, causes segfault' '
+       git merge F
+'
+
+test_done
index 6e6a2542a22712006ae23874069c55943a3cba27..ee60d03fe8a582100e9df5511f6fea8900bcc543 100755 (executable)
@@ -22,9 +22,21 @@ test_expect_success \
     'setup' \
     'mkdir path2 path2/baz &&
      echo Hi >path0 &&
-     ln -s path0 path1 &&
+     if test_have_prereq SYMLINKS
+     then
+       ln -s path0 path1 &&
+       ln -s ../path1 path2/bazbo
+       make_expected () {
+               cat >expected
+       }
+     else
+       printf path0 > path1 &&
+       printf ../path1 > path2/bazbo
+       make_expected () {
+               sed -e "s/120000 /100644 /" >expected
+       }
+     fi &&
      echo Lo >path2/foo &&
-     ln -s ../path1 path2/bazbo &&
      echo Mi >path2/baz/b &&
      find path? \( -type f -o -type l \) -print |
      xargs git update-index --add &&
@@ -41,7 +53,7 @@ test_output () {
 test_expect_success \
     'ls-tree plain' \
     'git ls-tree $tree >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 040000 tree X  path2
@@ -51,7 +63,7 @@ EOF
 test_expect_success \
     'ls-tree recursive' \
     'git ls-tree -r $tree >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 100644 blob X  path2/baz/b
@@ -63,7 +75,7 @@ EOF
 test_expect_success \
     'ls-tree recursive with -t' \
     'git ls-tree -r -t $tree >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 040000 tree X  path2
@@ -77,7 +89,7 @@ EOF
 test_expect_success \
     'ls-tree recursive with -d' \
     'git ls-tree -r -d $tree >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 040000 tree X  path2
 040000 tree X  path2/baz
 EOF
@@ -86,7 +98,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path' \
     'git ls-tree $tree path >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 EOF
      test_output'
 
@@ -96,7 +108,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path1 path0' \
     'git ls-tree $tree path1 path0 >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
 EOF
@@ -105,7 +117,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path0/' \
     'git ls-tree $tree path0/ >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 EOF
      test_output'
 
@@ -114,7 +126,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2' \
     'git ls-tree $tree path2 >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 040000 tree X  path2
 EOF
      test_output'
@@ -123,7 +135,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2/' \
     'git ls-tree $tree path2/ >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 040000 tree X  path2/baz
 120000 blob X  path2/bazbo
 100644 blob X  path2/foo
@@ -135,7 +147,7 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2/baz' \
     'git ls-tree $tree path2/baz >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 040000 tree X  path2/baz
 EOF
      test_output'
@@ -143,14 +155,14 @@ EOF
 test_expect_success \
     'ls-tree filtered with path2/bak' \
     'git ls-tree $tree path2/bak >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 EOF
      test_output'
 
 test_expect_success \
     'ls-tree -t filtered with path2/bak' \
     'git ls-tree -t $tree path2/bak >current &&
-     cat >expected <<\EOF &&
+     make_expected <<\EOF &&
 040000 tree X  path2
 EOF
      test_output'
index 1b1e9ece572b68d5a415458c799690c06ddd3695..d59a9b4aef5fc53e42ec95a3b96a18fb67aa82cb 100755 (executable)
@@ -121,7 +121,7 @@ test_expect_success 'renaming a symref is not allowed' \
        ! test -f .git/refs/heads/master3
 '
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'git branch -m u v should fail when the reflog for u is a symlink' '
      git branch -l u &&
      mv .git/logs/refs/heads/u real-u &&
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
new file mode 100755 (executable)
index 0000000..809d1c4
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='git branch display tests'
+. ./test-lib.sh
+
+test_expect_success 'make commits' '
+       echo content >file &&
+       git add file &&
+       git commit -m one &&
+       echo content >>file &&
+       git commit -a -m two
+'
+
+test_expect_success 'make branches' '
+       git branch branch-one
+       git branch branch-two HEAD^
+'
+
+test_expect_success 'make remote branches' '
+       git update-ref refs/remotes/origin/branch-one branch-one
+       git update-ref refs/remotes/origin/branch-two branch-two
+       git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/branch-one
+'
+
+cat >expect <<'EOF'
+  branch-one
+  branch-two
+* master
+EOF
+test_expect_success 'git branch shows local branches' '
+       git branch >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+  origin/HEAD -> origin/branch-one
+  origin/branch-one
+  origin/branch-two
+EOF
+test_expect_success 'git branch -r shows remote branches' '
+       git branch -r >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+  branch-one
+  branch-two
+* master
+  remotes/origin/HEAD -> origin/branch-one
+  remotes/origin/branch-one
+  remotes/origin/branch-two
+EOF
+test_expect_success 'git branch -a shows local and remote branches' '
+       git branch -a >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+two
+one
+two
+EOF
+test_expect_success 'git branch -v shows branch summaries' '
+       git branch -v >tmp &&
+       awk "{print \$NF}" <tmp >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<'EOF'
+* (no branch)
+  branch-one
+  branch-two
+  master
+EOF
+test_expect_success 'git branch shows detached HEAD properly' '
+       git checkout HEAD^0 &&
+       git branch >actual &&
+       test_cmp expect actual
+'
+
+test_done
index b7a670ef401429a50eb97e5f874c9e2c3897fd7d..6e391a370208c9a0a6e73facc87f6a0e5081b929 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 &&
@@ -78,10 +83,16 @@ test_expect_success 'rebase a single mode change' '
      git checkout -b modechange HEAD^ &&
      echo 1 > X &&
      git add X &&
-     chmod a+x A &&
+     test_chmod +x A &&
      test_tick &&
-     git commit -m modechange A X &&
+     git commit -m modechange &&
      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 4becc5513dc3f46b73fbe8d7ea09a4c2912aa0a2..c32ff6682b932b3d9b270de7260a2671d8d07403 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" &&
@@ -488,4 +459,15 @@ test_expect_success 'submodule rebase -i' '
        FAKE_LINES="1 squash 2 3" git rebase -i A
 '
 
+test_expect_success 'avoid unnecessary reset' '
+       git checkout master &&
+       test-chmtime =123456789 file3 &&
+       git update-index --refresh &&
+       HEAD=$(git rev-parse HEAD) &&
+       git rebase -i HEAD~4 &&
+       test $HEAD = $(git rev-parse HEAD) &&
+       MTIME=$(test-chmtime -v +0 file3 | sed 's/[^0-9].*$//') &&
+       test 123456789 = $MTIME
+'
+
 test_done
index 539108094345e3e0ba4cf03fc20a5ca6486fa230..85fc7c4af8cebdb50a7fa294b274bb2e7988997b 100755 (executable)
@@ -22,7 +22,8 @@ test_expect_success setup '
        git checkout topic &&
        quick_one A &&
        quick_one B &&
-       quick_one Z
+       quick_one Z &&
+       git tag start
 
 '
 
@@ -41,4 +42,24 @@ test_expect_success 'rebase -m' '
 
 '
 
+test_expect_success 'rebase --stat' '
+        git reset --hard start
+        git rebase --stat master >diffstat.txt &&
+        grep "^ fileX |  *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase w/config rebase.stat' '
+        git reset --hard start
+        git config rebase.stat true &&
+        git rebase master >diffstat.txt &&
+        grep "^ fileX |  *1 +$" diffstat.txt
+'
+
+test_expect_success 'rebase -n overrides config rebase.stat config' '
+        git reset --hard start
+        git config rebase.stat true &&
+        git rebase -n master >diffstat.txt &&
+        ! grep "^ fileX |  *1 +$" diffstat.txt
+'
+
 test_done
diff --git a/t/t3409-rebase-hook.sh b/t/t3409-rebase-hook.sh
deleted file mode 100755 (executable)
index 1f1b850..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-#!/bin/sh
-
-test_description='git rebase with its hook(s)'
-
-. ./test-lib.sh
-
-test_expect_success setup '
-       echo hello >file &&
-       git add file &&
-       test_tick &&
-       git commit -m initial &&
-       echo goodbye >file &&
-       git add file &&
-       test_tick &&
-       git commit -m second &&
-       git checkout -b side HEAD^ &&
-       echo world >git &&
-       git add git &&
-       test_tick &&
-       git commit -m side &&
-       git checkout master &&
-       git log --pretty=oneline --abbrev-commit --graph --all &&
-       git branch test side
-'
-
-test_expect_success 'rebase' '
-       git checkout test &&
-       git reset --hard side &&
-       git rebase master &&
-       test "z$(cat git)" = zworld
-'
-
-test_expect_success 'rebase -i' '
-       git checkout test &&
-       git reset --hard side &&
-       EDITOR=true git rebase -i master &&
-       test "z$(cat git)" = zworld
-'
-
-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
-'
-
-test_expect_success 'pre-rebase hook gets correct input (1)' '
-       git checkout test &&
-       git reset --hard side &&
-       git rebase master &&
-       test "z$(cat git)" = zworld &&
-       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,
-
-'
-
-test_expect_success 'pre-rebase hook gets correct input (2)' '
-       git checkout test &&
-       git reset --hard side &&
-       git rebase master test &&
-       test "z$(cat git)" = zworld &&
-       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
-'
-
-test_expect_success 'pre-rebase hook gets correct input (3)' '
-       git checkout test &&
-       git reset --hard side &&
-       git checkout master &&
-       git rebase master test &&
-       test "z$(cat git)" = zworld &&
-       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
-'
-
-test_expect_success 'pre-rebase hook gets correct input (4)' '
-       git checkout test &&
-       git reset --hard side &&
-       EDITOR=true git rebase -i master &&
-       test "z$(cat git)" = zworld &&
-       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,
-
-'
-
-test_expect_success 'pre-rebase hook gets correct input (5)' '
-       git checkout test &&
-       git reset --hard side &&
-       EDITOR=true git rebase -i master test &&
-       test "z$(cat git)" = zworld &&
-       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
-'
-
-test_expect_success 'pre-rebase hook gets correct input (6)' '
-       git checkout test &&
-       git reset --hard side &&
-       git checkout master &&
-       EDITOR=true git rebase -i master test &&
-       test "z$(cat git)" = zworld &&
-       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
-'
-
-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 (1)' '
-       git checkout test &&
-       git reset --hard side &&
-       test_must_fail git rebase master &&
-       test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
-       test 0 = $(git rev-list HEAD...side | wc -l)
-'
-
-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 &&
-       test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
-       test 0 = $(git rev-list HEAD...side | wc -l)
-'
-
-test_expect_success 'rebase --no-verify overrides pre-rebase (1)' '
-       git checkout test &&
-       git reset --hard side &&
-       git rebase --no-verify master &&
-       test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
-       test "z$(cat git)" = zworld
-'
-
-test_expect_success 'rebase --no-verify overrides pre-rebase (2)' '
-       git checkout test &&
-       git reset --hard side &&
-       EDITOR=true git rebase --no-verify -i master &&
-       test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
-       test "z$(cat git)" = zworld
-'
-
-test_done
index 5816415aafe02f03ede5e1afb6f2e92dedc4b3da..c49143a1a45d6949253e2daf0e57f60029041e60 100755 (executable)
@@ -22,47 +22,17 @@ rewritten.
 # where B, D and G touch the same file.
 
 test_expect_success 'setup' '
-       : > file1 &&
-       git add file1 &&
-       test_tick &&
-       git commit -m A &&
-       git tag A &&
-       echo 1 > file1 &&
-       test_tick &&
-       git commit -m B file1 &&
-       : > file2 &&
-       git add file2 &&
-       test_tick &&
-       git commit -m C &&
-       echo 2 > file1 &&
-       test_tick &&
-       git commit -m D file1 &&
-       : > file3 &&
-       git add file3 &&
-       test_tick &&
-       git commit -m E &&
-       git tag E &&
-       git checkout -b branch1 A &&
-       : > file4 &&
-       git add file4 &&
-       test_tick &&
-       git commit -m F &&
-       git tag F &&
-       echo 3 > file1 &&
-       test_tick &&
-       git commit -m G file1 &&
-       git tag G &&
-       : > file5 &&
-       git add file5 &&
-       test_tick &&
-       git commit -m H &&
-       git tag H &&
-       git checkout -b branch2 F &&
-       : > file6 &&
-       git add file6 &&
-       test_tick &&
-       git commit -m I &&
-       git tag I
+       test_commit A file1 &&
+       test_commit B file1 1 &&
+       test_commit C file2 &&
+       test_commit D file1 2 &&
+       test_commit E file3 &&
+       git checkout A &&
+       test_commit F file4 &&
+       test_commit G file1 3 &&
+       test_commit H file5 &&
+       git checkout F &&
+       test_commit I file6
 '
 
 # A - B - C - D - E
@@ -72,68 +42,44 @@ test_expect_success 'setup' '
 #         I -- G2 -- J -- K           I -- K
 # G2 = same changes as G
 test_expect_success 'skip same-resolution merges with -p' '
-       git checkout branch1 &&
+       git checkout H &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m L &&
-       git checkout branch2 &&
-       echo 3 > file1 &&
-       git commit -a -m G2 &&
+       test_commit L file1 23 &&
+       git checkout I &&
+       test_commit G2 file1 3 &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m J &&
-       echo file7 > file7 &&
-       git add file7 &&
-       git commit -m K &&
-       GIT_EDITOR=: git rebase -i -p branch1 &&
-       test $(git rev-parse branch2^^) = $(git rev-parse branch1) &&
+       test_commit J file1 23 &&
+       test_commit K file7 file7 &&
+       git rebase -i -p L &&
+       test $(git rev-parse HEAD^^) = $(git rev-parse L) &&
        test "23" = "$(cat file1)" &&
-       test "" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)" &&
-
-       git checkout branch1 &&
-       git reset --hard H &&
-       git checkout branch2 &&
-       git reset --hard I
+       test "I" = "$(cat file6)" &&
+       test "file7" = "$(cat file7)"
 '
 
 # A - B - C - D - E
 #   \             \ \
-#     F - G - H -- L \        -->   L
-#       \            |               \
-#         I -- G2 -- J -- K           I -- G2 -- K
+#     F - G - H -- L2 \        -->   L2
+#       \             |                \
+#         I -- G3 --- J2 -- K2           I -- G3 -- K2
 # G2 = different changes as G
 test_expect_success 'keep different-resolution merges with -p' '
-       git checkout branch1 &&
+       git checkout H &&
        ! git merge E &&
-       echo 23 > file1 &&
-       git add file1 &&
-       git commit -m L &&
-       git checkout branch2 &&
-       echo 4 > file1 &&
-       git commit -a -m G2 &&
+       test_commit L2 file1 23 &&
+       git checkout I &&
+       test_commit G3 file1 4 &&
        ! git merge E &&
-       echo 24 > file1 &&
-       git add file1 &&
-       git commit -m J &&
-       echo file7 > file7 &&
-       git add file7 &&
-       git commit -m K &&
-       ! GIT_EDITOR=: git rebase -i -p branch1 &&
+       test_commit J2 file1 24 &&
+       test_commit K2 file7 file7 &&
+       test_must_fail git rebase -i -p L2 &&
        echo 234 > file1 &&
        git add file1 &&
-       GIT_EDITOR=: git rebase --continue &&
-       test $(git rev-parse branch2^^^) = $(git rev-parse branch1) &&
+       git rebase --continue &&
+       test $(git rev-parse HEAD^^^) = $(git rev-parse L2) &&
        test "234" = "$(cat file1)" &&
-       test "" = "$(cat file6)" &&
-       test "file7" = "$(cat file7)" &&
-
-       git checkout branch1 &&
-       git reset --hard H &&
-       git checkout branch2 &&
-       git reset --hard I
+       test "I" = "$(cat file6)" &&
+       test "file7" = "$(cat file7)"
 '
 
 test_done
index aacfaae843395e383d999e570a6bc9942e4c1c0b..6533505218a51db369d03357603c2ad1926ce448 100755 (executable)
@@ -5,44 +5,14 @@
 
 test_description='git rebase preserve merges
 
-This test runs git rebase with and tries to squash a commit from after a merge
-to before the merge.
+This test runs git rebase with -p and tries to squash a commit from after
+a merge to before the merge.
 '
 . ./test-lib.sh
 
-# Copy/paste from t3404-rebase-interactive.sh
-echo "#!$SHELL_PATH" >fake-editor.sh
-cat >> fake-editor.sh <<\EOF
-case "$1" in
-*/COMMIT_EDITMSG)
-       test -z "$FAKE_COMMIT_MESSAGE" || echo "$FAKE_COMMIT_MESSAGE" > "$1"
-       test -z "$FAKE_COMMIT_AMEND" || echo "$FAKE_COMMIT_AMEND" >> "$1"
-       exit
-       ;;
-esac
-test -z "$EXPECT_COUNT" ||
-       test "$EXPECT_COUNT" = $(sed -e '/^#/d' -e '/^$/d' < "$1" | wc -l) ||
-       exit
-test -z "$FAKE_LINES" && exit
-grep -v '^#' < "$1" > "$1".tmp
-rm -f "$1"
-cat "$1".tmp
-action=pick
-for line in $FAKE_LINES; do
-       case $line in
-       squash|edit)
-               action="$line";;
-       *)
-               echo sed -n "${line}s/^pick/$action/p"
-               sed -n "${line}p" < "$1".tmp
-               sed -n "${line}s/^pick/$action/p" < "$1".tmp >> "$1"
-               action=pick;;
-       esac
-done
-EOF
+. ../lib-rebase.sh
 
-test_set_editor "$(pwd)/fake-editor.sh"
-chmod a+x fake-editor.sh
+set_fake_editor
 
 # set up two branches like this:
 #
@@ -51,27 +21,13 @@ chmod a+x fake-editor.sh
 #        -- C1 --
 
 test_expect_success 'setup' '
-       touch a &&
-       touch b &&
-       git add a &&
-       git commit -m A1 &&
-       git tag A1
-       git add b &&
-       git commit -m B1 &&
-       git tag B1 &&
-       git checkout -b branch &&
-       touch c &&
-       git add c &&
-       git commit -m C1 &&
-       git checkout master &&
-       touch d &&
-       git add d &&
-       git commit -m D1 &&
-       git merge branch &&
-       touch f &&
-       git add f &&
-       git commit -m F1 &&
-       git tag F1
+       test_commit A1 &&
+       test_commit B1 &&
+       test_commit C1 &&
+       git reset --hard B1 &&
+       test_commit D1 &&
+       test_merge E1 C1 &&
+       test_commit F1
 '
 
 # Should result in:
@@ -82,7 +38,7 @@ test_expect_success 'setup' '
 #
 test_expect_success 'squash F1 into D1' '
        FAKE_LINES="1 squash 3 2" git rebase -i -p B1 &&
-       test "$(git rev-parse HEAD^2)" = "$(git rev-parse branch)" &&
+       test "$(git rev-parse HEAD^2)" = "$(git rev-parse C1)" &&
        test "$(git rev-parse HEAD~2)" = "$(git rev-parse B1)" &&
        git tag E2
 '
@@ -100,32 +56,15 @@ test_expect_success 'squash F1 into D1' '
 # And rebase G1..M1 onto E2
 
 test_expect_success 'rebase two levels of merge' '
-       git checkout -b branch2 A1 &&
-       touch g &&
-       git add g &&
-       git commit -m G1 &&
-       git checkout -b branch3 &&
-       touch h
-       git add h &&
-       git commit -m H1 &&
-       git checkout -b branch4 &&
-       touch i &&
-       git add i &&
-       git commit -m I1 &&
-       git tag I1 &&
-       git checkout branch3 &&
-       touch j &&
-       git add j &&
-       git commit -m J1 &&
-       git merge I1 --no-commit &&
-       git commit -m K1 &&
-       git tag K1 &&
-       git checkout branch2 &&
-       touch l &&
-       git add l &&
-       git commit -m L1 &&
-       git merge K1 --no-commit &&
-       git commit -m M1 &&
+       test_commit G1 &&
+       test_commit H1 &&
+       test_commit I1 &&
+       git checkout -b branch3 H1 &&
+       test_commit J1 &&
+       test_merge K1 I1 &&
+       git checkout -b branch2 G1 &&
+       test_commit L1 &&
+       test_merge M1 K1 &&
        GIT_EDITOR=: git rebase -i -p E2 &&
        test "$(git rev-parse HEAD~3)" = "$(git rev-parse E2)" &&
        test "$(git rev-parse HEAD~2)" = "$(git rev-parse HEAD^2^2~2)" &&
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
new file mode 100755 (executable)
index 0000000..5869061
--- /dev/null
@@ -0,0 +1,280 @@
+#!/bin/sh
+
+test_description='git rebase --root
+
+Tests if git rebase --root --onto <newparent> can rebase the root commit.
+'
+. ./test-lib.sh
+
+log_with_names () {
+       git rev-list --topo-order --parents --pretty="tformat:%s" HEAD |
+       git name-rev --stdin --name-only --refs=refs/heads/$1
+}
+
+
+test_expect_success 'prepare repository' '
+       test_commit 1 A &&
+       test_commit 2 A &&
+       git symbolic-ref HEAD refs/heads/other &&
+       rm .git/index &&
+       test_commit 3 B &&
+       test_commit 1b A 1 &&
+       test_commit 4 B
+'
+
+test_expect_success 'rebase --root expects --onto' '
+       test_must_fail git rebase --root
+'
+
+test_expect_success 'setup pre-rebase hook' '
+       mkdir -p .git/hooks &&
+       cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+echo "\$1,\$2" >.git/PRE-REBASE-INPUT
+EOF
+       chmod +x .git/hooks/pre-rebase
+'
+cat > expect <<EOF
+4
+3
+2
+1
+EOF
+
+test_expect_success 'rebase --root --onto <newbase>' '
+       git checkout -b work &&
+       git rebase --root --onto master &&
+       git log --pretty=tformat:"%s" > rebased &&
+       test_cmp expect rebased
+'
+
+test_expect_success 'pre-rebase got correct input (1)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase --root --onto <newbase> <branch>' '
+       git branch work2 other &&
+       git rebase --root --onto master work2 &&
+       git log --pretty=tformat:"%s" > rebased2 &&
+       test_cmp expect rebased2
+'
+
+test_expect_success 'pre-rebase got correct input (2)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work2
+'
+
+test_expect_success 'rebase -i --root --onto <newbase>' '
+       git checkout -b work3 other &&
+       git rebase -i --root --onto master &&
+       git log --pretty=tformat:"%s" > rebased3 &&
+       test_cmp expect rebased3
+'
+
+test_expect_success 'pre-rebase got correct input (3)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'rebase -i --root --onto <newbase> <branch>' '
+       git branch work4 other &&
+       git rebase -i --root --onto master work4 &&
+       git log --pretty=tformat:"%s" > rebased4 &&
+       test_cmp expect rebased4
+'
+
+test_expect_success 'pre-rebase got correct input (4)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,work4
+'
+
+test_expect_success 'rebase -i -p with linear history' '
+       git checkout -b work5 other &&
+       git rebase -i -p --root --onto master &&
+       git log --pretty=tformat:"%s" > rebased5 &&
+       test_cmp expect rebased5
+'
+
+test_expect_success 'pre-rebase got correct input (5)' '
+       test "z$(cat .git/PRE-REBASE-INPUT)" = z--root,
+'
+
+test_expect_success 'set up merge history' '
+       git checkout other^ &&
+       git checkout -b side &&
+       test_commit 5 C &&
+       git checkout other &&
+       git merge side
+'
+
+cat > expect-side <<'EOF'
+commit work6 work6~1 work6^2
+Merge branch 'side' into other
+commit work6^2 work6~2
+5
+commit work6~1 work6~2
+4
+commit work6~2 work6~3
+3
+commit work6~3 work6~4
+2
+commit work6~4
+1
+EOF
+
+test_expect_success 'rebase -i -p with merge' '
+       git checkout -b work6 other &&
+       git rebase -i -p --root --onto master &&
+       log_with_names work6 > rebased6 &&
+       test_cmp expect-side rebased6
+'
+
+test_expect_success 'set up second root and merge' '
+       git symbolic-ref HEAD refs/heads/third &&
+       rm .git/index &&
+       rm A B C &&
+       test_commit 6 D &&
+       git checkout other &&
+       git merge third
+'
+
+cat > expect-third <<'EOF'
+commit work7 work7~1 work7^2
+Merge branch 'third' into other
+commit work7^2 work7~4
+6
+commit work7~1 work7~2 work7~1^2
+Merge branch 'side' into other
+commit work7~1^2 work7~3
+5
+commit work7~2 work7~3
+4
+commit work7~3 work7~4
+3
+commit work7~4 work7~5
+2
+commit work7~5
+1
+EOF
+
+test_expect_success 'rebase -i -p with two roots' '
+       git checkout -b work7 other &&
+       git rebase -i -p --root --onto master &&
+       log_with_names work7 > rebased7 &&
+       test_cmp expect-third rebased7
+'
+
+test_expect_success 'setup pre-rebase hook that fails' '
+       mkdir -p .git/hooks &&
+       cat >.git/hooks/pre-rebase <<EOF &&
+#!$SHELL_PATH
+false
+EOF
+       chmod +x .git/hooks/pre-rebase
+'
+
+test_expect_success 'pre-rebase hook stops rebase' '
+       git checkout -b stops1 other &&
+       test_must_fail git rebase --root --onto master &&
+       test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops1
+       test 0 = $(git rev-list other...stops1 | wc -l)
+'
+
+test_expect_success 'pre-rebase hook stops rebase -i' '
+       git checkout -b stops2 other &&
+       test_must_fail git rebase --root --onto master &&
+       test "z$(git symbolic-ref HEAD)" = zrefs/heads/stops2
+       test 0 = $(git rev-list other...stops2 | wc -l)
+'
+
+test_expect_success 'remove pre-rebase hook' '
+       rm -f .git/hooks/pre-rebase
+'
+
+test_expect_success 'set up a conflict' '
+       git checkout master &&
+       echo conflict > B &&
+       git add B &&
+       git commit -m conflict
+'
+
+test_expect_success 'rebase --root with conflict (first part)' '
+       git checkout -b conflict1 other &&
+       test_must_fail git rebase --root --onto master &&
+       git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+       echo 3 > B &&
+       git add B
+'
+
+cat > expect-conflict <<EOF
+6
+5
+4
+3
+conflict
+2
+1
+EOF
+
+test_expect_success 'rebase --root with conflict (second part)' '
+       git rebase --continue &&
+       git log --pretty=tformat:"%s" > conflict1 &&
+       test_cmp expect-conflict conflict1
+'
+
+test_expect_success 'rebase -i --root with conflict (first part)' '
+       git checkout -b conflict2 other &&
+       test_must_fail git rebase -i --root --onto master &&
+       git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+       echo 3 > B &&
+       git add B
+'
+
+test_expect_success 'rebase -i --root with conflict (second part)' '
+       git rebase --continue &&
+       git log --pretty=tformat:"%s" > conflict2 &&
+       test_cmp expect-conflict conflict2
+'
+
+cat >expect-conflict-p <<\EOF
+commit conflict3 conflict3~1 conflict3^2
+Merge branch 'third' into other
+commit conflict3^2 conflict3~4
+6
+commit conflict3~1 conflict3~2 conflict3~1^2
+Merge branch 'side' into other
+commit conflict3~1^2 conflict3~3
+5
+commit conflict3~2 conflict3~3
+4
+commit conflict3~3 conflict3~4
+3
+commit conflict3~4 conflict3~5
+conflict
+commit conflict3~5 conflict3~6
+2
+commit conflict3~6
+1
+EOF
+
+test_expect_success 'rebase -i -p --root with conflict (first part)' '
+       git checkout -b conflict3 other &&
+       test_must_fail git rebase -i -p --root --onto master &&
+       git ls-files -u | grep "B$"
+'
+
+test_expect_success 'fix the conflict' '
+       echo 3 > B &&
+       git add B
+'
+
+test_expect_success 'rebase -i -p --root with conflict (second part)' '
+       git rebase --continue &&
+       log_with_names conflict3 >out &&
+       test_cmp expect-conflict-p out
+'
+
+test_done
diff --git a/t/t3413-rebase-hook.sh b/t/t3413-rebase-hook.sh
new file mode 100755 (executable)
index 0000000..098b755
--- /dev/null
@@ -0,0 +1,146 @@
+#!/bin/sh
+
+test_description='git rebase with its hook(s)'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo hello >file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       echo goodbye >file &&
+       git add file &&
+       test_tick &&
+       git commit -m second &&
+       git checkout -b side HEAD^ &&
+       echo world >git &&
+       git add git &&
+       test_tick &&
+       git commit -m side &&
+       git checkout master &&
+       git log --pretty=oneline --abbrev-commit --graph --all &&
+       git branch test side
+'
+
+test_expect_success 'rebase' '
+       git checkout test &&
+       git reset --hard side &&
+       git rebase master &&
+       test "z$(cat git)" = zworld
+'
+
+test_expect_success 'rebase -i' '
+       git checkout test &&
+       git reset --hard side &&
+       EDITOR=true git rebase -i master &&
+       test "z$(cat git)" = zworld
+'
+
+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
+'
+
+test_expect_success 'pre-rebase hook gets correct input (1)' '
+       git checkout test &&
+       git reset --hard side &&
+       git rebase master &&
+       test "z$(cat git)" = zworld &&
+       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,
+
+'
+
+test_expect_success 'pre-rebase hook gets correct input (2)' '
+       git checkout test &&
+       git reset --hard side &&
+       git rebase master test &&
+       test "z$(cat git)" = zworld &&
+       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
+'
+
+test_expect_success 'pre-rebase hook gets correct input (3)' '
+       git checkout test &&
+       git reset --hard side &&
+       git checkout master &&
+       git rebase master test &&
+       test "z$(cat git)" = zworld &&
+       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
+'
+
+test_expect_success 'pre-rebase hook gets correct input (4)' '
+       git checkout test &&
+       git reset --hard side &&
+       EDITOR=true git rebase -i master &&
+       test "z$(cat git)" = zworld &&
+       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,
+
+'
+
+test_expect_success 'pre-rebase hook gets correct input (5)' '
+       git checkout test &&
+       git reset --hard side &&
+       EDITOR=true git rebase -i master test &&
+       test "z$(cat git)" = zworld &&
+       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
+'
+
+test_expect_success 'pre-rebase hook gets correct input (6)' '
+       git checkout test &&
+       git reset --hard side &&
+       git checkout master &&
+       EDITOR=true git rebase -i master test &&
+       test "z$(cat git)" = zworld &&
+       test "z$(cat .git/PRE-REBASE-INPUT)" = zmaster,test
+'
+
+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 (1)' '
+       git checkout test &&
+       git reset --hard side &&
+       test_must_fail git rebase master &&
+       test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
+       test 0 = $(git rev-list HEAD...side | wc -l)
+'
+
+test_expect_success 'pre-rebase hook stops rebase (2)' '
+       git checkout test &&
+       git reset --hard side &&
+       (
+               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)
+'
+
+test_expect_success 'rebase --no-verify overrides pre-rebase (1)' '
+       git checkout test &&
+       git reset --hard side &&
+       git rebase --no-verify master &&
+       test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
+       test "z$(cat git)" = zworld
+'
+
+test_expect_success 'rebase --no-verify overrides pre-rebase (2)' '
+       git checkout test &&
+       git reset --hard side &&
+       EDITOR=true git rebase --no-verify -i master &&
+       test "z$(git symbolic-ref HEAD)" = zrefs/heads/test &&
+       test "z$(cat git)" = zworld
+'
+
+test_done
index 95542e9cfec1d7cc0631521c1c2bef3b7a139af7..76b1bb45456a18a8c1c33256695396cc2b65a3a9 100755 (executable)
@@ -12,30 +12,37 @@ test_expect_success \
     'Initialize test directory' \
     "touch -- foo bar baz 'space embedded' -q &&
      git add -- foo bar baz 'space embedded' -q &&
-     git commit -m 'add normal files' &&
-     test_tabs=y &&
-     if touch -- 'tab  embedded' 'newline
-embedded'
-     then
+     git commit -m 'add normal files'"
+
+if touch -- 'tab       embedded' 'newline
+embedded' 2>/dev/null
+then
+       test_set_prereq FUNNYNAMES
+else
+       say 'Your filesystem does not allow tabs in filenames.'
+fi
+
+test_expect_success FUNNYNAMES 'add files with funny names' "
      git add -- 'tab   embedded' 'newline
 embedded' &&
      git commit -m 'add files with tabs and newlines'
-     else
-         say 'Your filesystem does not allow tabs in filenames.'
-         test_tabs=n
-     fi"
+"
 
+# Determine rm behavior
 # Later we will try removing an unremovable path to make sure
 # git rm barfs, but if the test is run as root that cannot be
 # arranged.
-test_expect_success \
-    'Determine rm behavior' \
-    ': >test-file
-     chmod a-w .
-     rm -f test-file
-     test -f test-file && test_failed_remove=y
-     chmod 775 .
-     rm -f test-file'
+: >test-file
+chmod a-w .
+rm -f test-file 2>/dev/null
+if test -f test-file
+then
+       test_set_prereq RO_DIR
+else
+       say 'skipping removal failure test (perhaps running as root?)'
+fi
+chmod 775 .
+rm -f test-file
 
 test_expect_success \
     'Pre-check that foo exists and is in index before git rm foo' \
@@ -100,20 +107,16 @@ test_expect_success \
     'Test that "git rm -- -q" succeeds (remove a file that looks like an option)' \
     'git rm -- -q'
 
-test "$test_tabs" = y && test_expect_success \
+test_expect_success FUNNYNAMES \
     "Test that \"git rm -f\" succeeds with embedded space, tab, or newline characters." \
     "git rm -f 'space embedded' 'tab   embedded' 'newline
 embedded'"
 
-if test "$test_failed_remove" = y; then
-chmod a-w .
-test_expect_success \
-    'Test that "git rm -f" fails if its rm fails' \
-    'test_must_fail git rm -f baz'
-chmod 775 .
-else
-    test_expect_success 'skipping removal failure (perhaps running as root?)' :
-fi
+test_expect_success RO_DIR 'Test that "git rm -f" fails if its rm fails' '
+       chmod a-w . &&
+       test_must_fail git rm -f baz &&
+       chmod 775 .
+'
 
 test_expect_success \
     'When the rm in "git rm -f" fails, it should not remove the file from the index' \
index 9f6454d2ffa22ef8a207b1a4e5e2ba156e84cdb9..050de42ef4148a730c30520ccaad5e9871e536bd 100755 (executable)
@@ -30,7 +30,7 @@ test_expect_success \
         *) echo fail; git ls-files --stage xfoo1; (exit 1);;
         esac'
 
-test_expect_success 'git add: filemode=0 should not get confused by symlink' '
+test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo1 &&
        ln -s foo xfoo1 &&
        git add xfoo1 &&
@@ -51,7 +51,7 @@ test_expect_success \
         *) echo fail; git ls-files --stage xfoo2; (exit 1);;
         esac'
 
-test_expect_success 'git add: filemode=0 should not get confused by symlink' '
+test_expect_success SYMLINKS 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo2 &&
        ln -s foo xfoo2 &&
        git update-index --add xfoo2 &&
@@ -61,7 +61,7 @@ test_expect_success 'git add: filemode=0 should not get confused by symlink' '
        esac
 '
 
-test_expect_success \
+test_expect_success SYMLINKS \
        'git update-index --add: Test that executable bit is not used...' \
        'git config core.filemode 0 &&
         ln -s xfoo2 xfoo3 &&
@@ -179,7 +179,7 @@ test_expect_success 'git add --refresh' '
        test -z "`git diff-index HEAD -- foo`"
 '
 
-test_expect_success 'git add should fail atomically upon an unreadable file' '
+test_expect_success POSIXPERM 'git add should fail atomically upon an unreadable file' '
        git reset --hard &&
        date >foo1 &&
        date >foo2 &&
@@ -190,7 +190,7 @@ test_expect_success 'git add should fail atomically upon an unreadable file' '
 
 rm -f foo2
 
-test_expect_success 'git add --ignore-errors' '
+test_expect_success POSIXPERM 'git add --ignore-errors' '
        git reset --hard &&
        date >foo1 &&
        date >foo2 &&
@@ -201,7 +201,7 @@ test_expect_success 'git add --ignore-errors' '
 
 rm -f foo2
 
-test_expect_success 'git add (add.ignore-errors)' '
+test_expect_success POSIXPERM 'git add (add.ignore-errors)' '
        git config add.ignore-errors 1 &&
        git reset --hard &&
        date >foo1 &&
@@ -212,7 +212,7 @@ test_expect_success 'git add (add.ignore-errors)' '
 '
 rm -f foo2
 
-test_expect_success 'git add (add.ignore-errors = false)' '
+test_expect_success POSIXPERM 'git add (add.ignore-errors = false)' '
        git config add.ignore-errors 0 &&
        git reset --hard &&
        date >foo1 &&
@@ -222,7 +222,7 @@ test_expect_success 'git add (add.ignore-errors = false)' '
        ! ( git ls-files foo1 | grep foo1 )
 '
 
-test_expect_success 'git add '\''fo\[ou\]bar'\'' ignores foobar' '
+test_expect_success BSLASHPSPEC "git add 'fo\\[ou\\]bar' ignores foobar" '
        git reset --hard &&
        touch fo\[ou\]bar foobar &&
        git add '\''fo\[ou\]bar'\'' &&
index e95663d8e66d5b94e574a6b956625fccfd341a05..dfc65601aa2171bfc9321753a3d90db112d81f72 100755 (executable)
@@ -3,6 +3,11 @@
 test_description='add -i basic tests'
 . ./test-lib.sh
 
+if ! test_have_prereq PERL; then
+       say 'skipping git add -i tests, perl not available'
+       test_done
+fi
+
 test_expect_success 'setup (initial)' '
        echo content >file &&
        git add file &&
@@ -135,10 +140,12 @@ test_expect_success 'real edit works' '
 
 if test "$(git config --bool core.filemode)" = false
 then
-    say 'skipping filemode tests (filesystem does not properly support modes)'
+       say 'skipping filemode tests (filesystem does not properly support modes)'
 else
+       test_set_prereq FILEMODE
+fi
 
-test_expect_success 'patch does not affect mode' '
+test_expect_success FILEMODE 'patch does not affect mode' '
        git reset --hard &&
        echo content >>file &&
        chmod +x file &&
@@ -147,7 +154,7 @@ test_expect_success 'patch does not affect mode' '
        git diff file | grep "new mode"
 '
 
-test_expect_success 'stage mode but not hunk' '
+test_expect_success FILEMODE 'stage mode but not hunk' '
        git reset --hard &&
        echo content >>file &&
        chmod +x file &&
@@ -156,7 +163,6 @@ test_expect_success 'stage mode but not hunk' '
        git diff          file | grep "+content"
 '
 
-fi
 # end of tests disabled when filemode is not usable
 
 test_done
index cc3681f16118ca70ae9f65a27ccd6f354a6deee1..18695ce8218a7b383258eeb0bad84b4d4bde45be 100755 (executable)
@@ -258,4 +258,12 @@ test_expect_success \
     git diff-tree -r -R $tree_A $tree_B >.test-b &&
     cmp -s .test-a .test-b'
 
+test_expect_success \
+    'diff can read from stdin' \
+    'test_must_fail git diff --no-index -- MN - < NN |
+        grep -v "^index" | sed "s#/-#/NN#" >.test-a &&
+    test_must_fail git diff --no-index -- MN NN |
+        grep -v "^index" >.test-b &&
+    test_cmp .test-a .test-b'
+
 test_done
index b35af9b42d318904bd12649562be309fd49977a3..a4da1196a93a00502c8945a14e3aafd628efda53 100755 (executable)
@@ -12,6 +12,12 @@ by an edit for them.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh
 
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
 test_expect_success \
     'prepare reference tree' \
     'echo xyzzy | tr -d '\\\\'012 >yomin &&
index 4e92fce1d00a55cfbc39e55b53f95cc309e96ff2..8c1b81e248bced2ccb5e4ff0067996462e89deb8 100755 (executable)
@@ -15,21 +15,10 @@ test_expect_success \
      tree=`git write-tree` &&
      echo $tree'
 
-if [ "$(git config --get core.filemode)" = false ]
-then
-       say 'filemode disabled on the filesystem, using update-index --chmod=+x'
-       test_expect_success \
-           'git update-index --chmod=+x' \
-           'git update-index rezrov &&
-            git update-index --chmod=+x rezrov &&
-            git diff-index $tree >current'
-else
-       test_expect_success \
-           'chmod' \
-           'chmod +x rezrov &&
-            git update-index rezrov &&
-            git diff-index $tree >current'
-fi
+test_expect_success \
+    'chmod' \
+    'test_chmod +x rezrov &&
+     git diff-index $tree >current'
 
 _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"
index 7e343a9cd130a73547ed1df9a4d9cd364e60bf9f..e19ca65885a1e49916f68eee4abbe65e98227c50 100755 (executable)
@@ -99,7 +99,7 @@ test_expect_success \
     'validate result of -B -M (#4)' \
     'compare_diff_raw expected current'
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'make file0 into something completely different' \
     'rm -f file0 &&
      ln -s frotz file0 &&
@@ -114,7 +114,7 @@ cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M100  file1
 EOF
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'validate result of -B (#5)' \
     'compare_diff_raw expected current'
 
@@ -129,7 +129,7 @@ cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 R     file0   file1
 EOF
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'validate result of -B -M (#6)' \
     'compare_diff_raw expected current'
 
@@ -144,7 +144,7 @@ cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 M     file1
 EOF
 
-test_expect_success \
+test_expect_success SYMLINKS \
     'validate result of -M (#7)' \
     'compare_diff_raw expected current'
 
index 02efecae3ad06e5a62553e990fc0934dd0c65eab..d7e327cc5bc5984546032fb085fb581de5755e11 100755 (executable)
@@ -9,6 +9,12 @@ test_description='Test diff of symlinks.
 . ./test-lib.sh
 . "$TEST_DIRECTORY"/diff-lib.sh
 
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
 cat > expected << EOF
 diff --git a/frotz b/frotz
 new file mode 120000
@@ -82,4 +88,11 @@ test_expect_success \
     git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
+test_expect_success \
+    'diff symlinks with non-existing targets' \
+    'ln -s narf pinky &&
+    ln -s take\ over brain &&
+    test_must_fail git diff --no-index pinky brain > output 2> output.err &&
+    grep narf output &&
+    ! grep error output.err'
 test_done
index 3cf5b5c4ea05d861d75aa146a52d7d273dcaa4cb..f64aa48d24f2f5704d07b9285c94ba983f7d92ac 100755 (executable)
@@ -87,7 +87,7 @@ nul_to_q() {
 
 test_expect_success 'diff --no-index with binary creation' '
        echo Q | q_to_nul >binary &&
-       (:# hide error code from diff, which just indicates differences
+       (: hide error code from diff, which just indicates differences
         git diff --binary --no-index /dev/null binary >current ||
         true
        ) &&
index 9c709022efb1b553ef53b3100d39e852117cbeda..8b33321f8c44dc7f8a08fa144bca4f10444ceaa1 100755 (executable)
@@ -101,8 +101,7 @@ do
        '' | '#'*) continue ;;
        esac
        test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
-       cnt=`expr $test_count + 1`
-       pfx=`printf "%04d" $cnt`
+       pfx=`printf "%04d" $test_count`
        expect="$TEST_DIRECTORY/t4013/diff.$test"
        actual="$pfx-diff.$test"
 
@@ -207,6 +206,10 @@ log --root -c --patch-with-stat --summary master
 log --root --cc --patch-with-stat --summary master
 log -SF master
 log -SF -p master
+log --decorate --all
+
+rev-list --parents HEAD
+rev-list --children HEAD
 
 whatchanged master
 whatchanged -p master
@@ -243,11 +246,12 @@ format-patch --stdout initial..master
 format-patch --stdout --no-numbered initial..master
 format-patch --stdout --numbered initial..master
 format-patch --attach --stdout initial..side
+format-patch --attach --stdout --suffix=.diff initial..side
 format-patch --attach --stdout initial..master^
 format-patch --attach --stdout initial..master
 format-patch --inline --stdout initial..side
 format-patch --inline --stdout initial..master^
-format-patch --inline --stdout initial..master
+format-patch --inline --stdout --numbered-files initial..master
 format-patch --inline --stdout initial..master
 format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
 config format.subjectprefix DIFFERENT_PREFIX
@@ -268,6 +272,7 @@ diff --no-index --name-status dir2 dir
 diff --no-index --name-status -- dir2 dir
 diff --no-index dir dir3
 diff master master^ side
+diff --dirstat master~1 master~2
 EOF
 
 test_done
diff --git a/t/t4013/diff.diff_--dirstat_master~1_master~2 b/t/t4013/diff.diff_--dirstat_master~1_master~2
new file mode 100644 (file)
index 0000000..b672e1c
--- /dev/null
@@ -0,0 +1,3 @@
+$ git diff --dirstat master~1 master~2
+  40.0% dir/
+$
diff --git a/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side b/t/t4013/diff.format-patch_--attach_--stdout_--suffix=.diff_initial..side
new file mode 100644 (file)
index 0000000..52116d3
--- /dev/null
@@ -0,0 +1,61 @@
+$ git format-patch --attach --stdout --suffix=.diff initial..side
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="0001-Side.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: attachment; filename="0001-Side.diff"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
index e5ab74437ef5e42f7863e56546f0bdb1923c2949..ce49bd676e1a59eb015efc77e9963caa6a57a419 100644 (file)
@@ -22,9 +22,9 @@ This is the second commit.
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="0001-Second.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: attachment; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: attachment; filename="0001-Second.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Type: text/x-patch; name="0002-Third.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: attachment; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Disposition: attachment; filename="0002-Third.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -129,9 +129,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Type: text/x-patch; name="0003-Side.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: attachment; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Disposition: attachment; filename="0003-Side.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 2c71d20d376094fe1be51baa316fb080ad2155b4..5f1b23863bd286a946dda09a8a7f3beb022b1f66 100644 (file)
@@ -22,9 +22,9 @@ This is the second commit.
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="0001-Second.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: attachment; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: attachment; filename="0001-Second.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Type: text/x-patch; name="0002-Third.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: attachment; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Disposition: attachment; filename="0002-Third.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
index 38f790290a41311e490c493bdaf71774853cc861..4a2364abc2263e0e8925053acdd7c3c98282df2e 100644 (file)
@@ -20,9 +20,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Type: text/x-patch; name="0001-Side.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: attachment; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Disposition: attachment; filename="0001-Side.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master b/t/t4013/diff.format-patch_--inline_--stdout_--numbered-files_initial..master
new file mode 100644 (file)
index 0000000..43b81eb
--- /dev/null
@@ -0,0 +1,170 @@
+$ git format-patch --inline --stdout --numbered-files initial..master
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [PATCH 1/3] Second
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+
+This is the second commit.
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file2   |    3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="1"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="1"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [PATCH 2/3] Third
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file1   |    3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="2"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="2"
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+
+From c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:03:00 +0000
+Subject: [PATCH 3/3] Side
+MIME-Version: 1.0
+Content-Type: multipart/mixed; boundary="------------g-i-t--v-e-r-s-i-o-n"
+
+This is a multi-part message in MIME format.
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/plain; charset=UTF-8; format=fixed
+Content-Transfer-Encoding: 8bit
+
+---
+ dir/sub |    2 ++
+ file0   |    3 +++
+ file3   |    4 ++++
+ 3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
+
+
+--------------g-i-t--v-e-r-s-i-o-n
+Content-Type: text/x-patch; name="3"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="3"
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..7289e35 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++1
++2
+diff --git a/file0 b/file0
+index 01e79c3..f4615da 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++A
++B
++C
+diff --git a/file3 b/file3
+new file mode 100644
+index 0000000..7289e35
+--- /dev/null
++++ b/file3
+@@ -0,0 +1,4 @@
++A
++B
++1
++2
+
+--------------g-i-t--v-e-r-s-i-o-n--
+
+
+$
index 58f8a7b7d6a86021e5b01888324dea97e8a50347..ca3f60bf0ed3858903fc6c86b99309211c340d13 100644 (file)
@@ -22,9 +22,9 @@ This is the second commit.
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="0001-Second.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: inline; filename="0001-Second.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Type: text/x-patch; name="0002-Third.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Disposition: inline; filename="0002-Third.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -129,9 +129,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Type: text/x-patch; name="0003-Side.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Disposition: inline; filename="0003-Side.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 9e7bbdffa226e1d687b1ac93aa5334540708a97e..08f23014bc15792946160c4ef45fdcfeba7e933d 100644 (file)
@@ -22,9 +22,9 @@ This is the second commit.
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="0001-Second.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: inline; filename="0001-Second.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Type: text/x-patch; name="0002-Third.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Disposition: inline; filename="0002-Third.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -129,9 +129,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Type: text/x-patch; name="0003-Side.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Disposition: inline; filename="0003-Side.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index f881f644ccdd9954ae38709a75060a5621666849..07f1230d31f4cdd9f712bd2a69fea035a6998eb2 100644 (file)
@@ -22,9 +22,9 @@ This is the second commit.
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="0001-Second.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: inline; filename="0001-Second.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -80,9 +80,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Type: text/x-patch; name="0002-Third.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
+Content-Disposition: inline; filename="0002-Third.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
index 4f258b8858df79ecf475514b69df904e83e29ffa..29e00ab8af8503e6da3b15c55147da550d4919f7 100644 (file)
@@ -22,9 +22,9 @@ This is the second commit.
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Type: text/x-patch; name="0001-Second.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Disposition: inline; filename="0001-Second.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
index e86dce69a3a78d5cfe5cd82067df0381b0f635bd..67633d424a466507015c5a02e721be9b8e6fdfe4 100644 (file)
@@ -20,9 +20,9 @@ Content-Transfer-Encoding: 8bit
 
 
 --------------g-i-t--v-e-r-s-i-o-n
-Content-Type: text/x-patch; name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Type: text/x-patch; name="0001-Side.patch"
 Content-Transfer-Encoding: 8bit
-Content-Disposition: inline; filename="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
+Content-Disposition: inline; filename="0001-Side.patch"
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all
new file mode 100644 (file)
index 0000000..12da8ac
--- /dev/null
@@ -0,0 +1,34 @@
+$ git log --decorate --all
+commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (refs/heads/master)
+Merge: 9a6d494 c7a2ab9
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:04:00 2006 +0000
+
+    Merge branch 'side'
+
+commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (refs/heads/side)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:03:00 2006 +0000
+
+    Side
+
+commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:02:00 2006 +0000
+
+    Third
+
+commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:01:00 2006 +0000
+
+    Second
+    
+    This is the second commit.
+
+commit 444ac553ac7612cc88969031b02b3767fb8a353a (refs/heads/initial)
+Author: A U Thor <author@example.com>
+Date:   Mon Jun 26 00:00:00 2006 +0000
+
+    Initial
+$
index 3ceb8e73c5d4e963a2c08eddf41197431ad52178..bd7f5c0f70571d0aa0f9b9c50a343a322831976f 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat --summary master -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 43d77761f988b7f43f46a5d8fd85d406ef9caa42..14595a614c362da4515f8d5b783bb0e0f079c72b 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5187a26816723476b049c44bb3801816e8672cdf..5a4e72765d316b855a8c2beee8f417cd050de323 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --patch-with-stat master -- dir/
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index c9640976a8f3bd724004f945b00d71f43f00c8ea..df0aaa9f2ca780ce62951a5667ed9fe3aa005e41 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --cc --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index ad050af55ffa6983bc08ef5919dd20229584fd59..c11b5f2c7f3e6846643b7d7e0e214993f4a2cf0d 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 628c6c03bc6195268c3e7e003478fd042ef36db2..5f0c98f9ce3d9d067cfcb662084b975a19c90f03 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root --patch-with-stat master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5d4e0f13b59652b170c0b2498319f970d071f774..e62c368dc64dae87c6c168462b175712f197e7af 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root -c --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 217a2eb203718b91ccd79a04e0369f4c5d2905aa..b42c334439b71cdb391d832d498b90a22aad2656 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root -p master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index e17ccfc2340eadbb8c393de0aa2793f6f899ba56..e8f46159da1e5ca68524f5657010d06667a9c94b 100644 (file)
@@ -1,6 +1,6 @@
 $ git log --root master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index f8fefef2c3869337ba048d5cd6d95169dc60cf00..bf1326dc36629096fc4e4102375c8a9c550391aa 100644 (file)
@@ -1,6 +1,6 @@
 $ git log -p master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index e9d9e7b40a08d803901ac001e4bf9f0c37ad8b15..a8f6ce5abd642e51672eb3e0ae5ea66a08aa74d2 100644 (file)
@@ -1,6 +1,6 @@
 $ git log master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
diff --git a/t/t4013/diff.rev-list_--children_HEAD b/t/t4013/diff.rev-list_--children_HEAD
new file mode 100644 (file)
index 0000000..e7f17d5
--- /dev/null
@@ -0,0 +1,7 @@
+$ git rev-list --children HEAD
+59d314ad6f356dd08601a4cd5e530381da3e3c64
+c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a 59d314ad6f356dd08601a4cd5e530381da3e3c64
+9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 59d314ad6f356dd08601a4cd5e530381da3e3c64
+1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0
+444ac553ac7612cc88969031b02b3767fb8a353a 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+$
diff --git a/t/t4013/diff.rev-list_--parents_HEAD b/t/t4013/diff.rev-list_--parents_HEAD
new file mode 100644 (file)
index 0000000..65d2a80
--- /dev/null
@@ -0,0 +1,7 @@
+$ git rev-list --parents HEAD
+59d314ad6f356dd08601a4cd5e530381da3e3c64 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a
+c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a 444ac553ac7612cc88969031b02b3767fb8a353a
+9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44
+1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 444ac553ac7612cc88969031b02b3767fb8a353a
+444ac553ac7612cc88969031b02b3767fb8a353a
+$
index 9e6e1f27105ca50262e5eb3f2ff132b46c12fe45..fb08ce0e46d16d223269754295cc4a8dc4364268 100644 (file)
@@ -1,6 +1,6 @@
 $ git show master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 5facf2543db0135ceb3aafdc5ffe0395b429f44c..e96ff1fb8c11ff8c6774cb249f45744ed05a97c3 100644 (file)
@@ -1,6 +1,6 @@
 $ git whatchanged --root --cc --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index 10f6767e498b0d5bdadd73067e4ef2dd5f028e89..c0aff68ef68c2d956492e7f9817b9bf5afe58005 100644 (file)
@@ -1,6 +1,6 @@
 $ git whatchanged --root -c --patch-with-stat --summary master
 commit 59d314ad6f356dd08601a4cd5e530381da3e3c64
-Merge: 9a6d494... c7a2ab9...
+Merge: 9a6d494 c7a2ab9
 Author: A U Thor <author@example.com>
 Date:   Mon Jun 26 00:04:00 2006 +0000
 
index f045898fe3196b068d03a66fd9edeea6f32add30..922a8941ed4720e2ad6dcd500a3759187aace969 100755 (executable)
@@ -16,9 +16,7 @@ test_expect_success setup '
        git checkout -b side &&
 
        for i in 1 2 5 6 A B C 7 8 9 10; do echo "$i"; done >file &&
-       chmod +x elif &&
-       git update-index file elif &&
-       git update-index --chmod=+x elif &&
+       test_chmod +x elif &&
        git commit -m "Side changes #1" &&
 
        for i in D E F; do echo "$i"; done >>file &&
@@ -130,6 +128,21 @@ test_expect_success 'additional command line cc' '
        grep "^ *S. E. Cipient <scipient@example.com>$" patch5
 '
 
+test_expect_success 'command line headers' '
+
+       git config --unset-all format.headers &&
+       git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch6 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>$" patch6
+'
+
+test_expect_success 'configuration headers and command line headers' '
+
+       git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
+       git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch7 &&
+       grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch7 &&
+       grep "^ *S. E. Cipient <scipient@example.com>$" patch7
+'
+
 test_expect_success 'multiple files' '
 
        rm -rf patches/ &&
@@ -138,56 +151,243 @@ test_expect_success 'multiple files' '
        ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch
 '
 
-test_expect_success 'thread' '
+check_threading () {
+       expect="$1" &&
+       shift &&
+       (git format-patch --stdout "$@"; echo $? > status.out) |
+       # Prints everything between the Message-ID and In-Reply-To,
+       # and replaces all Message-ID-lookalikes by a sequence number
+       perl -ne '
+               if (/^(message-id|references|in-reply-to)/i) {
+                       $printing = 1;
+               } elsif (/^\S/) {
+                       $printing = 0;
+               }
+               if ($printing) {
+                       $h{$1}=$i++ if (/<([^>]+)>/ and !exists $h{$1});
+                       for $k (keys %h) {s/$k/$h{$k}/};
+                       print;
+               }
+               print "---\n" if /^From /i;
+       ' > actual &&
+       test 0 = "$(cat status.out)" &&
+       test_cmp "$expect" actual
+}
+
+cat >> expect.no-threading <<EOF
+---
+---
+---
+EOF
 
-       rm -rf patches/ &&
+test_expect_success 'no threading' '
        git checkout side &&
-       git format-patch --thread -o patches/ master &&
-       FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
-       for i in patches/0002-* patches/0003-*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+       check_threading expect.no-threading master
 '
 
-test_expect_success 'thread in-reply-to' '
+cat > expect.thread <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <0>
+EOF
 
-       rm -rf patches/ &&
-       git checkout side &&
-       git format-patch --in-reply-to="<test.message>" --thread -o patches/ master &&
-       FIRST_MID="<test.message>" &&
-       for i in patches/*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+test_expect_success 'thread' '
+       check_threading expect.thread --thread master
 '
 
-test_expect_success 'thread cover-letter' '
+cat > expect.in-reply-to <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <3>
+In-Reply-To: <1>
+References: <1>
+EOF
 
-       rm -rf patches/ &&
-       git checkout side &&
-       git format-patch --cover-letter --thread -o patches/ master &&
-       FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
-       for i in patches/0001-* patches/0002-* patches/0003-*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+test_expect_success 'thread in-reply-to' '
+       check_threading expect.in-reply-to --in-reply-to="<test.message>" \
+               --thread master
+'
+
+cat > expect.cover-letter <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <3>
+In-Reply-To: <0>
+References: <0>
+EOF
+
+test_expect_success 'thread cover-letter' '
+       check_threading expect.cover-letter --cover-letter --thread master
 '
 
+cat > expect.cl-irt <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <3>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <4>
+In-Reply-To: <0>
+References: <1>
+       <0>
+EOF
+
 test_expect_success 'thread cover-letter in-reply-to' '
+       check_threading expect.cl-irt --cover-letter \
+               --in-reply-to="<test.message>" --thread master
+'
 
-       rm -rf patches/ &&
-       git checkout side &&
-       git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master &&
-       FIRST_MID="<test.message>" &&
-       for i in patches/*
-       do
-         grep "References: $FIRST_MID" $i &&
-         grep "In-Reply-To: $FIRST_MID" $i || break
-       done
+test_expect_success 'thread explicit shallow' '
+       check_threading expect.cl-irt --cover-letter \
+               --in-reply-to="<test.message>" --thread=shallow master
+'
+
+cat > expect.deep <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <1>
+References: <0>
+       <1>
+EOF
+
+test_expect_success 'thread deep' '
+       check_threading expect.deep --thread=deep master
+'
+
+cat > expect.deep-irt <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <3>
+In-Reply-To: <2>
+References: <1>
+       <0>
+       <2>
+EOF
+
+test_expect_success 'thread deep in-reply-to' '
+       check_threading expect.deep-irt  --thread=deep \
+               --in-reply-to="<test.message>" master
+'
+
+cat > expect.deep-cl <<EOF
+---
+Message-Id: <0>
+---
+Message-Id: <1>
+In-Reply-To: <0>
+References: <0>
+---
+Message-Id: <2>
+In-Reply-To: <1>
+References: <0>
+       <1>
+---
+Message-Id: <3>
+In-Reply-To: <2>
+References: <0>
+       <1>
+       <2>
+EOF
+
+test_expect_success 'thread deep cover-letter' '
+       check_threading expect.deep-cl --cover-letter --thread=deep master
+'
+
+cat > expect.deep-cl-irt <<EOF
+---
+Message-Id: <0>
+In-Reply-To: <1>
+References: <1>
+---
+Message-Id: <2>
+In-Reply-To: <0>
+References: <1>
+       <0>
+---
+Message-Id: <3>
+In-Reply-To: <2>
+References: <1>
+       <0>
+       <2>
+---
+Message-Id: <4>
+In-Reply-To: <3>
+References: <1>
+       <0>
+       <2>
+       <3>
+EOF
+
+test_expect_success 'thread deep cover-letter in-reply-to' '
+       check_threading expect.deep-cl-irt --cover-letter \
+               --in-reply-to="<test.message>" --thread=deep master
+'
+
+test_expect_success 'thread via config' '
+       git config format.thread true &&
+       check_threading expect.thread master
+'
+
+test_expect_success 'thread deep via config' '
+       git config format.thread deep &&
+       check_threading expect.deep master
+'
+
+test_expect_success 'thread config + override' '
+       git config format.thread deep &&
+       check_threading expect.thread --thread master
+'
+
+test_expect_success 'thread config + --no-thread' '
+       git config format.thread deep &&
+       check_threading expect.no-threading --no-thread master
 '
 
 test_expect_success 'excessive subject' '
@@ -305,4 +505,15 @@ test_expect_success 'format-patch from a subdirectory (3)' '
        test -f "$basename"
 '
 
+test_expect_success 'format-patch --in-reply-to' '
+       git format-patch -1 --stdout --in-reply-to "baz@foo.bar" > patch8 &&
+       grep "^In-Reply-To: <baz@foo.bar>" patch8 &&
+       grep "^References: <baz@foo.bar>" patch8
+'
+
+test_expect_success 'format-patch --signoff' '
+       git format-patch -1 --signoff --stdout |
+       grep "^Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+'
+
 test_done
diff --git a/t/t4017-quiet.sh b/t/t4017-quiet.sh
deleted file mode 100755 (executable)
index e747e84..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-#!/bin/sh
-
-test_description='Return value of diffs'
-
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-       echo 1 >a &&
-       git add . &&
-       git commit -m first &&
-       echo 2 >b &&
-       git add . &&
-       git commit -a -m second
-'
-
-test_expect_success 'git diff-tree HEAD^ HEAD' '
-       git diff-tree --quiet HEAD^ HEAD >cnt
-       test $? = 1 && test $(wc -l <cnt) = 0
-'
-test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
-       git diff-tree --quiet HEAD^ HEAD -- a >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
-'
-test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
-       git diff-tree --quiet HEAD^ HEAD -- b >cnt
-       test $? = 1 && test $(wc -l <cnt) = 0
-'
-# this diff outputs one line: sha1 of the given head
-test_expect_success 'echo HEAD | git diff-tree --stdin' '
-       echo $(git rev-parse HEAD) | git diff-tree --quiet --stdin >cnt
-       test $? = 1 && test $(wc -l <cnt) = 1
-'
-test_expect_success 'git diff-tree HEAD HEAD' '
-       git diff-tree --quiet HEAD HEAD >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
-'
-test_expect_success 'git diff-files' '
-       git diff-files --quiet >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
-'
-test_expect_success 'git diff-index --cached HEAD' '
-       git diff-index --quiet --cached HEAD >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
-'
-test_expect_success 'git diff-index --cached HEAD^' '
-       git diff-index --quiet --cached HEAD^ >cnt
-       test $? = 1 && test $(wc -l <cnt) = 0
-'
-test_expect_success 'git diff-index --cached HEAD^' '
-       echo text >>b &&
-       echo 3 >c &&
-       git add . && {
-               git diff-index --quiet --cached HEAD^ >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
-       }
-'
-test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
-       git commit -m "text in b" && {
-               git diff-tree --quiet -Stext HEAD^ HEAD -- b >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
-       }
-'
-test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
-       git diff-tree --quiet -Snot-found HEAD^ HEAD -- b >cnt
-       test $? = 0 && test $(wc -l <cnt) = 0
-'
-test_expect_success 'git diff-files' '
-       echo 3 >>c && {
-               git diff-files --quiet >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
-       }
-'
-test_expect_success 'git diff-index --cached HEAD' '
-       git update-index c && {
-               git diff-index --quiet --cached HEAD >cnt
-               test $? = 1 && test $(wc -l <cnt) = 0
-       }
-'
-
-test_done
index be541348c6f8d469428e2fcc7abaf6d25168c08f..5b10e976a3fd0c554ff2fa14004e69343a9ec634 100755 (executable)
@@ -32,7 +32,7 @@ EOF
 
 sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
 
-builtin_patterns="bibtex html java objc pascal php python ruby tex"
+builtin_patterns="bibtex cpp html java objc pascal php python ruby tex"
 for p in $builtin_patterns
 do
        test_expect_success "builtin $p pattern compiles" '
index 2a72e751e290ad59d5277695a1128aebb678c2a1..0720001281db6aeb5a3b6bb46cd6914ad7d78d33 100755 (executable)
@@ -128,6 +128,30 @@ 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
+'
+
+echo "#!$SHELL_PATH" >fake-diff.sh
+cat >> fake-diff.sh <<\EOF
+cat $2 >> crlfed.txt
+EOF
+chmod a+x fake-diff.sh
+
+keep_only_cr () {
+       tr -dc '\015'
+}
+
+test_expect_success 'external diff with autocrlf = true' '
+       git config core.autocrlf true &&
+       GIT_EXTERNAL_DIFF=./fake-diff.sh git diff &&
+       test $(wc -l < crlfed.txt) = $(cat crlfed.txt | keep_only_cr | wc -c)
+'
+
 test_expect_success 'diff --cached' '
        git add file &&
        git update-index --assume-unchanged file &&
index 390af2389f3b9559fcfebe1e21a24317562ab7af..709b3231ca8d9da631727b4aadfb2f46049d37e9 100755 (executable)
@@ -86,6 +86,13 @@ test_expect_success 'format.numbered && --no-numbered' '
 
 '
 
+test_expect_success 'format.numbered && --keep-subject' '
+
+       git format-patch --keep-subject --stdout HEAD^ >patch4a &&
+       grep "^Subject: Third" patch4a
+
+'
+
 test_expect_success 'format.numbered = auto' '
 
        git config format.numbered auto
@@ -108,4 +115,10 @@ test_expect_success 'format.numbered = auto && --no-numbered' '
 
 '
 
+test_expect_success '--start-number && --numbered' '
+
+       git format-patch --start-number 3 --numbered --stdout HEAD~1 > patch8 &&
+       grep "^Subject: \[PATCH 3/3\]" patch8
+'
+
 test_done
diff --git a/t/t4021-format-patch-signer-mime.sh b/t/t4021-format-patch-signer-mime.sh
deleted file mode 100755 (executable)
index ba43f18..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/sh
-
-test_description='format-patch -s should force MIME encoding as needed'
-
-. ./test-lib.sh
-
-test_expect_success setup '
-
-       >F &&
-       git add F &&
-       git commit -m initial &&
-       echo new line >F &&
-
-       test_tick &&
-       git commit -m "This adds some lines to F" F
-
-'
-
-test_expect_success 'format normally' '
-
-       git format-patch --stdout -1 >output &&
-       ! grep Content-Type output
-
-'
-
-test_expect_success 'format with signoff without funny signer name' '
-
-       git format-patch -s --stdout -1 >output &&
-       ! grep Content-Type output
-
-'
-
-test_expect_success 'format with non ASCII signer name' '
-
-       GIT_COMMITTER_NAME="はまの ふにおう" \
-       git format-patch -s --stdout -1 >output &&
-       grep Content-Type output
-
-'
-
-test_expect_success 'attach and signoff do not duplicate mime headers' '
-
-       GIT_COMMITTER_NAME="はまの ふにおう" \
-       git format-patch -s --stdout -1 --attach >output &&
-       test `grep -ci ^MIME-Version: output` = 1
-
-'
-
-test_done
-
index 297ddb5a25b682412851aed70370771aa5a15976..9bdf6596d82878f709a375084095697124a97609 100755 (executable)
@@ -4,6 +4,12 @@ test_description='typechange rename detection'
 
 . ./test-lib.sh
 
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
 test_expect_success setup '
 
        rm -f foo bar &&
index 9ddbbcde57489e266e2f229314ebac5dbfeb3be6..3ccc237a8d4443bfc8763fbb9cb51033f846b0e8 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 #
 # Copyright (c) Jim Meyering
 #
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/t4035-diff-quiet.sh b/t/t4035-diff-quiet.sh
new file mode 100755 (executable)
index 0000000..e747e84
--- /dev/null
@@ -0,0 +1,80 @@
+#!/bin/sh
+
+test_description='Return value of diffs'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       echo 1 >a &&
+       git add . &&
+       git commit -m first &&
+       echo 2 >b &&
+       git add . &&
+       git commit -a -m second
+'
+
+test_expect_success 'git diff-tree HEAD^ HEAD' '
+       git diff-tree --quiet HEAD^ HEAD >cnt
+       test $? = 1 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- a' '
+       git diff-tree --quiet HEAD^ HEAD -- a >cnt
+       test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-tree HEAD^ HEAD -- b' '
+       git diff-tree --quiet HEAD^ HEAD -- b >cnt
+       test $? = 1 && test $(wc -l <cnt) = 0
+'
+# this diff outputs one line: sha1 of the given head
+test_expect_success 'echo HEAD | git diff-tree --stdin' '
+       echo $(git rev-parse HEAD) | git diff-tree --quiet --stdin >cnt
+       test $? = 1 && test $(wc -l <cnt) = 1
+'
+test_expect_success 'git diff-tree HEAD HEAD' '
+       git diff-tree --quiet HEAD HEAD >cnt
+       test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-files' '
+       git diff-files --quiet >cnt
+       test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD' '
+       git diff-index --quiet --cached HEAD >cnt
+       test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+       git diff-index --quiet --cached HEAD^ >cnt
+       test $? = 1 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-index --cached HEAD^' '
+       echo text >>b &&
+       echo 3 >c &&
+       git add . && {
+               git diff-index --quiet --cached HEAD^ >cnt
+               test $? = 1 && test $(wc -l <cnt) = 0
+       }
+'
+test_expect_success 'git diff-tree -Stext HEAD^ HEAD -- b' '
+       git commit -m "text in b" && {
+               git diff-tree --quiet -Stext HEAD^ HEAD -- b >cnt
+               test $? = 1 && test $(wc -l <cnt) = 0
+       }
+'
+test_expect_success 'git diff-tree -Snot-found HEAD^ HEAD -- b' '
+       git diff-tree --quiet -Snot-found HEAD^ HEAD -- b >cnt
+       test $? = 0 && test $(wc -l <cnt) = 0
+'
+test_expect_success 'git diff-files' '
+       echo 3 >>c && {
+               git diff-files --quiet >cnt
+               test $? = 1 && test $(wc -l <cnt) = 0
+       }
+'
+test_expect_success 'git diff-index --cached HEAD' '
+       git update-index c && {
+               git diff-index --quiet --cached HEAD >cnt
+               test $? = 1 && test $(wc -l <cnt) = 0
+       }
+'
+
+test_done
diff --git a/t/t4036-format-patch-signer-mime.sh b/t/t4036-format-patch-signer-mime.sh
new file mode 100755 (executable)
index 0000000..ba43f18
--- /dev/null
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+test_description='format-patch -s should force MIME encoding as needed'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       >F &&
+       git add F &&
+       git commit -m initial &&
+       echo new line >F &&
+
+       test_tick &&
+       git commit -m "This adds some lines to F" F
+
+'
+
+test_expect_success 'format normally' '
+
+       git format-patch --stdout -1 >output &&
+       ! grep Content-Type output
+
+'
+
+test_expect_success 'format with signoff without funny signer name' '
+
+       git format-patch -s --stdout -1 >output &&
+       ! grep Content-Type output
+
+'
+
+test_expect_success 'format with non ASCII signer name' '
+
+       GIT_COMMITTER_NAME="はまの ふにおう" \
+       git format-patch -s --stdout -1 >output &&
+       grep Content-Type output
+
+'
+
+test_expect_success 'attach and signoff do not duplicate mime headers' '
+
+       GIT_COMMITTER_NAME="はまの ふにおう" \
+       git format-patch -s --stdout -1 --attach >output &&
+       test `grep -ci ^MIME-Version: output` = 1
+
+'
+
+test_done
+
index d42abff1ad59343fa1c84bded9a82c3212370da0..1597965241c3566aa3a14e986fd5a630fb348479 100755 (executable)
@@ -31,14 +31,16 @@ test_expect_success setup \
 test_expect_success apply \
     'git apply --index --stat --summary --apply test-patch'
 
-if [ "$(git config --get core.filemode)" = false ]
+if test "$(git config --bool core.filemode)" = false
 then
        say 'filemode disabled on the filesystem'
 else
-       test_expect_success validate \
-           'test -f bar && ls -l bar | grep "^-..x......"'
+       test_set_prereq FILEMODE
 fi
 
+test_expect_success FILEMODE validate \
+           'test -f bar && ls -l bar | grep "^-..x......"'
+
 test_expect_success 'apply reverse' \
     'git apply -R --index --stat --summary --apply test-patch &&
      test "$(cat foo)" = "This is foo"'
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 0f185caa44f3a9d048a2c058d963a1e86e9984fd..99ec13dd531c71299681acc3eb678b490ff68707 100755 (executable)
@@ -9,6 +9,12 @@ test_description='git apply should not get confused with type changes.
 
 . ./test-lib.sh
 
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
 test_expect_success 'setup repository and commits' '
        echo "hello world" > foo &&
        echo "hi planet" > bar &&
index 9ace578f17a07aafc050ccaf935aef8a4a3cab4e..b852e5898009bca0205c231033f8f72f48962b81 100755 (executable)
@@ -9,6 +9,12 @@ test_description='git apply symlinks and partial files
 
 . ./test-lib.sh
 
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
 test_expect_success setup '
 
        ln -s path1/path2/path3/path4/path5 link1 &&
index f92e259cc6f251ec6f89edee3fc16720f264d82f..65f2e4c3efb9ae5b5459e15df337e07201d78c38 100755 (executable)
@@ -20,10 +20,10 @@ test_expect_success setup '
                cat file1 &&
                echo Q | tr -d "\\012"
        } >file2 &&
-       cat file2 >file2.orig
+       cat file2 >file2.orig &&
        git add file1 file2 &&
        sed -e "/^B/d" <file1.orig >file1 &&
-       sed -e "/^[BQ]/d" <file2.orig >file2 &&
+       cat file1 > file2 &&
        echo Q | tr -d "\\012" >>file2 &&
        cat file1 >file1.mods &&
        cat file2 >file2.mods &&
index 841773f75fc085d07836b39b3775f49bde5d8d19..0d3c1d5dd5c0f35f9cc44eab4fcba5ba2e36ddd7 100755 (executable)
@@ -3,6 +3,12 @@
 test_description='apply to deeper directory without getting fooled with symlink'
 . ./test-lib.sh
 
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
 lecho () {
        for l_
        do
index adfcbb5a3b9a6ec7e77f45141c2753cb79ed3763..fc7af0493102f12438d59ac5ed6e3e96eda8c841 100755 (executable)
@@ -4,6 +4,13 @@ test_description='applying patch with mode bits'
 
 . ./test-lib.sh
 
+if test "$(git config --bool core.filemode)" = false
+then
+       say 'filemode disabled on the filesystem'
+else
+       test_set_prereq FILEMODE
+fi
+
 test_expect_success setup '
        echo original >file &&
        git add file &&
@@ -16,14 +23,14 @@ test_expect_success setup '
        git diff --stat -p >patch-1.txt
 '
 
-test_expect_success 'same mode (no index)' '
+test_expect_success FILEMODE 'same mode (no index)' '
        git reset --hard &&
        chmod +x file &&
        git apply patch-0.txt &&
        test -x file
 '
 
-test_expect_success 'same mode (with index)' '
+test_expect_success FILEMODE 'same mode (with index)' '
        git reset --hard &&
        chmod +x file &&
        git add file &&
@@ -32,7 +39,7 @@ test_expect_success 'same mode (with index)' '
        git diff --exit-code
 '
 
-test_expect_success 'same mode (index only)' '
+test_expect_success FILEMODE 'same mode (index only)' '
        git reset --hard &&
        chmod +x file &&
        git add file &&
@@ -40,20 +47,20 @@ test_expect_success 'same mode (index only)' '
        git ls-files -s file | grep "^100755"
 '
 
-test_expect_success 'mode update (no index)' '
+test_expect_success FILEMODE 'mode update (no index)' '
        git reset --hard &&
        git apply patch-1.txt &&
        test -x file
 '
 
-test_expect_success 'mode update (with index)' '
+test_expect_success FILEMODE 'mode update (with index)' '
        git reset --hard &&
        git apply --index patch-1.txt &&
        test -x file &&
        git diff --exit-code
 '
 
-test_expect_success 'mode update (index only)' '
+test_expect_success FILEMODE 'mode update (index only)' '
        git reset --hard &&
        git apply --cached patch-1.txt &&
        git ls-files -s file | grep "^100755"
diff --git a/t/t4130-apply-criss-cross-rename.sh b/t/t4130-apply-criss-cross-rename.sh
new file mode 100755 (executable)
index 0000000..7cfa2d6
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+
+test_description='git apply handling criss-cross rename patch.'
+. ./test-lib.sh
+
+create_file() {
+       cnt=0
+       while test $cnt -le 100
+       do
+               cnt=$(($cnt + 1))
+               echo "$2" >> "$1"
+       done
+}
+
+test_expect_success 'setup' '
+       create_file file1 "File1 contents" &&
+       create_file file2 "File2 contents" &&
+       create_file file3 "File3 contents" &&
+       git add file1 file2 file3 &&
+       git commit -m 1
+'
+
+test_expect_success 'criss-cross rename' '
+       mv file1 tmp &&
+       mv file2 file1 &&
+       mv tmp file2 &&
+       cp file1 file1-swapped &&
+       cp file2 file2-swapped
+'
+
+test_expect_success 'diff -M -B' '
+       git diff -M -B > diff &&
+       git reset --hard
+
+'
+
+test_expect_success 'apply' '
+       git apply diff &&
+       test_cmp file1 file1-swapped &&
+       test_cmp file2 file2-swapped
+'
+
+test_expect_success 'criss-cross rename' '
+       git reset --hard &&
+       mv file1 tmp &&
+       mv file2 file1 &&
+       mv file3 file2
+       mv tmp file3 &&
+       cp file1 file1-swapped &&
+       cp file2 file2-swapped &&
+       cp file3 file3-swapped
+'
+
+test_expect_success 'diff -M -B' '
+       git diff -M -B > diff &&
+       git reset --hard
+'
+
+test_expect_success 'apply' '
+       git apply diff &&
+       test_cmp file1 file1-swapped &&
+       test_cmp file2 file2-swapped &&
+       test_cmp file3 file3-swapped
+'
+
+test_done
index 796f795267dee1eaf63b10fb3e5e14ce0431bebd..d6ebbaebe2c258a4694cfb709809e13d24084231 100755 (executable)
@@ -257,4 +257,52 @@ 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_expect_success 'am into an unborn branch' '
+       rm -fr subdir &&
+       mkdir -p subdir &&
+       git format-patch --numbered-files -o subdir -1 first &&
+       (
+               cd subdir &&
+               git init &&
+               git am 1
+       ) &&
+       result=$(
+               cd subdir && git rev-parse HEAD^{tree}
+       ) &&
+       test "z$result" = "z$(git rev-parse first^{tree})"
+'
+
 test_done
index b68ab11f2915789cd04ac6bd43363aeab2079198..a6bc028a57115729d38e4b228cd259880d0bf6f8 100755 (executable)
@@ -57,7 +57,7 @@ test_expect_success 'conflicting merge' '
        test_must_fail git merge first
 '
 
-sha1=$(sed -e 's/      .*//' .git/MERGE_RR)
+sha1=$(perl -pe 's/    .*//' .git/MERGE_RR)
 rr=.git/rr-cache/$sha1
 test_expect_success 'recorded preimage' "grep ^=======$ $rr/preimage"
 
@@ -190,8 +190,6 @@ test_expect_success 'file2 added differently in two branches' '
        git add file2 &&
        git commit -m version2 &&
        test_must_fail git merge fourth &&
-       sha1=$(sed -e "s/       .*//" .git/MERGE_RR) &&
-       rr=.git/rr-cache/$sha1 &&
        echo Cello > file2 &&
        git add file2 &&
        git commit -m resolution
index 0ab925c4e4710a560f0d35e47ccdda8ddb2b8212..64502e2be762a82b65a72af6847d5a3b79303d45 100755 (executable)
@@ -16,27 +16,71 @@ test_expect_success setup '
        test_tick &&
        git commit -m second &&
 
-       mkdir a &&
-       echo ni >a/two &&
-       git add a/two &&
+       git mv one ichi &&
        test_tick &&
        git commit -m third &&
 
-       echo san >a/three &&
-       git add a/three &&
+       cp ichi ein &&
+       git add ein &&
        test_tick &&
        git commit -m fourth &&
 
-       git rm a/three &&
+       mkdir a &&
+       echo ni >a/two &&
+       git add a/two &&
+       test_tick &&
+       git commit -m fifth  &&
+
+       git rm a/two &&
        test_tick &&
-       git commit -m fifth
+       git commit -m sixth
+
+'
+
+printf "sixth\nfifth\nfourth\nthird\nsecond\ninitial" > expect
+test_expect_success 'pretty' '
+
+       git log --pretty="format:%s" > actual &&
+       test_cmp expect actual
+'
+
+printf "sixth\nfifth\nfourth\nthird\nsecond\ninitial\n" > expect
+test_expect_success 'pretty (tformat)' '
+
+       git log --pretty="tformat:%s" > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'pretty (shortcut)' '
 
+       git log --pretty="%s" > actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'format' '
+
+       git log --format="%s" > actual &&
+       test_cmp expect actual
+'
+
+cat > expect << EOF
+804a787 sixth
+394ef78 fifth
+5d31159 fourth
+2fbe8c0 third
+f7dab8e second
+3a2fdcb initial
+EOF
+test_expect_success 'oneline' '
+
+       git log --oneline > actual &&
+       test_cmp expect actual
 '
 
 test_expect_success 'diff-filter=A' '
 
        actual=$(git log --pretty="format:%s" --diff-filter=A HEAD) &&
-       expect=$(echo fourth ; echo third ; echo initial) &&
+       expect=$(echo fifth ; echo fourth ; echo third ; echo initial) &&
        test "$actual" = "$expect" || {
                echo Oops
                echo "Actual: $actual"
@@ -60,7 +104,43 @@ test_expect_success 'diff-filter=M' '
 test_expect_success 'diff-filter=D' '
 
        actual=$(git log --pretty="format:%s" --diff-filter=D HEAD) &&
-       expect=$(echo fifth) &&
+       expect=$(echo sixth ; echo third) &&
+       test "$actual" = "$expect" || {
+               echo Oops
+               echo "Actual: $actual"
+               false
+       }
+
+'
+
+test_expect_success 'diff-filter=R' '
+
+       actual=$(git log -M --pretty="format:%s" --diff-filter=R HEAD) &&
+       expect=$(echo third) &&
+       test "$actual" = "$expect" || {
+               echo Oops
+               echo "Actual: $actual"
+               false
+       }
+
+'
+
+test_expect_success 'diff-filter=C' '
+
+       actual=$(git log -C -C --pretty="format:%s" --diff-filter=C HEAD) &&
+       expect=$(echo fourth) &&
+       test "$actual" = "$expect" || {
+               echo Oops
+               echo "Actual: $actual"
+               false
+       }
+
+'
+
+test_expect_success 'git log --follow' '
+
+       actual=$(git log --follow --pretty="format:%s" ichi) &&
+       expect=$(echo third ; echo second ; echo initial) &&
        test "$actual" = "$expect" || {
                echo Oops
                echo "Actual: $actual"
@@ -72,6 +152,7 @@ test_expect_success 'diff-filter=D' '
 test_expect_success 'setup case sensitivity tests' '
        echo case >one &&
        test_tick &&
+       git add one
        git commit -a -m Second
 '
 
@@ -93,5 +174,179 @@ test_expect_success 'log --grep -i' '
        test_cmp expect actual
 '
 
+cat > expect <<EOF
+* Second
+* sixth
+* fifth
+* fourth
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'simple log --graph' '
+       git log --graph --pretty=tformat:%s >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'set up merge history' '
+       git checkout -b side HEAD~4 &&
+       test_commit side-1 1 1 &&
+       test_commit side-2 2 2 &&
+       git checkout master &&
+       git merge side
+'
+
+cat > expect <<\EOF
+*   Merge branch 'side'
+|\
+| * side-2
+| * side-1
+* | Second
+* | sixth
+* | fifth
+* | fourth
+|/
+* third
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge' '
+       git log --graph --date-order --pretty=tformat:%s |
+               sed "s/ *$//" >actual &&
+       test_cmp expect actual
+'
+
+cat > expect <<\EOF
+*   commit master
+|\  Merge: A B
+| | Author: A U Thor <author@example.com>
+| |
+| |     Merge branch 'side'
+| |
+| * commit side
+| | Author: A U Thor <author@example.com>
+| |
+| |     side-2
+| |
+| * commit tags/side-1
+| | Author: A U Thor <author@example.com>
+| |
+| |     side-1
+| |
+* | commit master~1
+| | Author: A U Thor <author@example.com>
+| |
+| |     Second
+| |
+* | commit master~2
+| | Author: A U Thor <author@example.com>
+| |
+| |     sixth
+| |
+* | commit master~3
+| | Author: A U Thor <author@example.com>
+| |
+| |     fifth
+| |
+* | commit master~4
+|/  Author: A U Thor <author@example.com>
+|
+|       fourth
+|
+* commit tags/side-1~1
+| Author: A U Thor <author@example.com>
+|
+|     third
+|
+* commit tags/side-1~2
+| Author: A U Thor <author@example.com>
+|
+|     second
+|
+* commit tags/side-1~3
+  Author: A U Thor <author@example.com>
+
+      initial
+EOF
+
+test_expect_success 'log --graph with full output' '
+       git log --graph --date-order --pretty=short |
+               git name-rev --name-only --stdin |
+               sed "s/Merge:.*/Merge: A B/;s/ *$//" >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'set up more tangled history' '
+       git checkout -b tangle HEAD~6 &&
+       test_commit tangle-a tangle-a a &&
+       git merge master~3 &&
+       git merge side~1 &&
+       git checkout master &&
+       git merge tangle &&
+       git checkout -b reach &&
+       test_commit reach &&
+       git checkout master &&
+       git checkout -b octopus-a &&
+       test_commit octopus-a &&
+       git checkout master &&
+       git checkout -b octopus-b &&
+       test_commit octopus-b &&
+       git checkout master &&
+       test_commit seventh &&
+       git merge octopus-a octopus-b
+       git merge reach
+'
+
+cat > expect <<\EOF
+*   Merge branch 'reach'
+|\
+| \
+|  \
+*-. \   Merge branches 'octopus-a' and 'octopus-b'
+|\ \ \
+* | | | seventh
+| | * | octopus-b
+| |/ /
+|/| |
+| * | octopus-a
+|/ /
+| * reach
+|/
+*   Merge branch 'tangle'
+|\
+| *   Merge branch 'side' (early part) into tangle
+| |\
+| * \   Merge branch 'master' (early part) into tangle
+| |\ \
+| * | | tangle-a
+* | | |   Merge branch 'side'
+|\ \ \ \
+| * | | | side-2
+| | | |/
+| | |/|
+| |/| |
+| * | | side-1
+* | | | Second
+* | | | sixth
+| | |/
+| |/|
+|/| |
+* | | fifth
+* | | fourth
+|/ /
+* | third
+|/
+* second
+* initial
+EOF
+
+test_expect_success 'log --graph with merge' '
+       git log --graph --date-order --pretty=tformat:%s |
+               sed "s/ *$//" >actual &&
+       test_cmp expect actual
+'
+
 test_done
 
diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh
new file mode 100755 (executable)
index 0000000..9a7d1b4
--- /dev/null
@@ -0,0 +1,215 @@
+#!/bin/sh
+
+test_description='.mailmap configurations'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo one >one &&
+       git add one &&
+       test_tick &&
+       git commit -m initial &&
+       echo two >>one &&
+       git add one &&
+       git commit --author "nick1 <bugs@company.xx>" -m second
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'No mailmap' '
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'default .mailmap' '
+       echo "Repo Guy <author@example.com>" > .mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+# Using a mailmap file in a subdirectory of the repo here, but
+# could just as well have been a file outside of the repository
+cat >expect <<\EOF
+Internal Guy (1):
+      second
+
+Repo Guy (1):
+      initial
+
+EOF
+test_expect_success 'mailmap.file set' '
+       mkdir internal_mailmap &&
+       echo "Internal Guy <bugs@company.xx>" > internal_mailmap/.mailmap &&
+       git config mailmap.file internal_mailmap/.mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+External Guy (1):
+      initial
+
+Internal Guy (1):
+      second
+
+EOF
+test_expect_success 'mailmap.file override' '
+       echo "External Guy <author@example.com>" >> internal_mailmap/.mailmap &&
+       git config mailmap.file internal_mailmap/.mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+Repo Guy (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+
+test_expect_success 'mailmap.file non-existant' '
+       rm internal_mailmap/.mailmap &&
+       rmdir internal_mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+cat >expect <<\EOF
+A U Thor (1):
+      initial
+
+nick1 (1):
+      second
+
+EOF
+test_expect_success 'No mailmap files, but configured' '
+       rm .mailmap &&
+       git shortlog HEAD >actual &&
+       test_cmp expect actual
+'
+
+# Extended mailmap configurations should give us the following output for shortlog
+cat >expect <<\EOF
+A U Thor <author@example.com> (1):
+      initial
+
+CTO <cto@company.xx> (1):
+      seventh
+
+Other Author <other@author.xx> (2):
+      third
+      fourth
+
+Santa Claus <santa.claus@northpole.xx> (2):
+      fifth
+      sixth
+
+Some Dude <some@dude.xx> (1):
+      second
+
+EOF
+
+test_expect_success 'Shortlog output (complex mapping)' '
+       echo three >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "nick2 <bugs@company.xx>" -m third &&
+
+       echo four >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "nick2 <nick2@company.xx>" -m fourth &&
+
+       echo five >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "santa <me@company.xx>" -m fifth &&
+
+       echo six >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "claus <me@company.xx>" -m sixth &&
+
+       echo seven >>one &&
+       git add one &&
+       test_tick &&
+       git commit --author "CTO <cto@coompany.xx>" -m seventh &&
+
+       mkdir internal_mailmap &&
+       echo "Committed <committer@example.com>" > internal_mailmap/.mailmap &&
+       echo "<cto@company.xx>                       <cto@coompany.xx>" >> internal_mailmap/.mailmap &&
+       echo "Some Dude <some@dude.xx>         nick1 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Other Author <other@author.xx>   nick2 <bugs@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Other Author <other@author.xx>         <nick2@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+       echo "Santa Claus <santa.claus@northpole.xx> <me@company.xx>" >> internal_mailmap/.mailmap &&
+
+       git shortlog -e HEAD >actual &&
+       test_cmp expect actual
+
+'
+
+# git log with --pretty format which uses the name and email mailmap placemarkers
+cat >expect <<\EOF
+Author CTO <cto@coompany.xx> maps to CTO <cto@company.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author claus <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author santa <me@company.xx> maps to Santa Claus <santa.claus@northpole.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <nick2@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick2 <bugs@company.xx> maps to Other Author <other@author.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author nick1 <bugs@company.xx> maps to Some Dude <some@dude.xx>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+
+Author A U Thor <author@example.com> maps to A U Thor <author@example.com>
+Committer C O Mitter <committer@example.com> maps to Committed <committer@example.com>
+EOF
+
+test_expect_success 'Log output (complex mapping)' '
+       git log --pretty=format:"Author %an <%ae> maps to %aN <%aE>%nCommitter %cn <%ce> maps to %cN <%cE>%n" >actual &&
+       test_cmp expect actual
+'
+
+# git blame
+cat >expect <<\EOF
+^3a2fdcb (A U Thor     2005-04-07 15:13:13 -0700 1) one
+7de6f99b (Some Dude    2005-04-07 15:13:13 -0700 2) two
+5815879d (Other Author 2005-04-07 15:14:13 -0700 3) three
+ff859d96 (Other Author 2005-04-07 15:15:13 -0700 4) four
+5ab6d4fa (Santa Claus  2005-04-07 15:16:13 -0700 5) five
+38a42d8b (Santa Claus  2005-04-07 15:17:13 -0700 6) six
+8ddc0386 (CTO          2005-04-07 15:18:13 -0700 7) seven
+EOF
+
+test_expect_success 'Blame output (complex mapping)' '
+       git blame one >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh
new file mode 100755 (executable)
index 0000000..04f7bae
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+test_description='git patch-id'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit initial foo a &&
+       test_commit first foo b &&
+       git checkout -b same HEAD^ &&
+       test_commit same-msg foo b &&
+       git checkout -b notsame HEAD^ &&
+       test_commit notsame-msg foo c
+'
+
+test_expect_success 'patch-id output is well-formed' '
+       git log -p -1 | git patch-id > output &&
+       grep "^[a-f0-9]\{40\} $(git rev-parse HEAD)$" output
+'
+
+get_patch_id () {
+       git log -p -1 "$1" | git patch-id |
+               sed "s# .*##" > patch-id_"$1"
+}
+
+test_expect_success 'patch-id detects equality' '
+       get_patch_id master &&
+       get_patch_id same &&
+       test_cmp patch-id_master patch-id_same
+'
+
+test_expect_success 'patch-id detects inequality' '
+       get_patch_id master &&
+       get_patch_id notsame &&
+       ! test_cmp patch-id_master patch-id_notsame
+'
+
+test_done
index 3ab9e8e6e3635ce54b19cec7987ab976fd994309..f603c1b1336c4a00889177376d9b51077c9cc2ac 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git am not losing options'
+test_description='git am with options and not losing them'
 . ./test-lib.sh
 
 tm="$TEST_DIRECTORY/t4252"
@@ -50,4 +50,29 @@ test_expect_success 'interrupted am -C1 -p2' '
        grep "^Three$" file-2
 '
 
+test_expect_success 'interrupted am --directory="frotz nitfol"' '
+       rm -rf .git/rebase-apply &&
+       git reset --hard initial &&
+       test_must_fail git am --directory="frotz nitfol" "$tm"/am-test-5-? &&
+       git am --skip &&
+       grep One "frotz nitfol/file-5"
+'
+
+test_expect_success 'apply to a funny path' '
+       with_sq="with'\''sq"
+       rm -fr .git/rebase-apply &&
+       git reset --hard initial &&
+       git am --directory="$with_sq" "$tm"/am-test-5-2 &&
+       test -f "$with_sq/file-5"
+'
+
+test_expect_success 'am --reject' '
+       rm -rf .git/rebase-apply &&
+       git reset --hard initial &&
+       test_must_fail git am --reject "$tm"/am-test-6-1 &&
+       grep "@@ -1,3 +1,3 @@" file-2.rej &&
+       test_must_fail git diff-files --exit-code --quiet file-2 &&
+       grep "[-]-reject" .git/rebase-apply/apply-opt
+'
+
 test_done
diff --git a/t/t4252/am-test-5-1 b/t/t4252/am-test-5-1
new file mode 100644 (file)
index 0000000..da7bf29
--- /dev/null
@@ -0,0 +1,20 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Six
+
+Applying this patch with --directory='frotz nitfol' should fail
+
+diff --git i/junk/file-2 w/junk/file-2
+index 06e567b..b6f3a16 100644
+--- i/junk/file-2
++++ w/junk/file-2
+@@ -1,7 +1,7 @@
+ One
+ 2
+-3
++Three
+ 4
+ 5
+-6
++Six
+ 7
diff --git a/t/t4252/am-test-5-2 b/t/t4252/am-test-5-2
new file mode 100644 (file)
index 0000000..373025b
--- /dev/null
@@ -0,0 +1,15 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Six
+
+Applying this patch with --directory='frotz nitfol' should succeed
+
+diff --git i/file-5 w/file-5
+new file mode 100644
+index 000000..1d6ed9f
+--- /dev/null
++++ w/file-5
+@@ -0,0 +1,3 @@
++One
++two
++three
diff --git a/t/t4252/am-test-6-1 b/t/t4252/am-test-6-1
new file mode 100644 (file)
index 0000000..a8859e9
--- /dev/null
@@ -0,0 +1,21 @@
+From: A U Thor <au.thor@example.com>
+Date: Thu Dec 4 16:00:00 2008 -0800
+Subject: Huh
+
+Should fail and leave rejects
+
+diff --git i/file-2 w/file-2
+index 06e567b..b6f3a16 100644
+--- i/file-2
++++ w/file-2
+@@ -1,3 +1,3 @@
+-0
++One
+ 2
+ 3
+@@ -4,4 +4,4 @@
+ 4
+ 5
+-6
++Six
+ 7
index c942c8be85339157e22f755d8fc94e64efaee4dd..abb41b07ef1985d53c2186617e6b2fcf7e7fe033 100755 (executable)
@@ -37,7 +37,11 @@ test_expect_success \
      cp /bin/sh a/bin &&
      printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile1 &&
      printf "A not substituted O" >a/substfile2 &&
-     ln -s a a/l1 &&
+     if test_have_prereq SYMLINKS; then
+       ln -s a a/l1
+     else
+       printf %s a > a/l1
+     fi &&
      (p=long_path_to_a_file && cd a &&
       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&
       echo text >file_with_long_path) &&
@@ -46,7 +50,7 @@ test_expect_success \
 test_expect_success \
     'add ignored file' \
     'echo ignore me >a/ignored &&
-     echo ignored export-ignore >.gitattributes'
+     echo ignored export-ignore >.git/info/attributes'
 
 test_expect_success \
     'add files to repository' \
@@ -60,7 +64,7 @@ test_expect_success \
 test_expect_success \
     'create bare clone' \
     'git clone --bare . bare.git &&
-     cp .gitattributes bare.git/info/attributes'
+     cp .git/info/attributes bare.git/info/attributes'
 
 test_expect_success \
     'remove ignored file' \
@@ -76,7 +80,7 @@ test_expect_success \
 
 test_expect_success \
     'git archive vs. git tar-tree' \
-    'diff b.tar b2.tar'
+    'test_cmp b.tar b2.tar'
 
 test_expect_success \
     'git archive in a bare repo' \
@@ -86,18 +90,22 @@ test_expect_success \
     'git archive vs. the same in a bare repo' \
     'test_cmp b.tar b3.tar'
 
+test_expect_success 'git archive with --output' \
+    'git archive --output=b4.tar HEAD &&
+    test_cmp b.tar b4.tar'
+
 test_expect_success \
     'validate file modification time' \
     'mkdir extract &&
      "$TAR" xf b.tar -C extract a/a &&
      test-chmtime -v +0 extract/a/a |cut -f 1 >b.mtime &&
      echo "1117231200" >expected.mtime &&
-     diff expected.mtime b.mtime'
+     test_cmp expected.mtime b.mtime'
 
 test_expect_success \
     'git get-tar-commit-id' \
     'git get-tar-commit-id <b.tar >b.commitid &&
-     diff .git/$(git symbolic-ref HEAD) b.commitid'
+     test_cmp .git/$(git symbolic-ref HEAD) b.commitid'
 
 test_expect_success \
     'extract tar archive' \
@@ -106,7 +114,7 @@ test_expect_success \
 test_expect_success \
     'validate filenames' \
     '(cd b/a && find .) | sort >b.lst &&
-     diff a.lst b.lst'
+     test_cmp a.lst b.lst'
 
 test_expect_success \
     'validate file contents' \
@@ -123,7 +131,7 @@ test_expect_success \
 test_expect_success \
     'validate filenames with prefix' \
     '(cd c/prefix/a && find .) | sort >c.lst &&
-     diff a.lst c.lst'
+     test_cmp a.lst c.lst'
 
 test_expect_success \
     'validate file contents with prefix' \
@@ -131,10 +139,11 @@ test_expect_success \
 
 test_expect_success \
     'create archives with substfiles' \
-    'echo "substfile?" export-subst >a/.gitattributes &&
+    'cp .git/info/attributes .git/info/attributes.before &&
+     echo "substfile?" export-subst >>.git/info/attributes &&
      git archive HEAD >f.tar &&
      git archive --prefix=prefix/ HEAD >g.tar &&
-     rm a/.gitattributes'
+     mv .git/info/attributes.before .git/info/attributes'
 
 test_expect_success \
     'extract substfiles' \
@@ -144,8 +153,8 @@ test_expect_success \
      'validate substfile contents' \
      'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
       >f/a/substfile1.expected &&
-      diff f/a/substfile1.expected f/a/substfile1 &&
-      diff a/substfile2 f/a/substfile2
+      test_cmp f/a/substfile1.expected f/a/substfile1 &&
+      test_cmp a/substfile2 f/a/substfile2
 '
 
 test_expect_success \
@@ -156,8 +165,8 @@ test_expect_success \
      'validate substfile contents from archive with prefix' \
      'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
       >g/prefix/a/substfile1.expected &&
-      diff g/prefix/a/substfile1.expected g/prefix/a/substfile1 &&
-      diff a/substfile2 g/prefix/a/substfile2
+      test_cmp g/prefix/a/substfile1.expected g/prefix/a/substfile1 &&
+      test_cmp a/substfile2 g/prefix/a/substfile2
 '
 
 test_expect_success \
@@ -172,23 +181,27 @@ test_expect_success \
     'git archive --format=zip vs. the same in a bare repo' \
     'test_cmp d.zip d1.zip'
 
+test_expect_success 'git archive --format=zip with --output' \
+    'git archive --format=zip --output=d2.zip HEAD &&
+    test_cmp d.zip d2.zip'
+
 $UNZIP -v >/dev/null 2>&1
 if [ $? -eq 127 ]; then
-       echo "Skipping ZIP tests, because unzip was not found"
-       test_done
-       exit
+       say "Skipping ZIP tests, because unzip was not found"
+else
+       test_set_prereq UNZIP
 fi
 
-test_expect_success \
+test_expect_success UNZIP \
     'extract ZIP archive' \
     '(mkdir d && cd d && $UNZIP ../d.zip)'
 
-test_expect_success \
+test_expect_success UNZIP \
     'validate filenames' \
     '(cd d/a && find .) | sort >d.lst &&
-     diff a.lst d.lst'
+     test_cmp a.lst d.lst'
 
-test_expect_success \
+test_expect_success UNZIP \
     'validate file contents' \
     'diff -r a d/a'
 
@@ -196,16 +209,16 @@ test_expect_success \
     'git archive --format=zip with prefix' \
     'git archive --format=zip --prefix=prefix/ HEAD >e.zip'
 
-test_expect_success \
+test_expect_success UNZIP \
     'extract ZIP archive with prefix' \
     '(mkdir e && cd e && $UNZIP ../e.zip)'
 
-test_expect_success \
+test_expect_success UNZIP \
     'validate filenames with prefix' \
     '(cd e/prefix/a && find .) | sort >e.lst &&
-     diff a.lst e.lst'
+     test_cmp a.lst e.lst'
 
-test_expect_success \
+test_expect_success UNZIP \
     'validate file contents with prefix' \
     'diff -r a e/prefix/a'
 
diff --git a/t/t5001-archive-attr.sh b/t/t5001-archive-attr.sh
new file mode 100755 (executable)
index 0000000..426b319
--- /dev/null
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+test_description='git archive attribute tests'
+
+. ./test-lib.sh
+
+SUBSTFORMAT=%H%n
+
+test_expect_exists() {
+       test_expect_success " $1 exists" "test -e $1"
+}
+
+test_expect_missing() {
+       test_expect_success " $1 does not exist" "test ! -e $1"
+}
+
+test_expect_success 'setup' '
+       echo ignored >ignored &&
+       echo ignored export-ignore >>.git/info/attributes &&
+       git add ignored &&
+
+       echo ignored by tree >ignored-by-tree &&
+       echo ignored-by-tree export-ignore >.gitattributes &&
+       git add ignored-by-tree .gitattributes &&
+
+       echo ignored by worktree >ignored-by-worktree &&
+       echo ignored-by-worktree export-ignore >.gitattributes &&
+       git add ignored-by-worktree &&
+
+       printf "A\$Format:%s\$O" "$SUBSTFORMAT" >nosubstfile &&
+       printf "A\$Format:%s\$O" "$SUBSTFORMAT" >substfile1 &&
+       printf "A not substituted O" >substfile2 &&
+       echo "substfile?" export-subst >>.git/info/attributes &&
+       git add nosubstfile substfile1 substfile2 &&
+
+       git commit -m. &&
+
+       git clone --bare . bare &&
+       cp .git/info/attributes bare/info/attributes
+'
+
+test_expect_success 'git archive' '
+       git archive HEAD >archive.tar &&
+       (mkdir archive && cd archive && "$TAR" xf -) <archive.tar
+'
+
+test_expect_missing    archive/ignored
+test_expect_missing    archive/ignored-by-tree
+test_expect_exists     archive/ignored-by-worktree
+
+test_expect_success 'git archive with worktree attributes' '
+       git archive --worktree-attributes HEAD >worktree.tar &&
+       (mkdir worktree && cd worktree && "$TAR" xf -) <worktree.tar
+'
+
+test_expect_missing    worktree/ignored
+test_expect_exists     worktree/ignored-by-tree
+test_expect_missing    worktree/ignored-by-worktree
+
+test_expect_success 'git archive vs. bare' '
+       (cd bare && git archive HEAD) >bare-archive.tar &&
+       test_cmp archive.tar bare-archive.tar
+'
+
+test_expect_success 'git archive with worktree attributes, bare' '
+       (cd bare && git archive --worktree-attributes HEAD) >bare-worktree.tar &&
+       (mkdir bare-worktree && cd bare-worktree && "$TAR" xf -) <bare-worktree.tar
+'
+
+test_expect_missing    bare-worktree/ignored
+test_expect_exists     bare-worktree/ignored-by-tree
+test_expect_exists     bare-worktree/ignored-by-worktree
+
+test_expect_success 'export-subst' '
+       git log "--pretty=format:A${SUBSTFORMAT}O" HEAD >substfile1.expected &&
+       test_cmp nosubstfile archive/nosubstfile &&
+       test_cmp substfile1.expected archive/substfile1 &&
+       test_cmp substfile2 archive/substfile2
+'
+
+test_expect_success 'git tar-tree vs. git archive with worktree attributes' '
+       git tar-tree HEAD >tar-tree.tar &&
+       test_cmp worktree.tar tar-tree.tar
+'
+
+test_expect_success 'git tar-tree vs. git archive with worktree attrs, bare' '
+       (cd bare && git tar-tree HEAD) >bare-tar-tree.tar &&
+       test_cmp bare-worktree.tar bare-tar-tree.tar
+'
+
+test_done
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
index 0ca7ff0529be38f54a474fefc842fcee73bf75b1..f67a90a9749fae52b9061777c63ce6f02201eb84 100644 (file)
@@ -1,4 +1,4 @@
-Author: Nathaniel Borenstein   (םולש ןב ילטפנ)
+Author: Nathaniel Borenstein (םולש ןב ילטפנ)
 Email: nsb@thumper.bellcore.com
 Subject: Test of new header generator
 
index 85df55f2c43c9462f7bf36f0b3acf186b84d64b0..c5ad206b40e1fcf79019cebdfd848d72c17cefcc 100644 (file)
@@ -2,10 +2,10 @@
        
     
 From nobody Mon Sep 17 00:00:00 2001
-From: A
+From: A (zzz)
       U
       Thor
-      <a.u.thor@example.com>
+      <a.u.thor@example.com> (Comment)
 Date: Fri, 9 Jun 2006 00:44:16 -0700
 Subject: [PATCH] a commit.
 
index ccfc64c6eef7e0aba7bd8a8496427470e9020309..e2aa254eae2ea930f29c4735f3f3d11bc790c455 100755 (executable)
@@ -13,11 +13,10 @@ TRASH=`pwd`
 test_expect_success \
     'setup' \
     'rm -f .git/index*
-     for i in a b c
-     do
-            dd if=/dev/zero bs=4k count=1 | perl -pe "y/\\000/$i/" >$i &&
-            git update-index --add $i || return 1
-     done &&
+     perl -e "print \"a\" x 4096;" > a &&
+     perl -e "print \"b\" x 4096;" > b &&
+     perl -e "print \"c\" x 4096;" > c &&
+     git update-index --add a b c &&
      cat c >d && echo foo >>d && git update-index --add d &&
      tree=`git write-tree` &&
      commit=`git commit-tree $tree </dev/null` && {
@@ -221,7 +220,7 @@ test_expect_success \
 test_expect_success \
     'verify-pack catches a corrupted pack signature' \
     'cat test-1-${packname_1}.pack >test-3.pack &&
-     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
+     echo | dd of=test-3.pack count=1 bs=1 conv=notrunc seek=2 &&
      if git verify-pack test-3.idx
      then false
      else :;
@@ -230,7 +229,7 @@ test_expect_success \
 test_expect_success \
     'verify-pack catches a corrupted pack version' \
     'cat test-1-${packname_1}.pack >test-3.pack &&
-     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
+     echo | dd of=test-3.pack count=1 bs=1 conv=notrunc seek=7 &&
      if git verify-pack test-3.idx
      then false
      else :;
@@ -239,7 +238,7 @@ test_expect_success \
 test_expect_success \
     'verify-pack catches a corrupted type/size of the 1st packed object data' \
     'cat test-1-${packname_1}.pack >test-3.pack &&
-     dd if=/dev/zero of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
+     echo | dd of=test-3.pack count=1 bs=1 conv=notrunc seek=12 &&
      if git verify-pack test-3.idx
      then false
      else :;
@@ -250,7 +249,7 @@ test_expect_success \
     'l=`wc -c <test-3.idx` &&
      l=`expr $l - 20` &&
      cat test-1-${packname_1}.pack >test-3.pack &&
-     dd if=/dev/zero of=test-3.idx count=20 bs=1 conv=notrunc seek=$l &&
+     printf "%20s" "" | dd of=test-3.idx count=20 bs=1 conv=notrunc seek=$l &&
      if git verify-pack test-3.pack
      then false
      else :;
index 884e24253a0a9d262b39ae96ea5c03ecb7ba4072..4360e77d317bfdaf7b40795859b4a023a5b89c13 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
@@ -68,32 +69,27 @@ test_expect_success \
     'index v2: force some 64-bit offsets with pack-objects' \
     'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list)'
 
-have_64bits=
 if msg=$(git verify-pack -v "test-3-${pack3}.pack" 2>&1) ||
        ! (echo "$msg" | grep "pack too large .* off_t")
 then
-       have_64bits=t
+       test_set_prereq OFF64_T
 else
        say "skipping tests concerning 64-bit offsets"
 fi
 
-test "$have_64bits" &&
-test_expect_success \
+test_expect_success OFF64_T \
     'index v2: verify a pack with some 64-bit offsets' \
     'git verify-pack -v "test-3-${pack3}.pack"'
 
-test "$have_64bits" &&
-test_expect_success \
+test_expect_success OFF64_T \
     '64-bit offsets: should be different from previous index v2 results' \
     '! cmp "test-2-${pack2}.idx" "test-3-${pack3}.idx"'
 
-test "$have_64bits" &&
-test_expect_success \
+test_expect_success OFF64_T \
     'index v2: force some 64-bit offsets with index-pack' \
     'git index-pack --index-version=2,0x40000 -o 3.idx "test-1-${pack1}.pack"'
 
-test "$have_64bits" &&
-test_expect_success \
+test_expect_success OFF64_T \
     '64-bit offsets: index-pack result should match pack-objects one' \
     'cmp "test-3-${pack3}.idx" "3.idx"'
 
@@ -207,7 +203,7 @@ test_expect_success \
      obj=`git hash-object file_001` &&
      nr=`index_obj_nr ".git/objects/pack/pack-${pack1}.idx" $obj` &&
      chmod +w ".git/objects/pack/pack-${pack1}.idx" &&
-     dd if=/dev/zero of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \
+     printf xxxx | dd of=".git/objects/pack/pack-${pack1}.idx" conv=notrunc \
         bs=1 count=4 seek=$((8 + 256 * 4 + `wc -l <obj-list` * 20 + $nr * 4)) &&
      ( while read obj
        do git cat-file -p $obj >/dev/null || exit 1
index d4e30fc43c68ed9b0fc175f2a88a07de6458f58e..5132d4130968691bb115f5fc08cd0d2fd64ce669 100755 (executable)
@@ -55,6 +55,8 @@ do_corrupt_object() {
     test_must_fail git verify-pack ${pack}.pack
 }
 
+printf '\0' > zero
+
 test_expect_success \
     'initial setup validation' \
     'create_test_files &&
@@ -66,7 +68,7 @@ test_expect_success \
 
 test_expect_success \
     'create corruption in header of first object' \
-    'do_corrupt_object $blob_1 0 < /dev/zero &&
+    'do_corrupt_object $blob_1 0 < zero &&
      test_must_fail git cat-file blob $blob_1 > /dev/null &&
      test_must_fail git cat-file blob $blob_2 > /dev/null &&
      test_must_fail git cat-file blob $blob_3 > /dev/null'
@@ -125,7 +127,7 @@ test_expect_success \
     'create corruption in header of first delta' \
     'create_new_pack &&
      git prune-packed &&
-     do_corrupt_object $blob_2 0 < /dev/zero &&
+     do_corrupt_object $blob_2 0 < zero &&
      git cat-file blob $blob_1 > /dev/null &&
      test_must_fail git cat-file blob $blob_2 > /dev/null &&
      test_must_fail git cat-file blob $blob_3 > /dev/null'
@@ -180,7 +182,7 @@ test_expect_success \
     'corruption in delta base reference of first delta (OBJ_REF_DELTA)' \
     'create_new_pack &&
      git prune-packed &&
-     do_corrupt_object $blob_2 2 < /dev/zero &&
+     do_corrupt_object $blob_2 2 < zero &&
      git cat-file blob $blob_1 > /dev/null &&
      test_must_fail git cat-file blob $blob_2 > /dev/null &&
      test_must_fail git cat-file blob $blob_3 > /dev/null'
@@ -207,7 +209,7 @@ test_expect_success \
     'corruption #0 in delta base reference of first delta (OBJ_OFS_DELTA)' \
     'create_new_pack --delta-base-offset &&
      git prune-packed &&
-     do_corrupt_object $blob_2 2 < /dev/zero &&
+     do_corrupt_object $blob_2 2 < zero &&
      git cat-file blob $blob_1 > /dev/null &&
      test_must_fail git cat-file blob $blob_2 > /dev/null &&
      test_must_fail git cat-file blob $blob_3 > /dev/null'
@@ -259,7 +261,7 @@ test_expect_success \
 
 test_expect_success \
     '... and a redundant pack allows for full recovery too' \
-    'do_corrupt_object $blob_2 2 < /dev/zero &&
+    'do_corrupt_object $blob_2 2 < zero &&
      git cat-file blob $blob_1 > /dev/null &&
      test_must_fail git cat-file blob $blob_2 > /dev/null &&
      test_must_fail git cat-file blob $blob_3 > /dev/null &&
index 771c0a06a4bd01cf40628ffae379b5d992802ef6..55ed7c7935c007573761842cea5978a784c865b3 100755 (executable)
@@ -112,4 +112,42 @@ test_expect_success 'prune: do not prune heads listed as an argument' '
 
 '
 
+test_expect_success 'gc --no-prune' '
+
+       before=$(git count-objects | sed "s/ .*//") &&
+       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test $((1 + $before)) = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE &&
+       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       git config gc.pruneExpire 2.days.ago &&
+       git gc --no-prune &&
+       test 1 = $(git count-objects | sed "s/ .*//") &&
+       test -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc respects gc.pruneExpire' '
+
+       git config gc.pruneExpire 5002.days.ago &&
+       git gc &&
+       test -f $BLOB_FILE &&
+       git config gc.pruneExpire 5000.days.ago &&
+       git gc &&
+       test ! -f $BLOB_FILE
+
+'
+
+test_expect_success 'gc --prune=<date>' '
+
+       BLOB=$(echo aleph_0 | git hash-object -w --stdin) &&
+       BLOB_FILE=.git/objects/$(echo $BLOB | sed "s/^../&\//") &&
+       test-chmtime =-$((86400*5001)) $BLOB_FILE &&
+       git gc --prune=5002.days.ago &&
+       test -f $BLOB_FILE &&
+       git gc --prune=5000.days.ago &&
+       test ! -f $BLOB_FILE
+
+'
+
 test_done
index b21317d68527988d0c2939e7173098b08bfbb64f..f2d5581b12f7d70c9f346da75dced81e12bd4c7f 100755 (executable)
@@ -32,9 +32,7 @@ test_expect_success setup '
        done &&
        git update-ref HEAD "$commit" &&
        git clone ./. victim &&
-       cd victim &&
-       git log &&
-       cd .. &&
+       ( cd victim && git log ) &&
        git update-ref HEAD "$zero" &&
        parent=$zero &&
        i=0 &&
@@ -59,88 +57,84 @@ test_expect_success 'pack the source repository' '
 '
 
 test_expect_success 'pack the destination repository' '
+    (
        cd victim &&
        git repack -a -d &&
-       git prune &&
-       cd ..
+       git prune
+    )
 '
 
-test_expect_success \
-        'pushing rewound head should not barf but require --force' '
-       # should not fail but refuse to update.
-       if git send-pack ./victim/.git/ master
-       then
-               # now it should fail with Pasky patch
-               echo >&2 Gaah, it should have failed.
-               false
-       else
-               echo >&2 Thanks, it correctly failed.
-               true
-       fi &&
-       if cmp victim/.git/refs/heads/master .git/refs/heads/master
-       then
-               # should have been left as it was!
-               false
-       else
-               true
-       fi &&
+test_expect_success 'refuse pushing rewound head without --force' '
+       pushed_head=$(git rev-parse --verify master) &&
+       victim_orig=$(cd victim && git rev-parse --verify master) &&
+       test_must_fail git send-pack ./victim master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_head" = "$victim_orig" &&
        # this should update
-       git send-pack --force ./victim/.git/ master &&
-       cmp victim/.git/refs/heads/master .git/refs/heads/master
+       git send-pack --force ./victim master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_head" = "$pushed_head"
 '
 
 test_expect_success \
         'push can be used to delete a ref' '
-       cd victim &&
-       git branch extra master &&
-       cd .. &&
-       test -f victim/.git/refs/heads/extra &&
-       git send-pack ./victim/.git/ :extra master &&
-       ! test -f victim/.git/refs/heads/extra
+       ( cd victim && git branch extra master ) &&
+       git send-pack ./victim :extra master &&
+       ( cd victim &&
+         test_must_fail git rev-parse --verify extra )
 '
 
-unset GIT_CONFIG
-HOME=`pwd`/no-such-directory
-export HOME ;# this way we force the victim/.git/config to be used.
-
-test_expect_success \
-       'pushing a delete should be denied with denyDeletes' '
-       cd victim &&
-       git config receive.denyDeletes true &&
-       git branch extra master &&
-       cd .. &&
-       test -f victim/.git/refs/heads/extra &&
-       test_must_fail git send-pack ./victim/.git/ :extra master
+test_expect_success 'refuse deleting push with denyDeletes' '
+       (
+           cd victim &&
+           ( git branch -D extra || : ) &&
+           git config receive.denyDeletes true &&
+           git branch extra master
+       ) &&
+       test_must_fail git send-pack ./victim :extra master
 '
-rm -f victim/.git/refs/heads/extra
 
-test_expect_success \
-        'pushing with --force should be denied with denyNonFastforwards' '
-       cd victim &&
-       git config receive.denyNonFastforwards true &&
-       cd .. &&
-       git update-ref refs/heads/master master^ || return 1
-       git send-pack --force ./victim/.git/ master && return 1
-       ! test_cmp .git/refs/heads/master victim/.git/refs/heads/master
+test_expect_success 'denyNonFastforwards trumps --force' '
+       (
+           cd victim &&
+           ( git branch -D extra || : ) &&
+           git config receive.denyNonFastforwards true
+       ) &&
+       victim_orig=$(cd victim && git rev-parse --verify master) &&
+       test_must_fail git send-pack --force ./victim master^:master &&
+       victim_head=$(cd victim && git rev-parse --verify master) &&
+       test "$victim_orig" = "$victim_head"
 '
 
-test_expect_success \
-       'pushing does not include non-head refs' '
-       mkdir parent && cd parent &&
-       git init && touch file && git add file && git commit -m add &&
-       cd .. &&
-       git clone parent child && cd child && git push --all &&
-       cd ../parent &&
-       git branch -a >branches && ! grep origin/master branches
+test_expect_success 'push --all excludes remote tracking hierarchy' '
+       mkdir parent &&
+       (
+           cd parent &&
+           git init && : >file && git add file && git commit -m add
+       ) &&
+       git clone parent child &&
+       (
+           cd child && git push --all
+       ) &&
+       (
+           cd parent &&
+           test -z "$(git for-each-ref refs/remotes/origin)"
+       )
 '
 
 rewound_push_setup() {
        rm -rf parent child &&
-       mkdir parent && cd parent &&
-       git init && echo one >file && git add file && git commit -m one &&
-       echo two >file && git commit -a -m two &&
-       cd .. &&
-       git clone parent child && cd child && git reset --hard HEAD^
+       mkdir parent &&
+       (
+           cd parent &&
+           git init &&
+           echo one >file && git add file && git commit -m one &&
+           echo two >file && git commit -a -m two
+       ) &&
+       git clone parent child &&
+       (
+           cd child && git reset --hard HEAD^
+       )
 }
 
 rewound_push_succeeded() {
@@ -156,30 +150,57 @@ rewound_push_failed() {
        fi
 }
 
-test_expect_success \
-       'pushing explicit refspecs respects forcing' '
+test_expect_success 'pushing explicit refspecs respects forcing' '
        rewound_push_setup &&
-       if git send-pack ../parent/.git refs/heads/master:refs/heads/master
-       then
-               false
-       else
-               true
-       fi && rewound_push_failed &&
-       git send-pack ../parent/.git +refs/heads/master:refs/heads/master &&
-       rewound_push_succeeded
+       parent_orig=$(cd parent && git rev-parse --verify master) &&
+       (
+           cd child &&
+           test_must_fail git send-pack ../parent \
+               refs/heads/master:refs/heads/master
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_orig" = "$parent_head" &&
+       (
+           cd child &&
+           git send-pack ../parent \
+               +refs/heads/master:refs/heads/master
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       child_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_head" = "$child_head"
 '
 
-test_expect_success \
-       'pushing wildcard refspecs respects forcing' '
+test_expect_success 'pushing wildcard refspecs respects forcing' '
        rewound_push_setup &&
-       if git send-pack ../parent/.git refs/heads/*:refs/heads/*
-       then
-               false
-       else
-               true
-       fi && rewound_push_failed &&
-       git send-pack ../parent/.git +refs/heads/*:refs/heads/* &&
-       rewound_push_succeeded
+       parent_orig=$(cd parent && git rev-parse --verify master) &&
+       (
+           cd child &&
+           test_must_fail git send-pack ../parent \
+               "refs/heads/*:refs/heads/*"
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_orig" = "$parent_head" &&
+       (
+           cd child &&
+           git send-pack ../parent \
+               "+refs/heads/*:refs/heads/*"
+       ) &&
+       parent_head=$(cd parent && git rev-parse --verify master) &&
+       child_head=$(cd parent && git rev-parse --verify master) &&
+       test "$parent_head" = "$child_head"
+'
+
+test_expect_success 'warn pushing to delete current branch' '
+       rewound_push_setup &&
+       (
+           cd child &&
+           git send-pack ../parent :refs/heads/master 2>errs
+       ) &&
+       grep "warning: to refuse deleting" child/errs &&
+       (
+               cd parent &&
+               test_must_fail git rev-parse --verify master
+       )
 '
 
 test_done
index 9b2e1a94c5fb5aa094853a169cbdf2f7dc56e152..5858b868ed6ff05d458996cf8c359a7148591582 100755 (executable)
@@ -71,4 +71,18 @@ test_expect_success 'post-checkout receives the right args when not switching br
         test $old = $new -a $flag = 0
 '
 
+if test "$(git config --bool core.filemode)" = true; then
+mkdir -p templates/hooks
+cat >templates/hooks/post-checkout <<'EOF'
+#!/bin/sh
+echo $@ > $GIT_DIR/post-checkout.args
+EOF
+chmod +x templates/hooks/post-checkout
+
+test_expect_success 'post-checkout hook is triggered by clone' '
+       git clone --template=templates . clone3 &&
+       test -f clone3/.git/post-checkout.args
+'
+fi
+
 test_done
index 4074e23ffa2c7a93fdbd4a367e273118224b9038..d5db75d826c8584e1d7f0f5ef298021fecd6f055 100755 (executable)
@@ -4,6 +4,12 @@ test_description='test automatic tag following'
 
 . ./test-lib.sh
 
+case $(uname -s) in
+*MINGW*)
+       say "GIT_DEBUG_SEND_PACK not supported - skipping tests"
+       test_done
+esac
+
 # End state of the repository:
 #
 #         T - tag1          S - tag2
index bc5b7ce4a6b5fd898d1ea5a9f3e6a148d45f0535..5ec668d6d8ac22f161549e5592a49bfdb0f11081 100755 (executable)
@@ -28,7 +28,7 @@ tokens_match () {
 }
 
 check_remote_track () {
-       actual=$(git remote show "$1" | sed -e '1,/Tracked/d') &&
+       actual=$(git remote show "$1" | sed -ne 's|^    \(.*\) tracked$|\1|p')
        shift &&
        tokens_match "$*" "$actual"
 }
@@ -136,47 +136,73 @@ EOF
 cat > test/expect << EOF
 * remote origin
   URL: $(pwd)/one
-  Remote branch merged with 'git pull' while on branch master
+  HEAD branch: master
+  Remote branches:
+    master new (next fetch will store in remotes/origin)
+    side   tracked
+  Local branches configured for 'git pull':
+    ahead    merges with remote master
+    master   merges with remote master
+    octopus  merges with remote topic-a
+                and with remote topic-b
+                and with remote topic-c
+    rebase  rebases onto remote master
+  Local refs configured for 'git push':
+    master pushes to master   (local out of date)
+    master pushes to upstream (create)
+* remote two
+  URL: ../two
+  HEAD branch (remote HEAD is ambiguous, may be one of the following):
+    another
     master
-  New remote branch (next fetch will store in remotes/origin)
-    master
-  Tracked remote branches
-    side
-    master
-  Local branches pushed with 'git push'
-    master:upstream
-    +refs/tags/lastbackup
+  Local refs configured for 'git push':
+    ahead  forces to master  (fast forwardable)
+    master pushes to another (up to date)
 EOF
 
 test_expect_success 'show' '
        (cd test &&
-        git config --add remote.origin.fetch \
-               refs/heads/master:refs/heads/upstream &&
+        git config --add remote.origin.fetch refs/heads/master:refs/heads/upstream &&
         git fetch &&
+        git checkout -b ahead origin/master &&
+        echo 1 >> file &&
+        test_tick &&
+        git commit -m update file &&
+        git checkout master &&
+        git branch --track octopus origin/master &&
+        git branch --track rebase origin/master &&
         git branch -d -r origin/master &&
+        git config --add remote.two.url ../two &&
+        git config branch.rebase.rebase true &&
+        git config branch.octopus.merge "topic-a topic-b topic-c" &&
         (cd ../one &&
          echo 1 > file &&
          test_tick &&
          git commit -m update file) &&
-        git config remote.origin.push \
-               refs/heads/master:refs/heads/upstream &&
-        git config --add remote.origin.push \
-               +refs/tags/lastbackup &&
-        git remote show origin > output &&
+        git config --add remote.origin.push : &&
+        git config --add remote.origin.push refs/heads/master:refs/heads/upstream &&
+        git config --add remote.origin.push +refs/tags/lastbackup &&
+        git config --add remote.two.push +refs/heads/ahead:refs/heads/master &&
+        git config --add remote.two.push refs/heads/master:refs/heads/another &&
+        git remote show origin two > output &&
+        git branch -d rebase octopus &&
         test_cmp expect output)
 '
 
 cat > test/expect << EOF
 * remote origin
   URL: $(pwd)/one
-  Remote branch merged with 'git pull' while on branch master
-    master
-  Tracked remote branches
+  HEAD branch: (not queried)
+  Remote branches: (status not queried)
     master
     side
-  Local branches pushed with 'git push'
-    master:upstream
-    +refs/tags/lastbackup
+  Local branches configured for 'git pull':
+    ahead  merges with remote master
+    master merges with remote master
+  Local refs configured for 'git push' (status not queried):
+    (matching)           pushes to (matching)
+    refs/heads/master    pushes to refs/heads/upstream
+    refs/tags/lastbackup forces to refs/tags/lastbackup
 EOF
 
 test_expect_success 'show -n' '
@@ -197,6 +223,46 @@ test_expect_success 'prune' '
         test_must_fail git rev-parse refs/remotes/origin/side)
 '
 
+test_expect_success 'set-head --delete' '
+       (cd test &&
+        git symbolic-ref refs/remotes/origin/HEAD &&
+        git remote set-head --delete origin &&
+        test_must_fail git symbolic-ref refs/remotes/origin/HEAD)
+'
+
+test_expect_success 'set-head --auto' '
+       (cd test &&
+        git remote set-head --auto origin &&
+        echo refs/remotes/origin/master >expect &&
+        git symbolic-ref refs/remotes/origin/HEAD >output &&
+        test_cmp expect output
+       )
+'
+
+cat >test/expect <<EOF
+error: Multiple remote HEAD branches. Please choose one explicitly with:
+  git remote set-head two another
+  git remote set-head two master
+EOF
+
+test_expect_success 'set-head --auto fails w/multiple HEADs' '
+       (cd test &&
+        test_must_fail git remote set-head --auto two >output 2>&1 &&
+       test_cmp expect output)
+'
+
+cat >test/expect <<EOF
+refs/remotes/origin/side2
+EOF
+
+test_expect_success 'set-head explicit' '
+       (cd test &&
+        git remote set-head origin side2 &&
+        git symbolic-ref refs/remotes/origin/HEAD >output &&
+        git remote set-head origin master &&
+        test_cmp expect output)
+'
+
 cat > test/expect << EOF
 Pruning origin
 URL: $(pwd)/one
@@ -343,7 +409,7 @@ test_expect_success '"remote show" does not show symbolic refs' '
        git clone one three &&
        (cd three &&
         git remote show origin > output &&
-        ! grep HEAD < output &&
+        ! grep "^ *HEAD$" < output &&
         ! grep -i stale < output)
 
 '
@@ -402,4 +468,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
+
diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh
new file mode 100755 (executable)
index 0000000..2a1806b
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='git remote group handling'
+. ./test-lib.sh
+
+mark() {
+       echo "$1" >mark
+}
+
+update_repo() {
+       (cd $1 &&
+       echo content >>file &&
+       git add file &&
+       git commit -F ../mark)
+}
+
+update_repos() {
+       update_repo one $1 &&
+       update_repo two $1
+}
+
+repo_fetched() {
+       if test "`git log -1 --pretty=format:%s $1 --`" = "`cat mark`"; then
+               echo >&2 "repo was fetched: $1"
+               return 0
+       fi
+       echo >&2 "repo was not fetched: $1"
+       return 1
+}
+
+test_expect_success 'setup' '
+       mkdir one && (cd one && git init) &&
+       mkdir two && (cd two && git init) &&
+       git remote add -m master one one &&
+       git remote add -m master two two
+'
+
+test_expect_success 'no group updates all' '
+       mark update-all &&
+       update_repos &&
+       git remote update &&
+       repo_fetched one &&
+       repo_fetched two
+'
+
+test_expect_success 'nonexistant group produces error' '
+       mark nonexistant &&
+       update_repos &&
+       test_must_fail git remote update nonexistant &&
+       ! repo_fetched one &&
+       ! repo_fetched two
+'
+
+test_expect_success 'updating group updates all members' '
+       mark group-all &&
+       update_repos &&
+       git config --add remotes.all one &&
+       git config --add remotes.all two &&
+       git remote update all &&
+       repo_fetched one &&
+       repo_fetched two
+'
+
+test_expect_success 'updating group does not update non-members' '
+       mark group-some &&
+       update_repos &&
+       git config --add remotes.some one &&
+       git remote update some &&
+       repo_fetched one &&
+       ! repo_fetched two
+'
+
+test_expect_success 'updating remote name updates that remote' '
+       mark remote-name &&
+       update_repos &&
+       git remote update one &&
+       repo_fetched one &&
+       ! repo_fetched two
+'
+
+test_done
index 9e679b402dc826185377c431d353decd8a8a2bed..bee3424fed770aacac6dbcdf4ba58bfd7bf5c2da 100755 (executable)
@@ -191,38 +191,39 @@ test_expect_success 'bundle should be able to create a full history' '
 
 '
 
-test "$TEST_RSYNC" && {
+! rsync --help > /dev/null 2> /dev/null &&
+say 'Skipping rsync tests because rsync was not found' || {
 test_expect_success 'fetch via rsync' '
        git pack-refs &&
        mkdir rsynced &&
-       cd rsynced &&
-       git init &&
-       git fetch rsync://127.0.0.1$(pwd)/../.git master:refs/heads/master &&
-       git gc --prune &&
-       test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
-       git fsck --full
+       (cd rsynced &&
+        git init --bare &&
+        git fetch "rsync:$(pwd)/../.git" master:refs/heads/master &&
+        git gc --prune &&
+        test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
+        git fsck --full)
 '
 
 test_expect_success 'push via rsync' '
-       mkdir ../rsynced2 &&
-       (cd ../rsynced2 &&
+       mkdir rsynced2 &&
+       (cd rsynced2 &&
         git init) &&
-       git push rsync://127.0.0.1$(pwd)/../rsynced2/.git master &&
-       cd ../rsynced2 &&
-       git gc --prune &&
-       test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
-       git fsck --full
+       (cd rsynced &&
+        git push "rsync:$(pwd)/../rsynced2/.git" master) &&
+       (cd rsynced2 &&
+        git gc --prune &&
+        test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
+        git fsck --full)
 '
 
 test_expect_success 'push via rsync' '
-       cd .. &&
        mkdir rsynced3 &&
        (cd rsynced3 &&
         git init) &&
-       git push --all rsync://127.0.0.1$(pwd)/rsynced3/.git &&
-       cd rsynced3 &&
-       test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
-       git fsck --full
+       git push --all "rsync:$(pwd)/rsynced3/.git" &&
+       (cd rsynced3 &&
+        test $(git rev-parse master) = $(cd .. && git rev-parse master) &&
+        git fsck --full)
 '
 }
 
index 22ba380034775e7584a33ca606294af34f568443..c28932216b1dd0893bc3c3a0a643963663ff8e1f 100755 (executable)
@@ -72,4 +72,16 @@ test_refspec fetch ':refs/remotes/frotz/HEAD-to-me'
 test_refspec push ':refs/remotes/frotz/delete me'              invalid
 test_refspec fetch ':refs/remotes/frotz/HEAD to me'            invalid
 
+test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid
+test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*-blah' invalid
+
+test_refspec fetch 'refs/heads*/for-linus:refs/remotes/mine/*' invalid
+test_refspec push 'refs/heads*/for-linus:refs/remotes/mine/*' invalid
+
+test_refspec fetch 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
+test_refspec push 'refs/heads/*/*/for-linus:refs/remotes/mine/*' invalid
+
+test_refspec fetch 'refs/heads/*/for-linus:refs/remotes/mine/*'
+test_refspec push 'refs/heads/*/for-linus:refs/remotes/mine/*'
+
 test_done
index 1f4608d8ba4748a2bd5c7a3d5a75a04364e8f646..dbb927dec8ea9f40e8e106f416c276f1b6a07868 100755 (executable)
@@ -129,8 +129,7 @@ do
        '' | '#'*) continue ;;
        esac
        test=`echo "$cmd" | sed -e 's|[/ ][/ ]*|_|g'`
-       cnt=`expr $test_count + 1`
-       pfx=`printf "%04d" $cnt`
+       pfx=`printf "%04d" $test_count`
        expect_f="$TEST_DIRECTORY/t5515/fetch.$test"
        actual_f="$pfx-fetch.$test"
        expect_r="$TEST_DIRECTORY/t5515/refs.$test"
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' '
diff --git a/t/t5521-pull-symlink.sh b/t/t5521-pull-symlink.sh
deleted file mode 100755 (executable)
index 5672b51..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/sh
-
-test_description='pulling from symlinked subdir'
-
-. ./test-lib.sh
-
-# The scenario we are building:
-#
-#   trash\ directory/
-#     clone-repo/
-#       subdir/
-#         bar
-#     subdir-link -> clone-repo/subdir/
-#
-# The working directory is subdir-link.
-
-mkdir subdir
-echo file >subdir/file
-git add subdir/file
-git commit -q -m file
-git clone -q . clone-repo
-ln -s clone-repo/subdir/ subdir-link
-
-
-# Demonstrate that things work if we just avoid the symlink
-#
-test_expect_success 'pulling from real subdir' '
-       (
-               echo real >subdir/file &&
-               git commit -m real subdir/file &&
-               cd clone-repo/subdir/ &&
-               git pull &&
-               test real = $(cat file)
-       )
-'
-
-# From subdir-link, pulling should work as it does from
-# clone-repo/subdir/.
-#
-# Instead, the error pull gave was:
-#
-#   fatal: 'origin': unable to chdir or not a git archive
-#   fatal: The remote end hung up unexpectedly
-#
-# because git would find the .git/config for the "trash directory"
-# repo, not for the clone-repo repo.  The "trash directory" repo
-# had no entry for origin.  Git found the wrong .git because
-# git rev-parse --show-cdup printed a path relative to
-# clone-repo/subdir/, not subdir-link/.  Git rev-parse --show-cdup
-# used the correct .git, but when the git pull shell script did
-# "cd `git rev-parse --show-cdup`", it ended up in the wrong
-# directory.  A POSIX shell's "cd" works a little differently
-# than chdir() in C; "cd -P" is much closer to chdir().
-#
-test_expect_success 'pulling from symlinked subdir' '
-       (
-               echo link >subdir/file &&
-               git commit -m link subdir/file &&
-               cd subdir-link/ &&
-               git pull &&
-               test link = $(cat file)
-       )
-'
-
-# Prove that the remote end really is a repo, and other commands
-# work fine in this context.  It's just that "git pull" breaks.
-#
-test_expect_success 'pushing from symlinked subdir' '
-       (
-               cd subdir-link/ &&
-               echo push >file &&
-               git commit -m push ./file &&
-               git push
-       ) &&
-       test push = $(git show HEAD:subdir/file)
-'
-
-test_done
diff --git a/t/t5522-pull-symlink.sh b/t/t5522-pull-symlink.sh
new file mode 100755 (executable)
index 0000000..86bbd7d
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+test_description='pulling from symlinked subdir'
+
+. ./test-lib.sh
+
+if ! test_have_prereq SYMLINKS
+then
+       say 'Symbolic links not supported, skipping tests.'
+       test_done
+fi
+
+# The scenario we are building:
+#
+#   trash\ directory/
+#     clone-repo/
+#       subdir/
+#         bar
+#     subdir-link -> clone-repo/subdir/
+#
+# The working directory is subdir-link.
+
+mkdir subdir
+echo file >subdir/file
+git add subdir/file
+git commit -q -m file
+git clone -q . clone-repo
+ln -s clone-repo/subdir/ subdir-link
+
+
+# Demonstrate that things work if we just avoid the symlink
+#
+test_expect_success 'pulling from real subdir' '
+       (
+               echo real >subdir/file &&
+               git commit -m real subdir/file &&
+               cd clone-repo/subdir/ &&
+               git pull &&
+               test real = $(cat file)
+       )
+'
+
+# From subdir-link, pulling should work as it does from
+# clone-repo/subdir/.
+#
+# Instead, the error pull gave was:
+#
+#   fatal: 'origin': unable to chdir or not a git archive
+#   fatal: The remote end hung up unexpectedly
+#
+# because git would find the .git/config for the "trash directory"
+# repo, not for the clone-repo repo.  The "trash directory" repo
+# had no entry for origin.  Git found the wrong .git because
+# git rev-parse --show-cdup printed a path relative to
+# clone-repo/subdir/, not subdir-link/.  Git rev-parse --show-cdup
+# used the correct .git, but when the git pull shell script did
+# "cd `git rev-parse --show-cdup`", it ended up in the wrong
+# directory.  A POSIX shell's "cd" works a little differently
+# than chdir() in C; "cd -P" is much closer to chdir().
+#
+test_expect_success 'pulling from symlinked subdir' '
+       (
+               echo link >subdir/file &&
+               git commit -m link subdir/file &&
+               cd subdir-link/ &&
+               git pull &&
+               test link = $(cat file)
+       )
+'
+
+# Prove that the remote end really is a repo, and other commands
+# work fine in this context.  It's just that "git pull" breaks.
+#
+test_expect_success 'pushing from symlinked subdir' '
+       (
+               cd subdir-link/ &&
+               echo push >file &&
+               git commit -m push ./file &&
+               git push
+       ) &&
+       test push = $(git show HEAD:subdir/file)
+'
+
+test_done
index c236b5e83beed8997e8f6c3a50f0bb74f73f3f33..5fe479e1c2362ba05d059805d412b605aa7a83aa 100755 (executable)
@@ -11,22 +11,16 @@ This test runs various sanity checks on http-push.'
 
 ROOT_PATH="$PWD"
 LIB_HTTPD_DAV=t
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5540'}
 
 if git http-push > /dev/null 2>&1 || [ $? -eq 128 ]
 then
        say "skipping test, USE_CURL_MULTI is not defined"
        test_done
-       exit
 fi
 
 . "$TEST_DIRECTORY"/lib-httpd.sh
-
-if ! start_httpd >&3 2>&4
-then
-       say "skipping test, web server setup failed"
-       test_done
-       exit
-fi
+start_httpd
 
 test_expect_success 'setup remote repository' '
        cd "$ROOT_PATH" &&
@@ -94,6 +88,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
diff --git a/t/t5550-http-fetch.sh b/t/t5550-http-fetch.sh
new file mode 100755 (executable)
index 0000000..05b1b62
--- /dev/null
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+test_description='test fetching over http'
+. ./test-lib.sh
+
+if test -n "$NO_CURL"; then
+       say 'skipping test, git built without http support'
+       test_done
+fi
+
+. "$TEST_DIRECTORY"/lib-httpd.sh
+LIB_HTTPD_PORT=${LIB_HTTPD_PORT-'5550'}
+start_httpd
+
+test_expect_success 'setup repository' '
+       echo content >file &&
+       git add file &&
+       git commit -m one
+'
+
+test_expect_success 'create http-accessible bare repository' '
+       mkdir "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       (cd "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+        git --bare init &&
+        echo "exec git update-server-info" >hooks/post-update &&
+        chmod +x hooks/post-update
+       ) &&
+       git remote add public "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" &&
+       git push public master:master
+'
+
+test_expect_success 'clone http repository' '
+       git clone $HTTPD_URL/repo.git clone &&
+       test_cmp file clone/file
+'
+
+test_expect_success 'fetch changes via http' '
+       echo content >>file &&
+       git commit -a -m two &&
+       git push public
+       (cd clone && git pull) &&
+       test_cmp file clone/file
+'
+
+test_expect_success 'http remote detects correct HEAD' '
+       git push public master:other &&
+       (cd clone &&
+        git remote set-head origin -d &&
+        git remote set-head origin -a &&
+        git symbolic-ref refs/remotes/origin/HEAD > output &&
+        echo refs/remotes/origin/master > expect &&
+        test_cmp expect output
+       )
+'
+
+stop_httpd
+test_done
index 78a3fa639c97b7dce054d89c74c67a855d0d7954..2335d8bc850b4b0010fcb9ac64a25d0c205e8a42 100755 (executable)
@@ -125,4 +125,53 @@ 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_expect_success 'clone respects global branch.autosetuprebase' '
+       (
+               HOME=$(pwd) &&
+               export HOME &&
+               test_config="$HOME/.gitconfig" &&
+               unset GIT_CONFIG_NOGLOBAL &&
+               git config -f "$test_config" branch.autosetuprebase remote &&
+               rm -fr dst &&
+               git clone src dst &&
+               cd dst &&
+               actual="z$(git config branch.master.rebase)" &&
+               test ztrue = $actual
+       )
+'
+
 test_done
index 82b1d1e2b3f6704cf08540f0e6f4cecf0981cb7d..deffdaee490d620c44baaee143f11be604171a42 100755 (executable)
@@ -18,8 +18,8 @@ test_expect_success 'clone calls git upload-pack unqualified with no -u option'
 '
 
 test_expect_success 'clone calls specified git upload-pack with -u option' '
-       GIT_SSH=./not_ssh git clone -u /something/bin/git-upload-pack localhost:/path/to/repo junk
-       echo "localhost /something/bin/git-upload-pack '\''/path/to/repo'\''" >expected
+       GIT_SSH=./not_ssh git clone -u ./something/bin/git-upload-pack localhost:/path/to/repo junk
+       echo "localhost ./something/bin/git-upload-pack '\''/path/to/repo'\''" >expected
        test_cmp expected not_ssh_output
 '
 
index fe0fda282ca4f085f506c743848966fa6bb93591..19b5c0d552fa8b4665b5e396833264e258365b28 100755 (executable)
@@ -116,4 +116,30 @@ 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_expect_success 'clone empty repository, and then push should not segfault.' '
+       cd "$D" &&
+       rm -fr empty/ empty-clone/ &&
+       mkdir empty &&
+       (cd empty && git init) &&
+       git clone empty empty-clone &&
+       (cd empty-clone &&
+       test_must_fail git push)
+'
+
 test_done
diff --git a/t/t5704-bundle.sh b/t/t5704-bundle.sh
new file mode 100755 (executable)
index 0000000..a8f4419
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='some bundle related tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+       : > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       test_tick &&
+       git tag -m tag tag &&
+       : > file2 &&
+       git add file2 &&
+       : > file3 &&
+       test_tick &&
+       git commit -m second &&
+       git add file3 &&
+       test_tick &&
+       git commit -m third
+
+'
+
+test_expect_success 'tags can be excluded by rev-list options' '
+
+       git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
+       git ls-remote bundle > output &&
+       ! grep tag output
+
+'
+
+test_done
diff --git a/t/t5705-clone-2gb.sh b/t/t5705-clone-2gb.sh
new file mode 100755 (executable)
index 0000000..9f52154
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+test_description='Test cloning a repository larger than 2 gigabyte'
+. ./test-lib.sh
+
+test -z "$GIT_TEST_CLONE_2GB" &&
+say "Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t" &&
+test_done &&
+exit
+
+test_expect_success 'setup' '
+
+       git config pack.compression 0 &&
+       git config pack.depth 0 &&
+       blobsize=$((20*1024*1024)) &&
+       blobcount=$((2*1024*1024*1024/$blobsize+1)) &&
+       i=1 &&
+       (while test $i -le $blobcount
+        do
+               printf "Generating blob $i/$blobcount\r" >&2 &&
+               printf "blob\nmark :$i\ndata $blobsize\n" &&
+               #test-genrandom $i $blobsize &&
+               printf "%-${blobsize}s" $i &&
+               echo "M 100644 :$i $i" >> commit
+               i=$(($i+1)) ||
+               echo $? > exit-status
+        done &&
+        echo "commit refs/heads/master" &&
+        echo "author A U Thor <author@email.com> 123456789 +0000" &&
+        echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
+        echo "data 5" &&
+        echo ">2gb" &&
+        cat commit) |
+       git fast-import &&
+       test ! -f exit-status
+
+'
+
+test_expect_success 'clone' '
+
+       git clone --bare --no-hardlinks . clone
+
+'
+
+test_done
index 86bf7e14ba5bb9a76ef00cb5a3564e326f674a18..59d1f6283bec5cf7740d988dfd090c1463389c71 100755 (executable)
@@ -14,7 +14,7 @@ touch foo && git add foo && git commit -m "added foo" &&
 test_format() {
        cat >expect.$1
        test_expect_success "format $1" "
-git rev-list --pretty=format:$2 master >output.$1 &&
+git rev-list --pretty=format:'$2' master >output.$1 &&
 test_cmp expect.$1 output.$1
 "
 }
@@ -101,6 +101,13 @@ commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
 \e[31mfoo\e[32mbar\e[34mbaz\e[mxyzzy
 EOF
 
+test_format advanced-colors '%C(red yellow bold)foo%C(reset)' <<'EOF'
+commit 131a310eb913d107dd3c09a65d1651175898735d
+\e[1;31;43mfoo\e[m
+commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
+\e[1;31;43mfoo\e[m
+EOF
+
 cat >commit-msg <<'EOF'
 Test printing of complex bodies
 
diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh
deleted file mode 100755 (executable)
index 65be95f..0000000
+++ /dev/null
@@ -1,139 +0,0 @@
-#!/bin/sh
-
-test_description='Merge-recursive merging renames'
-. ./test-lib.sh
-
-test_expect_success setup \
-'
-cat >A <<\EOF &&
-a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
-b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
-c cccccccccccccccccccccccccccccccccccccccccccccccc
-d dddddddddddddddddddddddddddddddddddddddddddddddd
-e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
-f ffffffffffffffffffffffffffffffffffffffffffffffff
-g gggggggggggggggggggggggggggggggggggggggggggggggg
-h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
-i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
-j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
-k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
-l llllllllllllllllllllllllllllllllllllllllllllllll
-m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
-n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
-o oooooooooooooooooooooooooooooooooooooooooooooooo
-EOF
-
-cat >M <<\EOF &&
-A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
-B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
-C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
-D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
-E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
-F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
-G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
-H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
-I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
-J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
-K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
-L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
-M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
-N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
-O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
-EOF
-
-git add A M &&
-git commit -m "initial has A and M" &&
-git branch white &&
-git branch red &&
-git branch blue &&
-
-git checkout white &&
-sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
-sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
-rm -f A M &&
-git update-index --add --remove A B M N &&
-git commit -m "white renames A->B, M->N" &&
-
-git checkout red &&
-echo created by red >R &&
-git update-index --add R &&
-git commit -m "red creates R" &&
-
-git checkout blue &&
-sed -e "/^o /s/.*/g : blue changes a line/" <A >B &&
-rm -f A &&
-mv B A &&
-git update-index A &&
-git commit -m "blue modify A" &&
-
-git checkout master'
-
-# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae
-test_expect_success 'merge white into red (A->B,M->N)' \
-'
-       git checkout -b red-white red &&
-       git merge white &&
-       git write-tree >/dev/null || {
-               echo "BAD: merge did not complete"
-               return 1
-       }
-
-       test -f B || {
-               echo "BAD: B does not exist in working directory"
-               return 1
-       }
-       test -f N || {
-               echo "BAD: N does not exist in working directory"
-               return 1
-       }
-       test -f R || {
-               echo "BAD: R does not exist in working directory"
-               return 1
-       }
-
-       test -f A && {
-               echo "BAD: A still exists in working directory"
-               return 1
-       }
-       test -f M && {
-               echo "BAD: M still exists in working directory"
-               return 1
-       }
-       return 0
-'
-
-# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9
-test_expect_success 'merge blue into white (A->B, mod A, A untracked)' \
-'
-       git checkout -b white-blue white &&
-       echo dirty >A &&
-       git merge blue &&
-       git write-tree >/dev/null || {
-               echo "BAD: merge did not complete"
-               return 1
-       }
-
-       test -f A || {
-               echo "BAD: A does not exist in working directory"
-               return 1
-       }
-       test `cat A` = dirty || {
-               echo "BAD: A content is wrong"
-               return 1
-       }
-       test -f B || {
-               echo "BAD: B does not exist in working directory"
-               return 1
-       }
-       test -f N || {
-               echo "BAD: N does not exist in working directory"
-               return 1
-       }
-       test -f M && {
-               echo "BAD: M still exists in working directory"
-               return 1
-       }
-       return 0
-'
-
-test_done
index 052a6c90f5a184ddc82f2db1a2907a1b1104166c..54b7ea6505d8c189c6c557cffd3d6518df06fb73 100755 (executable)
@@ -506,6 +506,66 @@ test_expect_success 'optimized merge base checks' '
        unset GIT_TRACE
 '
 
+# This creates another side branch called "parallel" with some files
+# in some directories, to test bisecting with paths.
+#
+# We should have the following:
+#
+#    P1-P2-P3-P4-P5-P6-P7
+#   /        /        /
+# H1-H2-H3-H4-H5-H6-H7
+#            \  \     \
+#             S5-A     \
+#              \        \
+#               S6-S7----B
+#
+test_expect_success '"parallel" side branch creation' '
+       git bisect reset &&
+       git checkout -b parallel $HASH1 &&
+       mkdir dir1 dir2 &&
+       add_line_into_file "1(para): line 1 on parallel branch" dir1/file1 &&
+       PARA_HASH1=$(git rev-parse --verify HEAD) &&
+       add_line_into_file "2(para): line 2 on parallel branch" dir2/file2 &&
+       PARA_HASH2=$(git rev-parse --verify HEAD) &&
+       add_line_into_file "3(para): line 3 on parallel branch" dir2/file3 &&
+       PARA_HASH3=$(git rev-parse --verify HEAD)
+       git merge -m "merge HASH4 and PARA_HASH3" "$HASH4" &&
+       PARA_HASH4=$(git rev-parse --verify HEAD)
+       add_line_into_file "5(para): add line on parallel branch" dir1/file1 &&
+       PARA_HASH5=$(git rev-parse --verify HEAD)
+       add_line_into_file "6(para): add line on parallel branch" dir2/file2 &&
+       PARA_HASH6=$(git rev-parse --verify HEAD)
+       git merge -m "merge HASH7 and PARA_HASH6" "$HASH7" &&
+       PARA_HASH7=$(git rev-parse --verify HEAD)
+'
+
+test_expect_success 'restricting bisection on one dir' '
+       git bisect reset &&
+       git bisect start HEAD $HASH1 -- dir1 &&
+       para1=$(git rev-parse --verify HEAD) &&
+       test "$para1" = "$PARA_HASH1" &&
+       git bisect bad > my_bisect_log.txt &&
+       grep "$PARA_HASH1 is first bad commit" my_bisect_log.txt
+'
+
+test_expect_success 'restricting bisection on one dir and a file' '
+       git bisect reset &&
+       git bisect start HEAD $HASH1 -- dir1 hello &&
+       para4=$(git rev-parse --verify HEAD) &&
+       test "$para4" = "$PARA_HASH4" &&
+       git bisect bad &&
+       hash3=$(git rev-parse --verify HEAD) &&
+       test "$hash3" = "$HASH3" &&
+       git bisect good &&
+       hash4=$(git rev-parse --verify HEAD) &&
+       test "$hash4" = "$HASH4" &&
+       git bisect good &&
+       para1=$(git rev-parse --verify HEAD) &&
+       test "$para1" = "$PARA_HASH1" &&
+       git bisect good > my_bisect_log.txt &&
+       grep "$PARA_HASH4 is first bad commit" my_bisect_log.txt
+'
+
 #
 #
 test_done
index 8073e0c3efb2ac01e4a81e722fc357bcab13f5d4..8a3304fb0b5901fb02435d3b77c3d049404f4e25 100755 (executable)
@@ -3,8 +3,10 @@
 test_description='merge-recursive: handle file mode'
 . ./test-lib.sh
 
-# Note that we follow "chmod +x F" with "update-index --chmod=+x F" to
-# help filesystems that do not have the executable bit.
+if ! test "$(git config --bool core.filemode)" = false
+then
+       test_set_prereq FILEMODE
+fi
 
 test_expect_success 'mode change in one branch: keep changed version' '
        : >file1 &&
@@ -15,11 +17,14 @@ test_expect_success 'mode change in one branch: keep changed version' '
        git add dummy &&
        git commit -m a &&
        git checkout -b b1 master &&
-       chmod +x file1 &&
-       git update-index --chmod=+x file1 &&
+       test_chmod +x file1 &&
        git commit -m b1 &&
        git checkout a1 &&
        git merge-recursive master -- a1 b1 &&
+       git ls-files -s file1 | grep ^100755
+'
+
+test_expect_success FILEMODE 'verify executable bit on file' '
        test -x file1
 '
 
@@ -28,8 +33,7 @@ test_expect_success 'mode change in both branches: expect conflict' '
        git checkout -b a2 master &&
        : >file2 &&
        H=$(git hash-object file2) &&
-       chmod +x file2 &&
-       git update-index --add --chmod=+x file2 &&
+       test_chmod +x file2 &&
        git commit -m a2 &&
        git checkout -b b2 master &&
        : >file2 &&
@@ -46,6 +50,10 @@ test_expect_success 'mode change in both branches: expect conflict' '
                echo "100644 $H 3       file2"
        ) >expect &&
        test_cmp actual expect &&
+       git ls-files -s file2 | grep ^100755
+'
+
+test_expect_success FILEMODE 'verify executable bit on file' '
        test -x file2
 '
 
diff --git a/t/t6034-merge-rename-nocruft.sh b/t/t6034-merge-rename-nocruft.sh
new file mode 100755 (executable)
index 0000000..65be95f
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+test_description='Merge-recursive merging renames'
+. ./test-lib.sh
+
+test_expect_success setup \
+'
+cat >A <<\EOF &&
+a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+c cccccccccccccccccccccccccccccccccccccccccccccccc
+d dddddddddddddddddddddddddddddddddddddddddddddddd
+e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+f ffffffffffffffffffffffffffffffffffffffffffffffff
+g gggggggggggggggggggggggggggggggggggggggggggggggg
+h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
+k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
+l llllllllllllllllllllllllllllllllllllllllllllllll
+m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+o oooooooooooooooooooooooooooooooooooooooooooooooo
+EOF
+
+cat >M <<\EOF &&
+A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
+C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
+E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
+F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
+H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
+I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
+J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
+K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
+L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
+M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
+O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
+EOF
+
+git add A M &&
+git commit -m "initial has A and M" &&
+git branch white &&
+git branch red &&
+git branch blue &&
+
+git checkout white &&
+sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
+sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+rm -f A M &&
+git update-index --add --remove A B M N &&
+git commit -m "white renames A->B, M->N" &&
+
+git checkout red &&
+echo created by red >R &&
+git update-index --add R &&
+git commit -m "red creates R" &&
+
+git checkout blue &&
+sed -e "/^o /s/.*/g : blue changes a line/" <A >B &&
+rm -f A &&
+mv B A &&
+git update-index A &&
+git commit -m "blue modify A" &&
+
+git checkout master'
+
+# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae
+test_expect_success 'merge white into red (A->B,M->N)' \
+'
+       git checkout -b red-white red &&
+       git merge white &&
+       git write-tree >/dev/null || {
+               echo "BAD: merge did not complete"
+               return 1
+       }
+
+       test -f B || {
+               echo "BAD: B does not exist in working directory"
+               return 1
+       }
+       test -f N || {
+               echo "BAD: N does not exist in working directory"
+               return 1
+       }
+       test -f R || {
+               echo "BAD: R does not exist in working directory"
+               return 1
+       }
+
+       test -f A && {
+               echo "BAD: A still exists in working directory"
+               return 1
+       }
+       test -f M && {
+               echo "BAD: M still exists in working directory"
+               return 1
+       }
+       return 0
+'
+
+# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9
+test_expect_success 'merge blue into white (A->B, mod A, A untracked)' \
+'
+       git checkout -b white-blue white &&
+       echo dirty >A &&
+       git merge blue &&
+       git write-tree >/dev/null || {
+               echo "BAD: merge did not complete"
+               return 1
+       }
+
+       test -f A || {
+               echo "BAD: A does not exist in working directory"
+               return 1
+       }
+       test `cat A` = dirty || {
+               echo "BAD: A content is wrong"
+               return 1
+       }
+       test -f B || {
+               echo "BAD: B does not exist in working directory"
+               return 1
+       }
+       test -f N || {
+               echo "BAD: N does not exist in working directory"
+               return 1
+       }
+       test -f M && {
+               echo "BAD: M still exists in working directory"
+               return 1
+       }
+       return 0
+'
+
+test_done
index ba9060190d31f0b57a461c114e1c856523845f78..3d6db4d386a0a009892fac490818294664d188fa 100755 (executable)
@@ -29,7 +29,9 @@ test_expect_success setup '
                git checkout -b b4 origin &&
                advance e &&
                advance f
-       )
+       ) &&
+       git checkout -b follower --track master &&
+       advance g
 '
 
 script='s/^..\(b.\)[    0-9a-f]*\[\([^]]*\)\].*/\1 \2/p'
@@ -56,6 +58,12 @@ test_expect_success 'checkout' '
        grep "have 1 and 1 different" actual
 '
 
+test_expect_success 'checkout with local tracked branch' '
+       git checkout master &&
+       git checkout follower >actual
+       grep "is ahead of" actual
+'
+
 test_expect_success 'status' '
        (
                cd test &&
index 8f5a06f7dd8383722022eb7a8abd0ff9bafa6f45..2049ab6cf844da233837b58857cfde8a2d563252 100755 (executable)
@@ -83,13 +83,13 @@ test_expect_success 'merge-msg test #1' '
 '
 
 cat >expected <<EOF
-Merge branch 'left' of $TEST_DIRECTORY/$test
+Merge branch 'left' of $(pwd)
 EOF
 
 test_expect_success 'merge-msg test #2' '
 
        git checkout master &&
-       git fetch "$TEST_DIRECTORY/$test" left &&
+       git fetch "$(pwd)" left &&
 
        git fmt-merge-msg <.git/FETCH_HEAD >actual &&
        test_cmp expected actual
index 8bfae44a8392898453785f43c7e35b65e9c1f017..8052c86ad3516505765ab214f4801940c8cc1684 100755 (executable)
@@ -26,6 +26,13 @@ test_expect_success 'Create sample commit with known timestamp' '
        git tag -a -m "Tagging at $datestamp" testtag
 '
 
+test_expect_success 'Create upstream config' '
+       git update-ref refs/remotes/origin/master master &&
+       git remote add origin nowhere &&
+       git config branch.master.remote origin &&
+       git config branch.master.merge refs/heads/master
+'
+
 test_atom() {
        case "$1" in
                head) ref=refs/heads/master ;;
@@ -39,6 +46,7 @@ test_atom() {
 }
 
 test_atom head refname refs/heads/master
+test_atom head upstream refs/remotes/origin/master
 test_atom head objecttype commit
 test_atom head objectsize 171
 test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581
@@ -68,6 +76,7 @@ test_atom head contents 'Initial
 '
 
 test_atom tag refname refs/tags/testtag
+test_atom tag upstream ''
 test_atom tag objecttype tag
 test_atom tag objectsize 154
 test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322
@@ -203,6 +212,7 @@ test_expect_success 'Check format "rfc2822" date fields output' '
 
 cat >expected <<\EOF
 refs/heads/master
+refs/remotes/origin/master
 refs/tags/testtag
 EOF
 
@@ -214,6 +224,7 @@ test_expect_success 'Verify ascending sort' '
 
 cat >expected <<\EOF
 refs/tags/testtag
+refs/remotes/origin/master
 refs/heads/master
 EOF
 
@@ -224,6 +235,7 @@ test_expect_success 'Verify descending sort' '
 
 cat >expected <<\EOF
 'refs/heads/master'
+'refs/remotes/origin/master'
 'refs/tags/testtag'
 EOF
 
@@ -244,6 +256,7 @@ test_expect_success 'Quoting style: python' '
 
 cat >expected <<\EOF
 "refs/heads/master"
+"refs/remotes/origin/master"
 "refs/tags/testtag"
 EOF
 
@@ -273,16 +286,26 @@ test_expect_success 'Check short refname format' '
        test_cmp expected actual
 '
 
+cat >expected <<EOF
+origin/master
+EOF
+
+test_expect_success 'Check short upstream format' '
+       git for-each-ref --format="%(upstream:short)" refs/heads >actual &&
+       test_cmp expected actual
+'
+
 test_expect_success 'Check for invalid refname format' '
        test_must_fail git for-each-ref --format="%(refname:INVALID)"
 '
 
 cat >expected <<\EOF
 heads/master
-master
+tags/master
 EOF
 
-test_expect_success 'Check ambiguous head and tag refs' '
+test_expect_success 'Check ambiguous head and tag refs (strict)' '
+       git config --bool core.warnambiguousrefs true &&
        git checkout -b newtag &&
        echo "Using $datestamp" > one &&
        git add one &&
@@ -293,12 +316,23 @@ test_expect_success 'Check ambiguous head and tag refs' '
        test_cmp expected actual
 '
 
+cat >expected <<\EOF
+heads/master
+master
+EOF
+
+test_expect_success 'Check ambiguous head and tag refs (loose)' '
+       git config --bool core.warnambiguousrefs false &&
+       git for-each-ref --format "%(refname:short)" refs/heads/master refs/tags/master >actual &&
+       test_cmp expected actual
+'
+
 cat >expected <<\EOF
 heads/ambiguous
 ambiguous
 EOF
 
-test_expect_success 'Check ambiguous head and tag refs II' '
+test_expect_success 'Check ambiguous head and tag refs II (loose)' '
        git checkout master &&
        git tag ambiguous testtag^0 &&
        git branch ambiguous testtag^0 &&
index 8fb3a56838dd476b9b0923f835ce70bd95499f2b..10b8f8c44befdb4eb00b3959f8b29cbebb7a22e1 100755 (executable)
@@ -206,7 +206,7 @@ test_expect_success 'git mv should not change sha1 of moved cache entry' '
 
 rm -f dirty dirty2
 
-test_expect_success 'git mv should overwrite symlink to a file' '
+test_expect_success SYMLINKS 'git mv should overwrite symlink to a file' '
 
        rm -fr .git &&
        git init &&
@@ -225,7 +225,7 @@ test_expect_success 'git mv should overwrite symlink to a file' '
 
 rm -f moved symlink
 
-test_expect_success 'git mv should overwrite file with a symlink' '
+test_expect_success SYMLINKS 'git mv should overwrite file with a symlink' '
 
        rm -fr .git &&
        git init &&
index 6a9936e5c45a973f2fd64a2fe20463497f9564eb..329c851685b1c663ee88d45a5d21d452a293fa8e 100755 (executable)
@@ -48,6 +48,22 @@ 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
 '
@@ -264,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..73dbc4360b58c95ae135577031fb8e60b3f395f7 100755 (executable)
@@ -185,8 +185,9 @@ cba
 EOF
 test_expect_success \
        'listing tags with substring as pattern must print those matching' '
-       git tag -l "*a*" > actual &&
-       test_cmp expect actual
+       rm *a* &&
+       git tag -l "*a*" > current &&
+       test_cmp expect current
 '
 
 cat >expect <<EOF
@@ -580,28 +581,38 @@ test_expect_success \
 '
 
 # subsequent tests require gpg; check if it is available
-gpg --version >/dev/null
+gpg --version >/dev/null 2>/dev/null
 if [ $? -eq 127 ]; then
-       echo "gpg not found - skipping tag signing and verification tests"
-       test_done
-       exit
+       say "gpg not found - skipping tag signing and verification tests"
+else
+       # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
+       # the gpg version 1.0.6 didn't parse trust packets correctly, so for
+       # that version, creation of signed tags using the generated key fails.
+       case "$(gpg --version)" in
+       'gpg (GnuPG) 1.0.6'*)
+               say "Skipping signed tag tests, because a bug in 1.0.6 version"
+               ;;
+       *)
+               test_set_prereq GPG
+               ;;
+       esac
 fi
 
 # trying to verify annotated non-signed tags:
 
-test_expect_success \
+test_expect_success GPG \
        'trying to verify an annotated non-signed tag should fail' '
        tag_exists annotated-tag &&
        test_must_fail git tag -v annotated-tag
 '
 
-test_expect_success \
+test_expect_success GPG \
        'trying to verify a file-annotated non-signed tag should fail' '
        tag_exists file-annotated-tag &&
        test_must_fail git tag -v file-annotated-tag
 '
 
-test_expect_success \
+test_expect_success GPG \
        'trying to verify two annotated non-signed tags should fail' '
        tag_exists annotated-tag file-annotated-tag &&
        test_must_fail git tag -v annotated-tag file-annotated-tag
@@ -609,17 +620,6 @@ test_expect_success \
 
 # creating and verifying signed tags:
 
-# As said here: http://www.gnupg.org/documentation/faqs.html#q6.19
-# the gpg version 1.0.6 didn't parse trust packets correctly, so for
-# that version, creation of signed tags using the generated key fails.
-case "$(gpg --version)" in
-'gpg (GnuPG) 1.0.6'*)
-       echo "Skipping signed tag tests, because a bug in 1.0.6 version"
-       test_done
-       exit
-       ;;
-esac
-
 # key generation info: gpg --homedir t/t7004 --gen-key
 # Type DSA and Elgamal, size 2048 bits, no expiration date.
 # Name and email: C O Mitter <committer@example.com>
@@ -633,7 +633,7 @@ export GNUPGHOME
 get_tag_header signed-tag $commit commit $time >expect
 echo 'A signed tag message' >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success 'creating a signed tag with -m message should succeed' '
+test_expect_success GPG 'creating a signed tag with -m message should succeed' '
        git tag -s -m "A signed tag message" signed-tag &&
        get_tag_msg signed-tag >actual &&
        test_cmp expect actual
@@ -642,7 +642,7 @@ test_expect_success 'creating a signed tag with -m message should succeed' '
 get_tag_header u-signed-tag $commit commit $time >expect
 echo 'Another message' >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success 'sign with a given key id' '
+test_expect_success GPG 'sign with a given key id' '
 
        git tag -u committer@example.com -m "Another message" u-signed-tag &&
        get_tag_msg u-signed-tag >actual &&
@@ -650,14 +650,14 @@ test_expect_success 'sign with a given key id' '
 
 '
 
-test_expect_success 'sign with an unknown id (1)' '
+test_expect_success GPG 'sign with an unknown id (1)' '
 
        test_must_fail git tag -u author@example.com \
                -m "Another message" o-signed-tag
 
 '
 
-test_expect_success 'sign with an unknown id (2)' '
+test_expect_success GPG 'sign with an unknown id (2)' '
 
        test_must_fail git tag -u DEADBEEF -m "Another message" o-signed-tag
 
@@ -674,7 +674,7 @@ chmod +x fakeeditor
 get_tag_header implied-sign $commit commit $time >expect
 ./fakeeditor >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success '-u implies signed tag' '
+test_expect_success GPG '-u implies signed tag' '
        GIT_EDITOR=./fakeeditor git tag -u CDDE430D implied-sign &&
        get_tag_msg implied-sign >actual &&
        test_cmp expect actual
@@ -687,7 +687,7 @@ EOF
 get_tag_header file-signed-tag $commit commit $time >expect
 cat sigmsgfile >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with -F messagefile should succeed' '
        git tag -s -F sigmsgfile file-signed-tag &&
        get_tag_msg file-signed-tag >actual &&
@@ -701,7 +701,7 @@ EOF
 get_tag_header stdin-signed-tag $commit commit $time >expect
 cat siginputmsg >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success 'creating a signed tag with -F - should succeed' '
+test_expect_success GPG 'creating a signed tag with -F - should succeed' '
        git tag -s -F - stdin-signed-tag <siginputmsg &&
        get_tag_msg stdin-signed-tag >actual &&
        test_cmp expect actual
@@ -710,13 +710,13 @@ test_expect_success 'creating a signed tag with -F - should succeed' '
 get_tag_header implied-annotate $commit commit $time >expect
 ./fakeeditor >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success '-s implies annotated tag' '
+test_expect_success GPG '-s implies annotated tag' '
        GIT_EDITOR=./fakeeditor git tag -s implied-annotate &&
        get_tag_msg implied-annotate >actual &&
        test_cmp expect actual
 '
 
-test_expect_success \
+test_expect_success GPG \
        'trying to create a signed tag with non-existing -F file should fail' '
        ! test -f nonexistingfile &&
        ! tag_exists nosigtag &&
@@ -724,13 +724,13 @@ test_expect_success \
        ! tag_exists nosigtag
 '
 
-test_expect_success 'verifying a signed tag should succeed' \
+test_expect_success GPG 'verifying a signed tag should succeed' \
        'git tag -v signed-tag'
 
-test_expect_success 'verifying two signed tags in one command should succeed' \
+test_expect_success GPG 'verifying two signed tags in one command should succeed' \
        'git tag -v signed-tag file-signed-tag'
 
-test_expect_success \
+test_expect_success GPG \
        'verifying many signed and non-signed tags should fail' '
        test_must_fail git tag -v signed-tag annotated-tag &&
        test_must_fail git tag -v file-annotated-tag file-signed-tag &&
@@ -739,7 +739,7 @@ test_expect_success \
        test_must_fail git tag -v signed-tag annotated-tag file-signed-tag
 '
 
-test_expect_success 'verifying a forged tag should fail' '
+test_expect_success GPG 'verifying a forged tag should fail' '
        forged=$(git cat-file tag signed-tag |
                sed -e "s/signed-tag/forged-tag/" |
                git mktag) &&
@@ -751,7 +751,7 @@ test_expect_success 'verifying a forged tag should fail' '
 
 get_tag_header empty-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with an empty -m message should succeed' '
        git tag -s -m "" empty-signed-tag &&
        get_tag_msg empty-signed-tag >actual &&
@@ -762,7 +762,7 @@ test_expect_success \
 >sigemptyfile
 get_tag_header emptyfile-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with an empty -F messagefile should succeed' '
        git tag -s -F sigemptyfile emptyfile-signed-tag &&
        get_tag_msg emptyfile-signed-tag >actual &&
@@ -785,7 +785,7 @@ Trailing spaces
 Trailing blank lines
 EOF
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'extra blanks in the message for a signed tag should be removed' '
        git tag -s -F sigblanksfile blanks-signed-tag &&
        get_tag_msg blanks-signed-tag >actual &&
@@ -795,7 +795,7 @@ test_expect_success \
 
 get_tag_header blank-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with a blank -m message should succeed' '
        git tag -s -m "     " blank-signed-tag &&
        get_tag_msg blank-signed-tag >actual &&
@@ -808,7 +808,7 @@ echo ''      >>sigblankfile
 echo '  '    >>sigblankfile
 get_tag_header blankfile-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with blank -F file with spaces should succeed' '
        git tag -s -F sigblankfile blankfile-signed-tag &&
        get_tag_msg blankfile-signed-tag >actual &&
@@ -819,7 +819,7 @@ test_expect_success \
 printf '      ' >sigblanknonlfile
 get_tag_header blanknonlfile-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with spaces and no newline should succeed' '
        git tag -s -F sigblanknonlfile blanknonlfile-signed-tag &&
        get_tag_msg blanknonlfile-signed-tag >actual &&
@@ -856,7 +856,7 @@ Another line.
 Last line.
 EOF
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with a -F file with #comments should succeed' '
        git tag -s -F sigcommentsfile comments-signed-tag &&
        get_tag_msg comments-signed-tag >actual &&
@@ -866,7 +866,7 @@ test_expect_success \
 
 get_tag_header comment-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with #commented -m message should succeed' '
        git tag -s -m "#comment" comment-signed-tag &&
        get_tag_msg comment-signed-tag >actual &&
@@ -879,7 +879,7 @@ echo ''         >>sigcommentfile
 echo '####'     >>sigcommentfile
 get_tag_header commentfile-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with #commented -F messagefile should succeed' '
        git tag -s -F sigcommentfile commentfile-signed-tag &&
        get_tag_msg commentfile-signed-tag >actual &&
@@ -890,7 +890,7 @@ test_expect_success \
 printf '#comment' >sigcommentnonlfile
 get_tag_header commentnonlfile-signed-tag $commit commit $time >expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag with a #comment and no newline should succeed' '
        git tag -s -F sigcommentnonlfile commentnonlfile-signed-tag &&
        get_tag_msg commentnonlfile-signed-tag >actual &&
@@ -900,7 +900,7 @@ test_expect_success \
 
 # listing messages for signed tags:
 
-test_expect_success \
+test_expect_success GPG \
        'listing the one-line message of a signed tag should succeed' '
        git tag -s -m "A message line signed" stag-one-line &&
 
@@ -925,7 +925,7 @@ test_expect_success \
        test_cmp expect actual
 '
 
-test_expect_success \
+test_expect_success GPG \
        'listing the zero-lines message of a signed tag should succeed' '
        git tag -s -m "" stag-zero-lines &&
 
@@ -953,7 +953,7 @@ test_expect_success \
 echo 'stag line one' >sigtagmsg
 echo 'stag line two' >>sigtagmsg
 echo 'stag line three' >>sigtagmsg
-test_expect_success \
+test_expect_success GPG \
        'listing many message lines of a signed tag should succeed' '
        git tag -s -F sigtagmsg stag-lines &&
 
@@ -998,12 +998,12 @@ test_expect_success \
 
 tree=$(git rev-parse HEAD^{tree})
 blob=$(git rev-parse HEAD:foo)
-tag=$(git rev-parse signed-tag)
+tag=$(git rev-parse signed-tag 2>/dev/null)
 
 get_tag_header tree-signed-tag $tree tree $time >expect
 echo "A message for a tree" >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag pointing to a tree should succeed' '
        git tag -s -m "A message for a tree" tree-signed-tag HEAD^{tree} &&
        get_tag_msg tree-signed-tag >actual &&
@@ -1013,7 +1013,7 @@ test_expect_success \
 get_tag_header blob-signed-tag $blob blob $time >expect
 echo "A message for a blob" >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag pointing to a blob should succeed' '
        git tag -s -m "A message for a blob" blob-signed-tag HEAD:foo &&
        get_tag_msg blob-signed-tag >actual &&
@@ -1023,7 +1023,7 @@ test_expect_success \
 get_tag_header tag-signed-tag $tag tag $time >expect
 echo "A message for another tag" >>expect
 echo '-----BEGIN PGP SIGNATURE-----' >>expect
-test_expect_success \
+test_expect_success GPG \
        'creating a signed tag pointing to another tag should succeed' '
        git tag -s -m "A message for another tag" tag-signed-tag signed-tag &&
        get_tag_msg tag-signed-tag >actual &&
@@ -1032,7 +1032,7 @@ test_expect_success \
 
 # try to sign with bad user.signingkey
 git config user.signingkey BobTheMouse
-test_expect_success \
+test_expect_success GPG \
        'git tag -s fails if gpg is misconfigured' \
        'test_must_fail git tag -s -m tail tag-gpg-failure'
 git config --unset user.signingkey
@@ -1040,7 +1040,7 @@ git config --unset user.signingkey
 # try to verify without gpg:
 
 rm -rf gpghome
-test_expect_success \
+test_expect_success GPG \
        'verify signed tag fails when public key is not present' \
        'test_must_fail git tag -v signed-tag'
 
@@ -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 2d919d69ef110408b820c76185d6b8da63ea183e..b647957d75fa0d0ce4d88c7c3c7243f31af38b4a 100755 (executable)
@@ -7,6 +7,7 @@ test_description='GIT_EDITOR, core.editor, and stuff'
 for i in GIT_EDITOR core_editor EDITOR VISUAL vi
 do
        cat >e-$i.sh <<-EOF
+       #!$SHELL_PATH
        echo "Edited by $i" >"\$1"
        EOF
        chmod +x e-$i.sh
@@ -87,30 +88,26 @@ do
        '
 done
 
+if ! echo 'echo space > "$1"' > "e space.sh"
+then
+       say "Skipping; FS does not support spaces in filenames"
+       test_done
+fi
+
 test_expect_success 'editor with a space' '
 
-       if echo "echo space > \"\$1\"" > "e space.sh"
-       then
-               chmod a+x "e space.sh" &&
-               GIT_EDITOR="./e\ space.sh" git commit --amend &&
-               test space = "$(git show -s --pretty=format:%s)"
-       else
-               say "Skipping; FS does not support spaces in filenames"
-       fi
+       chmod a+x "e space.sh" &&
+       GIT_EDITOR="./e\ space.sh" git commit --amend &&
+       test space = "$(git show -s --pretty=format:%s)"
 
 '
 
 unset GIT_EDITOR
 test_expect_success 'core.editor with a space' '
 
-       if test -f "e space.sh"
-       then
-               git config core.editor \"./e\ space.sh\" &&
-               git commit --amend &&
-               test space = "$(git show -s --pretty=format:%s)"
-       else
-               say "Skipping; FS does not support spaces in filenames"
-       fi
+       git config core.editor \"./e\ space.sh\" &&
+       git commit --amend &&
+       test space = "$(git show -s --pretty=format:%s)"
 
 '
 
index 0e21632f19e75e1e7bbca1ceb2b9402a4d291584..ebfd34df36068f8808406a98d371731fb85012c4 100755 (executable)
@@ -171,7 +171,7 @@ test_expect_success 'checkout to detach HEAD' '
        git checkout -f renamer && git clean -f &&
        git checkout renamer^ 2>messages &&
        (cat >messages.expect <<EOF
-Note: moving to "renamer^" which isn'"'"'t a local branch
+Note: moving to '\''renamer^'\'' which isn'\''t a local branch
 If you want to create a new branch from this checkout, you may do so
 (now or later) by using -b with the checkout command again. Example:
   git checkout -b <new_branch_name>
@@ -534,4 +534,12 @@ test_expect_success 'failing checkout -b should not break working tree' '
 
 '
 
+test_expect_success 'switch out of non-branch' '
+       git reset --hard master &&
+       git checkout master^0 &&
+       echo modified >one &&
+       test_must_fail git checkout renamer 2>error.log &&
+       ! grep "^Previous HEAD" error.log
+'
+
 test_done
index 1636fac2a43e30a99674df98ff4544ee04612cc5..929d5d4d3b9d55f570cef1617a0716b17265c988 100755 (executable)
@@ -373,9 +373,9 @@ test_expect_success 'removal failure' '
 
        mkdir foo &&
        touch foo/bar &&
-       exec <foo/bar &&
-       chmod 0 foo &&
-       test_must_fail git clean -f -d
+       (exec <foo/bar &&
+        chmod 0 foo &&
+        test_must_fail git clean -f -d)
 
 '
 chmod 755 foo
index 2ec7ac6a510c5b83bc1ee6ce428379beb7a8b5ef..0f2ccc6cf0123951d9bdbb880931868f29de5b4e 100755 (executable)
@@ -47,6 +47,65 @@ test_expect_success 'Prepare submodule testing' '
        GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/init.git
 '
 
+test_expect_success 'Prepare submodule add testing' '
+       submodurl=$(pwd)
+       (
+               mkdir addtest &&
+               cd addtest &&
+               git init
+       )
+'
+
+test_expect_success 'submodule add' '
+       (
+               cd addtest &&
+               git submodule add "$submodurl" submod &&
+               git submodule init
+       )
+'
+
+test_expect_success 'submodule add --branch' '
+       (
+               cd addtest &&
+               git submodule add -b initial "$submodurl" submod-branch &&
+               git submodule init &&
+               cd submod-branch &&
+               git branch | grep initial
+       )
+'
+
+test_expect_success 'submodule add with ./ in path' '
+       (
+               cd addtest &&
+               git submodule add "$submodurl" ././dotsubmod/./frotz/./ &&
+               git submodule init
+       )
+'
+
+test_expect_success 'submodule add with // in path' '
+       (
+               cd addtest &&
+               git submodule add "$submodurl" slashslashsubmod///frotz// &&
+               git submodule init
+       )
+'
+
+test_expect_success 'submodule add with /.. in path' '
+       (
+               cd addtest &&
+               git submodule add "$submodurl" dotdotsubmod/../realsubmod/frotz/.. &&
+               git submodule init
+       )
+'
+
+test_expect_success 'submodule add with ./, /.. and // in path' '
+       (
+               cd addtest &&
+               git submodule add "$submodurl" dot/dotslashsubmod/./../..////realsubmod2/a/b/c/d/../../../../frotz//.. &&
+               git submodule init
+       )
+'
+
 test_expect_success 'status should fail for unmapped paths' '
        if git submodule status
        then
@@ -234,4 +293,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
diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh
new file mode 100755 (executable)
index 0000000..9a21f78
--- /dev/null
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+test_description='merging with submodules'
+
+. ./test-lib.sh
+
+#
+# history
+#
+#        a --- c
+#      /   \ /
+# root      X
+#      \   / \
+#        b --- d
+#
+
+test_expect_success setup '
+
+       mkdir sub &&
+       (cd sub &&
+        git init &&
+        echo original > file &&
+        git add file &&
+        test_tick &&
+        git commit -m sub-root) &&
+       git add sub &&
+       test_tick &&
+       git commit -m root &&
+
+       git checkout -b a master &&
+       (cd sub &&
+        echo A > file &&
+        git add file &&
+        test_tick &&
+        git commit -m sub-a) &&
+       git add sub &&
+       test_tick &&
+       git commit -m a &&
+
+       git checkout -b b master &&
+       (cd sub &&
+        echo B > file &&
+        git add file &&
+        test_tick &&
+        git commit -m sub-b) &&
+       git add sub &&
+       test_tick &&
+       git commit -m b
+
+       git checkout -b c a &&
+       git merge -s ours b &&
+
+       git checkout -b d b &&
+       git merge -s ours a
+'
+
+test_expect_success 'merging with modify/modify conflict' '
+
+       git checkout -b test1 a &&
+       test_must_fail git merge b &&
+       test -f .git/MERGE_MSG &&
+       git diff &&
+       test -n "$(git ls-files -u)"
+'
+
+test_expect_success 'merging with a modify/modify conflict between merge bases' '
+
+       git reset --hard HEAD &&
+       git checkout -b test2 c &&
+       git merge d
+
+'
+
+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..e2ef53228ecc6162afdcce78109d426e154b7caa 100755 (executable)
@@ -38,7 +38,7 @@ test_expect_success \
        "echo King of the bongo >file &&
        test_must_fail git commit -m foo -a file"
 
-test_expect_success \
+test_expect_success PERL \
        "using paths with --interactive" \
        "echo bong-o-bong >file &&
        ! (echo 7 | git commit -m foo --interactive file)"
@@ -119,7 +119,7 @@ test_expect_success \
        "echo 'gak' >file && \
         git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
 
-test_expect_success \
+test_expect_success PERL \
        "interactive add" \
        "echo 7 | git commit --interactive | grep 'What now'"
 
@@ -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 ad42c78d7c21497a6ebb4e9cef4efd6334f947da..56cd866019dcc871e5dea684f1d879fcf2c1b884 100755 (executable)
@@ -234,7 +234,7 @@ cat >.git/FAKE_EDITOR <<EOF
 # kill -TERM command added below.
 EOF
 
-test_expect_success 'a SIGTERM should break locks' '
+test_expect_success EXECKEEPSPID 'a SIGTERM should break locks' '
        echo >>negative &&
        ! "$SHELL_PATH" -c '\''
          echo kill -TERM $$ >> .git/FAKE_EDITOR
diff --git a/t/t7502-status.sh b/t/t7502-status.sh
deleted file mode 100755 (executable)
index 93f875f..0000000
+++ /dev/null
@@ -1,400 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2007 Johannes E. Schindelin
-#
-
-test_description='git status'
-
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-       : > tracked &&
-       : > modified &&
-       mkdir dir1 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
-       mkdir dir2 &&
-       : > dir1/tracked &&
-       : > dir1/modified &&
-       git add . &&
-
-       git status >output &&
-
-       test_tick &&
-       git commit -m initial &&
-       : > untracked &&
-       : > dir1/untracked &&
-       : > dir2/untracked &&
-       echo 1 > dir1/modified &&
-       echo 2 > dir2/modified &&
-       echo 3 > dir2/added &&
-       git add dir2/added
-'
-
-test_expect_success 'status (1)' '
-
-       grep "use \"git rm --cached <file>\.\.\.\" to unstage" output
-
-'
-
-cat > expect << \EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   dir2/added
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      expect
-#      output
-#      untracked
-EOF
-
-test_expect_success 'status (2)' '
-
-       git status > output &&
-       test_cmp expect output
-
-'
-
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   dir2/added
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Untracked files not listed (use -u option to show untracked files)
-EOF
-test_expect_success 'status -uno' '
-       mkdir dir3 &&
-       : > dir3/untracked1 &&
-       : > dir3/untracked2 &&
-       git status -uno >output &&
-       test_cmp expect output
-'
-
-test_expect_success 'status (status.showUntrackedFiles no)' '
-       git config status.showuntrackedfiles no
-       git status >output &&
-       test_cmp expect output
-'
-
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   dir2/added
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      dir3/
-#      expect
-#      output
-#      untracked
-EOF
-test_expect_success 'status -unormal' '
-       git status -unormal >output &&
-       test_cmp expect output
-'
-
-test_expect_success 'status (status.showUntrackedFiles normal)' '
-       git config status.showuntrackedfiles normal
-       git status >output &&
-       test_cmp expect output
-'
-
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   dir2/added
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      dir3/untracked1
-#      dir3/untracked2
-#      expect
-#      output
-#      untracked
-EOF
-test_expect_success 'status -uall' '
-       git status -uall >output &&
-       test_cmp expect output
-'
-test_expect_success 'status (status.showUntrackedFiles all)' '
-       git config status.showuntrackedfiles all
-       git status >output &&
-       rm -rf dir3 &&
-       git config --unset status.showuntrackedfiles &&
-       test_cmp expect output
-'
-
-cat > expect << \EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   ../dir2/added
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      untracked
-#      ../dir2/modified
-#      ../dir2/untracked
-#      ../expect
-#      ../output
-#      ../untracked
-EOF
-
-test_expect_success 'status with relative paths' '
-
-       (cd dir1 && git status) > output &&
-       test_cmp expect output
-
-'
-
-cat > expect << \EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   dir2/added
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      expect
-#      output
-#      untracked
-EOF
-
-test_expect_success 'status without relative paths' '
-
-       git config status.relativePaths false
-       (cd dir1 && git status) > output &&
-       test_cmp expect output
-
-'
-
-cat <<EOF >expect
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      modified:   dir1/modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/
-#      expect
-#      output
-#      untracked
-EOF
-test_expect_success 'status of partial commit excluding new file in index' '
-       git status dir1/modified >output &&
-       test_cmp expect output
-'
-
-test_expect_success 'setup status submodule summary' '
-       test_create_repo sm && (
-               cd sm &&
-               >foo &&
-               git add foo &&
-               git commit -m "Add foo"
-       ) &&
-       git add sm
-'
-
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   dir2/added
-#      new file:   sm
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      expect
-#      output
-#      untracked
-EOF
-test_expect_success 'status submodule summary is disabled by default' '
-       git status >output &&
-       test_cmp expect output
-'
-
-# we expect the same as the previous test
-test_expect_success 'status --untracked-files=all does not show submodule' '
-       git status --untracked-files=all >output &&
-       test_cmp expect output
-'
-
-head=$(cd sm && git rev-parse --short=7 --verify HEAD)
-
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD <file>..." to unstage)
-#
-#      new file:   dir2/added
-#      new file:   sm
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Modified submodules:
-#
-# * sm 0000000...$head (1):
-#   > Add foo
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      expect
-#      output
-#      untracked
-EOF
-test_expect_success 'status submodule summary' '
-       git config status.submodulesummary 10 &&
-       git status >output &&
-       test_cmp expect output
-'
-
-
-cat >expect <<EOF
-# On branch master
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      expect
-#      output
-#      untracked
-no changes added to commit (use "git add" and/or "git commit -a")
-EOF
-test_expect_success 'status submodule summary (clean submodule)' '
-       git commit -m "commit submodule" &&
-       git config status.submodulesummary 10 &&
-       test_must_fail git status >output &&
-       test_cmp expect output
-'
-
-cat >expect <<EOF
-# On branch master
-# Changes to be committed:
-#   (use "git reset HEAD^1 <file>..." to unstage)
-#
-#      new file:   dir2/added
-#      new file:   sm
-#
-# Changed but not updated:
-#   (use "git add <file>..." to update what will be committed)
-#   (use "git checkout -- <file>..." to discard changes in working directory)
-#
-#      modified:   dir1/modified
-#
-# Modified submodules:
-#
-# * sm 0000000...$head (1):
-#   > Add foo
-#
-# Untracked files:
-#   (use "git add <file>..." to include in what will be committed)
-#
-#      dir1/untracked
-#      dir2/modified
-#      dir2/untracked
-#      expect
-#      output
-#      untracked
-EOF
-test_expect_success 'status submodule summary (--amend)' '
-       git config status.submodulesummary 10 &&
-       git status --amend >output &&
-       test_cmp expect output
-'
-
-test_done
index b06909599564a1c8afa027b0f9c71ef6bb61d6e4..8528f64c8d1491fd3c279f030b6f8aee2050cdf7 100755 (executable)
@@ -69,7 +69,7 @@ test_expect_success '--no-verify with failing hook' '
 '
 
 chmod -x "$HOOK"
-test_expect_success 'with non-executable hook' '
+test_expect_success POSIXPERM 'with non-executable hook' '
 
        echo "content" >> file &&
        git add file &&
@@ -77,7 +77,7 @@ test_expect_success 'with non-executable hook' '
 
 '
 
-test_expect_success '--no-verify with non-executable hook' '
+test_expect_success POSIXPERM '--no-verify with non-executable hook' '
 
        echo "more content" >> file &&
        git add file &&
index 47680e6df41c2bc14f23514b010a8aefb3fedcd7..1f53ea8090355c9a351da1983388e1a49fd88ae3 100755 (executable)
@@ -136,7 +136,7 @@ test_expect_success '--no-verify with failing hook (editor)' '
 '
 
 chmod -x "$HOOK"
-test_expect_success 'with non-executable hook' '
+test_expect_success POSIXPERM 'with non-executable hook' '
 
        echo "content" >> file &&
        git add file &&
@@ -144,7 +144,7 @@ test_expect_success 'with non-executable hook' '
 
 '
 
-test_expect_success 'with non-executable hook (editor)' '
+test_expect_success POSIXPERM 'with non-executable hook (editor)' '
 
        echo "content again" >> file &&
        git add file &&
@@ -153,7 +153,7 @@ test_expect_success 'with non-executable hook (editor)' '
 
 '
 
-test_expect_success '--no-verify with non-executable hook' '
+test_expect_success POSIXPERM '--no-verify with non-executable hook' '
 
        echo "more content" >> file &&
        git add file &&
@@ -161,7 +161,7 @@ test_expect_success '--no-verify with non-executable hook' '
 
 '
 
-test_expect_success '--no-verify with non-executable hook (editor)' '
+test_expect_success POSIXPERM '--no-verify with non-executable hook (editor)' '
 
        echo "even more content" >> file &&
        git add file &&
diff --git a/t/t7508-status.sh b/t/t7508-status.sh
new file mode 100755 (executable)
index 0000000..93f875f
--- /dev/null
@@ -0,0 +1,400 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git status'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       : > tracked &&
+       : > modified &&
+       mkdir dir1 &&
+       : > dir1/tracked &&
+       : > dir1/modified &&
+       mkdir dir2 &&
+       : > dir1/tracked &&
+       : > dir1/modified &&
+       git add . &&
+
+       git status >output &&
+
+       test_tick &&
+       git commit -m initial &&
+       : > untracked &&
+       : > dir1/untracked &&
+       : > dir2/untracked &&
+       echo 1 > dir1/modified &&
+       echo 2 > dir2/modified &&
+       echo 3 > dir2/added &&
+       git add dir2/added
+'
+
+test_expect_success 'status (1)' '
+
+       grep "use \"git rm --cached <file>\.\.\.\" to unstage" output
+
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+
+test_expect_success 'status (2)' '
+
+       git status > output &&
+       test_cmp expect output
+
+'
+
+cat >expect <<EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files not listed (use -u option to show untracked files)
+EOF
+test_expect_success 'status -uno' '
+       mkdir dir3 &&
+       : > dir3/untracked1 &&
+       : > dir3/untracked2 &&
+       git status -uno >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status (status.showUntrackedFiles no)' '
+       git config status.showuntrackedfiles no
+       git status >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      dir3/
+#      expect
+#      output
+#      untracked
+EOF
+test_expect_success 'status -unormal' '
+       git status -unormal >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'status (status.showUntrackedFiles normal)' '
+       git config status.showuntrackedfiles normal
+       git status >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      dir3/untracked1
+#      dir3/untracked2
+#      expect
+#      output
+#      untracked
+EOF
+test_expect_success 'status -uall' '
+       git status -uall >output &&
+       test_cmp expect output
+'
+test_expect_success 'status (status.showUntrackedFiles all)' '
+       git config status.showuntrackedfiles all
+       git status >output &&
+       rm -rf dir3 &&
+       git config --unset status.showuntrackedfiles &&
+       test_cmp expect output
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   ../dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      untracked
+#      ../dir2/modified
+#      ../dir2/untracked
+#      ../expect
+#      ../output
+#      ../untracked
+EOF
+
+test_expect_success 'status with relative paths' '
+
+       (cd dir1 && git status) > output &&
+       test_cmp expect output
+
+'
+
+cat > expect << \EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+
+test_expect_success 'status without relative paths' '
+
+       git config status.relativePaths false
+       (cd dir1 && git status) > output &&
+       test_cmp expect output
+
+'
+
+cat <<EOF >expect
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/
+#      expect
+#      output
+#      untracked
+EOF
+test_expect_success 'status of partial commit excluding new file in index' '
+       git status dir1/modified >output &&
+       test_cmp expect output
+'
+
+test_expect_success 'setup status submodule summary' '
+       test_create_repo sm && (
+               cd sm &&
+               >foo &&
+               git add foo &&
+               git commit -m "Add foo"
+       ) &&
+       git add sm
+'
+
+cat >expect <<EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#      new file:   sm
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+test_expect_success 'status submodule summary is disabled by default' '
+       git status >output &&
+       test_cmp expect output
+'
+
+# we expect the same as the previous test
+test_expect_success 'status --untracked-files=all does not show submodule' '
+       git status --untracked-files=all >output &&
+       test_cmp expect output
+'
+
+head=$(cd sm && git rev-parse --short=7 --verify HEAD)
+
+cat >expect <<EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
+#
+#      new file:   dir2/added
+#      new file:   sm
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Modified submodules:
+#
+# * sm 0000000...$head (1):
+#   > Add foo
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+test_expect_success 'status submodule summary' '
+       git config status.submodulesummary 10 &&
+       git status >output &&
+       test_cmp expect output
+'
+
+
+cat >expect <<EOF
+# On branch master
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+no changes added to commit (use "git add" and/or "git commit -a")
+EOF
+test_expect_success 'status submodule summary (clean submodule)' '
+       git commit -m "commit submodule" &&
+       git config status.submodulesummary 10 &&
+       test_must_fail git status >output &&
+       test_cmp expect output
+'
+
+cat >expect <<EOF
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD^1 <file>..." to unstage)
+#
+#      new file:   dir2/added
+#      new file:   sm
+#
+# Changed but not updated:
+#   (use "git add <file>..." to update what will be committed)
+#   (use "git checkout -- <file>..." to discard changes in working directory)
+#
+#      modified:   dir1/modified
+#
+# Modified submodules:
+#
+# * sm 0000000...$head (1):
+#   > Add foo
+#
+# Untracked files:
+#   (use "git add <file>..." to include in what will be committed)
+#
+#      dir1/untracked
+#      dir2/modified
+#      dir2/untracked
+#      expect
+#      output
+#      untracked
+EOF
+test_expect_success 'status submodule summary (--amend)' '
+       git config status.submodulesummary 10 &&
+       git status --amend >output &&
+       test_cmp expect output
+'
+
+test_done
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 9ce546e3b225563279c54eb6ceafd87398a3e5cc..87c9b0e121db6c8ab7074e6c4968cd06d37851fe 100755 (executable)
@@ -69,6 +69,25 @@ test_expect_success 'packed obs in alt ODB are repacked even when local repo is
        done
 '
 
+test_expect_success '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_expect_success 'packed obs in alternate ODB kept pack are repacked' '
        # swap the .keep so the commit object is in the pack with .keep
        for p in alt_objects/pack/*.pack
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
diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh
new file mode 100755 (executable)
index 0000000..ebdccf9
--- /dev/null
@@ -0,0 +1,216 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 David Aguilar
+#
+
+test_description='git-difftool
+
+Testing basic diff tool invocation
+'
+
+. ./test-lib.sh
+
+if ! test_have_prereq PERL; then
+       say 'skipping difftool tests, perl not available'
+       test_done
+fi
+
+remove_config_vars()
+{
+       # Unset all config variables used by git-difftool
+       git config --unset diff.tool
+       git config --unset difftool.test-tool.cmd
+       git config --unset difftool.prompt
+       git config --unset merge.tool
+       git config --unset mergetool.test-tool.cmd
+       return 0
+}
+
+restore_test_defaults()
+{
+       # Restores the test defaults used by several tests
+       remove_config_vars
+       unset GIT_DIFF_TOOL
+       unset GIT_MERGE_TOOL
+       unset GIT_DIFFTOOL_PROMPT
+       unset GIT_DIFFTOOL_NO_PROMPT
+       git config diff.tool test-tool &&
+       git config difftool.test-tool.cmd 'cat $LOCAL'
+}
+
+prompt_given()
+{
+       prompt="$1"
+       test "$prompt" = "Hit return to launch 'test-tool': branch"
+}
+
+# Create a file on master and change it on branch
+test_expect_success 'setup' '
+       echo master >file &&
+       git add file &&
+       git commit -m "added file" &&
+
+       git checkout -b branch master &&
+       echo branch >file &&
+       git commit -a -m "branch changed file" &&
+       git checkout master
+'
+
+# Configure a custom difftool.<tool>.cmd and use it
+test_expect_success 'custom commands' '
+       restore_test_defaults &&
+       git config difftool.test-tool.cmd "cat \$REMOTE" &&
+
+       diff=$(git difftool --no-prompt branch) &&
+       test "$diff" = "master" &&
+
+       restore_test_defaults &&
+       diff=$(git difftool --no-prompt branch) &&
+       test "$diff" = "branch"
+'
+
+# Ensures that git-difftool ignores bogus --tool values
+test_expect_success 'difftool ignores bad --tool values' '
+       diff=$(git difftool --no-prompt --tool=bogus-tool branch)
+       test "$?" = 1 &&
+       test "$diff" = ""
+'
+
+# Specify the diff tool using $GIT_DIFF_TOOL
+test_expect_success 'GIT_DIFF_TOOL variable' '
+       git config --unset diff.tool
+       GIT_DIFF_TOOL=test-tool &&
+       export GIT_DIFF_TOOL &&
+
+       diff=$(git difftool --no-prompt branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
+# Test the $GIT_*_TOOL variables and ensure
+# that $GIT_DIFF_TOOL always wins unless --tool is specified
+test_expect_success 'GIT_DIFF_TOOL overrides' '
+       git config diff.tool bogus-tool &&
+       git config merge.tool bogus-tool &&
+
+       GIT_MERGE_TOOL=test-tool &&
+       export GIT_MERGE_TOOL &&
+       diff=$(git difftool --no-prompt branch) &&
+       test "$diff" = "branch" &&
+       unset GIT_MERGE_TOOL &&
+
+       GIT_MERGE_TOOL=bogus-tool &&
+       GIT_DIFF_TOOL=test-tool &&
+       export GIT_MERGE_TOOL &&
+       export GIT_DIFF_TOOL &&
+
+       diff=$(git difftool --no-prompt branch) &&
+       test "$diff" = "branch" &&
+
+       GIT_DIFF_TOOL=bogus-tool &&
+       export GIT_DIFF_TOOL &&
+
+       diff=$(git difftool --no-prompt --tool=test-tool branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
+# Test that we don't have to pass --no-prompt to difftool
+# when $GIT_DIFFTOOL_NO_PROMPT is true
+test_expect_success 'GIT_DIFFTOOL_NO_PROMPT variable' '
+       GIT_DIFFTOOL_NO_PROMPT=true &&
+       export GIT_DIFFTOOL_NO_PROMPT &&
+
+       diff=$(git difftool branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
+# git-difftool supports the difftool.prompt variable.
+# Test that GIT_DIFFTOOL_PROMPT can override difftool.prompt = false
+test_expect_success 'GIT_DIFFTOOL_PROMPT variable' '
+       git config difftool.prompt false &&
+       GIT_DIFFTOOL_PROMPT=true &&
+       export GIT_DIFFTOOL_PROMPT &&
+
+       prompt=$(echo | git difftool --prompt branch | tail -1) &&
+       prompt_given "$prompt" &&
+
+       restore_test_defaults
+'
+
+# Test that we don't have to pass --no-prompt when difftool.prompt is false
+test_expect_success 'difftool.prompt config variable is false' '
+       git config difftool.prompt false &&
+
+       diff=$(git difftool branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
+# Test that the -y flag can override difftool.prompt = true
+test_expect_success 'difftool.prompt can overridden with -y' '
+       git config difftool.prompt true &&
+
+       diff=$(git difftool -y branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
+# Test that the --prompt flag can override difftool.prompt = false
+test_expect_success 'difftool.prompt can overridden with --prompt' '
+       git config difftool.prompt false &&
+
+       prompt=$(echo | git difftool --prompt branch | tail -1) &&
+       prompt_given "$prompt" &&
+
+       restore_test_defaults
+'
+
+# Test that the last flag passed on the command-line wins
+test_expect_success 'difftool last flag wins' '
+       diff=$(git difftool --prompt --no-prompt branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults &&
+
+       prompt=$(echo | git difftool --no-prompt --prompt branch | tail -1) &&
+       prompt_given "$prompt" &&
+
+       restore_test_defaults
+'
+
+# git-difftool falls back to git-mergetool config variables
+# so test that behavior here
+test_expect_success 'difftool + mergetool config variables' '
+       remove_config_vars
+       git config merge.tool test-tool &&
+       git config mergetool.test-tool.cmd "cat \$LOCAL" &&
+
+       diff=$(git difftool --no-prompt branch) &&
+       test "$diff" = "branch" &&
+
+       # set merge.tool to something bogus, diff.tool to test-tool
+       git config merge.tool bogus-tool &&
+       git config diff.tool test-tool &&
+
+       diff=$(git difftool --no-prompt branch) &&
+       test "$diff" = "branch" &&
+
+       restore_test_defaults
+'
+
+test_expect_success 'difftool.<tool>.path' '
+       git config difftool.tkdiff.path echo &&
+       diff=$(git difftool --tool=tkdiff --no-prompt branch) &&
+       git config --unset difftool.tkdiff.path &&
+       lines=$(echo "$diff" | grep file | wc -l) &&
+       test "$lines" -eq 1
+'
+
+test_done
index 4470a92bb235420a2a0123eca5b57e06039ff011..fcd5c266754a2b5de3dbbf94c8ef25aebff061f2 100755 (executable)
@@ -36,7 +36,7 @@ EOF
 test_expect_success \
        'blame respects i18n.commitencoding' '
        git blame --incremental file | \
-               grep "^\(author\|summary\) " > actual &&
+               egrep "^(author|summary) " > actual &&
        test_cmp actual expected
 '
 
@@ -53,7 +53,7 @@ test_expect_success \
        'blame respects i18n.logoutputencoding' '
        git config i18n.logoutputencoding cp1251 &&
        git blame --incremental file | \
-               grep "^\(author\|summary\) " > actual &&
+               egrep "^(author|summary) " > actual &&
        test_cmp actual expected
 '
 
@@ -69,7 +69,7 @@ EOF
 test_expect_success \
        'blame respects --encoding=utf-8' '
        git blame --incremental --encoding=utf-8 file | \
-               grep "^\(author\|summary\) " > actual &&
+               egrep "^(author|summary) " > actual &&
        test_cmp actual expected
 '
 
@@ -85,7 +85,7 @@ EOF
 test_expect_success \
        'blame respects --encoding=none' '
        git blame --incremental --encoding=none file | \
-               grep "^\(author\|summary\) " > actual &&
+               egrep "^(author|summary) " > actual &&
        test_cmp actual expected
 '
 
index cb3d1837709fbce30fc508e339ce671c55eb9a1b..ce26ea4ac53a4608f6f8234b67868a9cc85be7f8 100755 (executable)
@@ -3,6 +3,11 @@
 test_description='git send-email'
 . ./test-lib.sh
 
+if ! test_have_prereq PERL; then
+       say 'skipping git send-email tests, perl not available'
+       test_done
+fi
+
 PROG='git send-email'
 test_expect_success \
     'prepare reference tree' \
@@ -32,31 +37,76 @@ clean_fake_sendmail() {
 }
 
 test_expect_success 'Extract patches' '
-    patches=`git format-patch -n HEAD^1`
+    patches=`git format-patch -s --cc="One <one@example.com>" --cc=two@example.com -n HEAD^1`
 '
 
+# Test no confirm early to ensure remaining tests will not hang
+test_no_confirm () {
+       rm -f no_confirm_okay
+       echo n | \
+               GIT_SEND_EMAIL_NOTTY=1 \
+               git send-email \
+               --from="Example <from@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $@ \
+               $patches > stdout &&
+               test_must_fail grep "Send this email" stdout &&
+               > no_confirm_okay
+}
+
+# Exit immediately to prevent hang if a no-confirm test fails
+check_no_confirm () {
+       test -f no_confirm_okay || {
+               say 'No confirm test failed; skipping remaining tests to prevent hanging'
+               test_done
+       }
+}
+
+test_expect_success 'No confirm with --suppress-cc' '
+       test_no_confirm --suppress-cc=sob
+'
+check_no_confirm
+
+test_expect_success 'No confirm with --confirm=never' '
+       test_no_confirm --confirm=never
+'
+check_no_confirm
+
+# leave sendemail.confirm set to never after this so that none of the
+# remaining tests prompt unintentionally.
+test_expect_success 'No confirm with sendemail.confirm=never' '
+       git config sendemail.confirm never &&
+       test_no_confirm --compose --subject=foo
+'
+check_no_confirm
+
 test_expect_success 'Send patches' '
-     git send-email --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+     git send-email --suppress-cc=sob --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
 '
 
 cat >expected <<\EOF
 !nobody@example.com!
 !author@example.com!
+!one@example.com!
+!two@example.com!
 EOF
 test_expect_success \
     'Verify commandline' \
-    'diff commandline1 expected'
+    'test_cmp expected commandline1'
 
 cat >expected-show-all-headers <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<bcc@example.com>
+RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<bcc@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: cc@example.com, A <author@example.com>
+Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -70,6 +120,7 @@ EOF
 test_expect_success 'Show all headers' '
        git send-email \
                --dry-run \
+               --suppress-cc=sob \
                --from="Example <from@example.com>" \
                --to=to@example.com \
                --cc=cc@example.com \
@@ -84,6 +135,19 @@ test_expect_success 'Show all headers' '
        test_cmp expected-show-all-headers actual-show-all-headers
 '
 
+test_expect_success 'Prompting works' '
+       clean_fake_sendmail &&
+       (echo "Example <from@example.com>"
+        echo "to@example.com"
+        echo ""
+       ) | GIT_SEND_EMAIL_NOTTY=1 git send-email \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $patches \
+               2>errors &&
+               grep "^From: Example <from@example.com>$" msgtxt1 &&
+               grep "^To: to@example.com$" msgtxt1
+'
+
 z8=zzzzzzzz
 z64=$z8$z8$z8$z8$z8$z8$z8$z8
 z512=$z64$z64$z64$z64$z64$z64$z64$z64
@@ -104,6 +168,28 @@ test_expect_success 'no patch was sent' '
        ! test -e commandline1
 '
 
+test_expect_success 'Author From: in message body' '
+       clean_fake_sendmail &&
+       git send-email \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $patches &&
+       sed "1,/^$/d" < msgtxt1 > msgbody1
+       grep "From: A <author@example.com>" msgbody1
+'
+
+test_expect_success 'Author From: not in message body' '
+       clean_fake_sendmail &&
+       git send-email \
+               --from="A <author@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $patches &&
+       sed "1,/^$/d" < msgtxt1 > msgbody1
+       ! grep "From: A <author@example.com>" msgbody1
+'
+
 test_expect_success 'allow long lines with --no-validate' '
        git send-email \
                --from="Example <nobody@example.com>" \
@@ -148,15 +234,13 @@ test_set_editor "$(pwd)/fake-editor"
 
 test_expect_success '--compose works' '
        clean_fake_sendmail &&
-       echo y | \
-               GIT_SEND_EMAIL_NOTTY=1 \
-               git send-email \
-               --compose --subject foo \
-               --from="Example <nobody@example.com>" \
-               --to=nobody@example.com \
-               --smtp-server="$(pwd)/fake.sendmail" \
-               $patches \
-               2>errors
+       git send-email \
+       --compose --subject foo \
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       --smtp-server="$(pwd)/fake.sendmail" \
+       $patches \
+       2>errors
 '
 
 test_expect_success 'first message is compose text' '
@@ -167,16 +251,18 @@ test_expect_success 'second message is patch' '
        grep "Subject:.*Second" msgtxt2
 '
 
-cat >expected-show-all-headers <<\EOF
+cat >expected-suppress-sob <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>
+RCPT TO:<to@example.com>,<cc@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: cc@example.com, A <author@example.com>
+Cc: cc@example.com, A <author@example.com>, One <one@example.com>, two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -185,10 +271,10 @@ X-Mailer: X-MAILER-STRING
 Result: OK
 EOF
 
-test_expect_success 'sendemail.cc set' '
-       git config sendemail.cc cc@example.com &&
+test_suppression () {
        git send-email \
                --dry-run \
+               --suppress-cc=$1 \
                --from="Example <from@example.com>" \
                --to=to@example.com \
                --smtp-server relay.example.com \
@@ -196,20 +282,27 @@ test_expect_success 'sendemail.cc set' '
        sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
                -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
                -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
-               >actual-show-all-headers &&
-       test_cmp expected-show-all-headers actual-show-all-headers
+               >actual-suppress-$1 &&
+       test_cmp expected-suppress-$1 actual-suppress-$1
+}
+
+test_expect_success 'sendemail.cc set' '
+       git config sendemail.cc cc@example.com &&
+       test_suppression sob
 '
 
-cat >expected-show-all-headers <<\EOF
+cat >expected-suppress-sob <<\EOF
 0001-Second.patch
 (mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
 Dry-OK. Log says:
 Server: relay.example.com
 MAIL FROM:<from@example.com>
-RCPT TO:<to@example.com>,<author@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
 From: Example <from@example.com>
 To: to@example.com
-Cc: A <author@example.com>
+Cc: A <author@example.com>, One <one@example.com>, two@example.com
 Subject: [PATCH 1/1] Second.
 Date: DATE-STRING
 Message-Id: MESSAGE-ID-STRING
@@ -220,17 +313,227 @@ EOF
 
 test_expect_success 'sendemail.cc unset' '
        git config --unset sendemail.cc &&
+       test_suppression sob
+'
+
+cat >expected-suppress-all <<\EOF
+0001-Second.patch
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=all' '
+       test_suppression all
+'
+
+cat >expected-suppress-body <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, One <one@example.com>, two@example.com
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=body' '
+       test_suppression body
+'
+
+cat >expected-suppress-sob <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, One <one@example.com>, two@example.com
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=sob' '
+       test_suppression sob
+'
+
+cat >expected-suppress-bodycc <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(mbox) Adding cc: One <one@example.com> from line 'Cc: One <one@example.com>, two@example.com'
+(mbox) Adding cc: two@example.com from line 'Cc: One <one@example.com>, two@example.com'
+(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<one@example.com>,<two@example.com>,<committer@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, One <one@example.com>, two@example.com, C O Mitter <committer@example.com>
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=bodycc' '
+       test_suppression bodycc
+'
+
+cat >expected-suppress-cc <<\EOF
+0001-Second.patch
+(mbox) Adding cc: A <author@example.com> from line 'From: A <author@example.com>'
+(body) Adding cc: C O Mitter <committer@example.com> from line 'Signed-off-by: C O Mitter <committer@example.com>'
+Dry-OK. Log says:
+Server: relay.example.com
+MAIL FROM:<from@example.com>
+RCPT TO:<to@example.com>,<author@example.com>,<committer@example.com>
+From: Example <from@example.com>
+To: to@example.com
+Cc: A <author@example.com>, C O Mitter <committer@example.com>
+Subject: [PATCH 1/1] Second.
+Date: DATE-STRING
+Message-Id: MESSAGE-ID-STRING
+X-Mailer: X-MAILER-STRING
+
+Result: OK
+EOF
+
+test_expect_success '--suppress-cc=cc' '
+       test_suppression cc
+'
+
+test_confirm () {
+       echo y | \
+               GIT_SEND_EMAIL_NOTTY=1 \
+               git send-email \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --smtp-server="$(pwd)/fake.sendmail" \
+               $@ $patches > stdout &&
+       grep "Send this email" stdout
+}
+
+test_expect_success '--confirm=always' '
+       test_confirm --confirm=always --suppress-cc=all
+'
+
+test_expect_success '--confirm=auto' '
+       test_confirm --confirm=auto
+'
+
+test_expect_success '--confirm=cc' '
+       test_confirm --confirm=cc
+'
+
+test_expect_success '--confirm=compose' '
+       test_confirm --confirm=compose --compose
+'
+
+test_expect_success 'confirm by default (due to cc)' '
+       CONFIRM=$(git config --get sendemail.confirm) &&
+       git config --unset sendemail.confirm &&
+       test_confirm
+       ret="$?"
+       git config sendemail.confirm ${CONFIRM:-never}
+       test $ret = "0"
+'
+
+test_expect_success 'confirm by default (due to --compose)' '
+       CONFIRM=$(git config --get sendemail.confirm) &&
+       git config --unset sendemail.confirm &&
+       test_confirm --suppress-cc=all --compose
+       ret="$?"
+       git config sendemail.confirm ${CONFIRM:-never}
+       test $ret = "0"
+'
+
+test_expect_success 'confirm detects EOF (inform assumes y)' '
+       CONFIRM=$(git config --get sendemail.confirm) &&
+       git config --unset sendemail.confirm &&
+       rm -fr outdir &&
+       git format-patch -2 -o outdir &&
+       GIT_SEND_EMAIL_NOTTY=1 \
+               git send-email \
+                       --from="Example <nobody@example.com>" \
+                       --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       outdir/*.patch < /dev/null
+       ret="$?"
+       git config sendemail.confirm ${CONFIRM:-never}
+       test $ret = "0"
+'
+
+test_expect_success 'confirm detects EOF (auto causes failure)' '
+       CONFIRM=$(git config --get sendemail.confirm) &&
+       git config sendemail.confirm auto &&
+       GIT_SEND_EMAIL_NOTTY=1 &&
+       export GIT_SEND_EMAIL_NOTTY &&
+               test_must_fail git send-email \
+                       --from="Example <nobody@example.com>" \
+                       --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       $patches < /dev/null
+       ret="$?"
+       git config sendemail.confirm ${CONFIRM:-never}
+       test $ret = "0"
+'
+
+test_expect_success 'confirm doesnt loop forever' '
+       CONFIRM=$(git config --get sendemail.confirm) &&
+       git config sendemail.confirm auto &&
+       GIT_SEND_EMAIL_NOTTY=1 &&
+       export GIT_SEND_EMAIL_NOTTY &&
+               yes "bogus" | test_must_fail git send-email \
+                       --from="Example <nobody@example.com>" \
+                       --to=nobody@example.com \
+                       --smtp-server="$(pwd)/fake.sendmail" \
+                       $patches
+       ret="$?"
+       git config sendemail.confirm ${CONFIRM:-never}
+       test $ret = "0"
+'
+
+test_expect_success 'utf8 Cc is rfc2047 encoded' '
+       clean_fake_sendmail &&
+       rm -fr outdir &&
+       git format-patch -1 -o outdir --cc="àéìöú <utf8@example.com>" &&
        git send-email \
-               --dry-run \
-               --from="Example <from@example.com>" \
-               --to=to@example.com \
-               --smtp-server relay.example.com \
-               $patches |
-       sed     -e "s/^\(Date:\).*/\1 DATE-STRING/" \
-               -e "s/^\(Message-Id:\).*/\1 MESSAGE-ID-STRING/" \
-               -e "s/^\(X-Mailer:\).*/\1 X-MAILER-STRING/" \
-               >actual-show-all-headers &&
-       test_cmp expected-show-all-headers actual-show-all-headers
+       --from="Example <nobody@example.com>" \
+       --to=nobody@example.com \
+       --smtp-server="$(pwd)/fake.sendmail" \
+       outdir/*.patch &&
+       grep "^Cc:" msgtxt1 |
+       grep "=?utf-8?q?=C3=A0=C3=A9=C3=AC=C3=B6=C3=BA?= <utf8@example.com>"
 '
 
 test_expect_success '--compose adds MIME for utf8 body' '
@@ -239,9 +542,7 @@ test_expect_success '--compose adds MIME for utf8 body' '
         echo "echo utf8 body: àéìöú >>\"\$1\""
        ) >fake-editor-utf8 &&
        chmod +x fake-editor-utf8 &&
-       echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor-utf8\"" \
-         GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject foo \
          --from="Example <nobody@example.com>" \
@@ -263,9 +564,7 @@ test_expect_success '--compose respects user mime type' '
         echo " echo utf8 body: àéìöú) >\"\$1\""
        ) >fake-editor-utf8-mime &&
        chmod +x fake-editor-utf8-mime &&
-       echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor-utf8-mime\"" \
-         GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject foo \
          --from="Example <nobody@example.com>" \
@@ -279,9 +578,7 @@ test_expect_success '--compose respects user mime type' '
 
 test_expect_success '--compose adds MIME for utf8 subject' '
        clean_fake_sendmail &&
-       echo y | \
          GIT_EDITOR="\"$(pwd)/fake-editor\"" \
-         GIT_SEND_EMAIL_NOTTY=1 \
          git send-email \
          --compose --subject utf8-sübjëct \
          --from="Example <nobody@example.com>" \
@@ -303,7 +600,7 @@ test_expect_success 'detects ambiguous reference/file conflict' '
 test_expect_success 'feed two files' '
        rm -fr outdir &&
        git format-patch -2 -o outdir &&
-       GIT_SEND_EMAIL_NOTTY=1 git send-email \
+       git send-email \
        --dry-run \
        --from="Example <nobody@example.com>" \
        --to=nobody@example.com \
@@ -313,4 +610,15 @@ test_expect_success 'feed two files' '
        test "z$(sed -n -e 2p subjects)" = "zSubject: [PATCH 2/2] add master"
 '
 
+test_expect_success 'in-reply-to but no threading' '
+       git send-email \
+               --dry-run \
+               --from="Example <nobody@example.com>" \
+               --to=nobody@example.com \
+               --in-reply-to="<in-reply-id@example.com>" \
+               --nothread \
+               $patches |
+       grep "In-Reply-To: <in-reply-id@example.com>"
+'
+
 test_done
index bb921af56af2c9a559c843dd4c3b69993c34206c..4eee2e9fa6bacdc0d130a692f727885c5fa42e49 100755 (executable)
@@ -6,19 +6,19 @@
 test_description='git svn basic tests'
 GIT_SVN_LC_ALL=${LC_ALL:-$LANG}
 
+. ./lib-git-svn.sh
+
+say 'define NO_SVN_TESTS to skip git svn tests'
+
 case "$GIT_SVN_LC_ALL" in
 *.UTF-8)
-       have_utf8=t
+       test_set_prereq UTF8
        ;;
 *)
-       have_utf8=
+       say "UTF-8 locale not set, some tests skipped ($GIT_SVN_LC_ALL)"
        ;;
 esac
 
-. ./lib-git-svn.sh
-
-say 'define NO_SVN_TESTS to skip git svn tests'
-
 test_expect_success \
     'initialize git svn' '
        mkdir import &&
@@ -171,20 +171,15 @@ test_expect_success "$name" '
        test ! -L "$SVN_TREE"/exec-2.sh &&
        test_cmp help "$SVN_TREE"/exec-2.sh'
 
-if test "$have_utf8" = t
-then
-       name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
-       LC_ALL="$GIT_SVN_LC_ALL"
-       export LC_ALL
-       test_expect_success "$name" "
-               echo '# hello' >> exec-2.sh &&
-               git update-index exec-2.sh &&
-               git commit -m 'éï∏' &&
-               git svn set-tree HEAD"
-       unset LC_ALL
-else
-       say "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
-fi
+name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
+LC_ALL="$GIT_SVN_LC_ALL"
+export LC_ALL
+test_expect_success UTF8 "$name" "
+       echo '# hello' >> exec-2.sh &&
+       git update-index exec-2.sh &&
+       git commit -m 'éï∏' &&
+       git svn set-tree HEAD"
+unset LC_ALL
 
 name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
 GIT_SVN_ID=alt
@@ -197,7 +192,7 @@ test_expect_success "$name" \
 
 name='check imported tree checksums expected tree checksums'
 rm -f expected
-if test "$have_utf8" = t
+if test_have_prereq UTF8
 then
        echo tree bf522353586b1b883488f2bc73dab0d9f774b9a9 > expected
 fi
diff --git a/t/t9106-git-svn-dcommit-clobber-series.sh b/t/t9106-git-svn-dcommit-clobber-series.sh
deleted file mode 100755 (executable)
index fd18501..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2007 Eric Wong
-test_description='git svn dcommit clobber series'
-. ./lib-git-svn.sh
-
-test_expect_success 'initialize repo' '
-       mkdir import &&
-       cd import &&
-       awk "BEGIN { for (i = 1; i < 64; i++) { print i } }" > file
-       svn import -m "initial" . "$svnrepo" &&
-       cd .. &&
-       git svn init "$svnrepo" &&
-       git svn fetch &&
-       test -e file
-       '
-
-test_expect_success '(supposedly) non-conflicting change from SVN' '
-       test x"`sed -n -e 58p < file`" = x58 &&
-       test x"`sed -n -e 61p < file`" = x61 &&
-       svn co "$svnrepo" tmp &&
-       cd tmp &&
-               perl -i.bak -p -e "s/^58$/5588/" file &&
-               perl -i.bak -p -e "s/^61$/6611/" file &&
-               poke file &&
-               test x"`sed -n -e 58p < file`" = x5588 &&
-               test x"`sed -n -e 61p < file`" = x6611 &&
-               svn commit -m "58 => 5588, 61 => 6611" &&
-               cd ..
-       '
-
-test_expect_success 'some unrelated changes to git' "
-       echo hi > life &&
-       git update-index --add life &&
-       git commit -m hi-life &&
-       echo bye >> life &&
-       git commit -m bye-life life
-       "
-
-test_expect_success 'change file but in unrelated area' "
-       test x\"\`sed -n -e 4p < file\`\" = x4 &&
-       test x\"\`sed -n -e 7p < file\`\" = x7 &&
-       perl -i.bak -p -e 's/^4\$/4444/' file &&
-       perl -i.bak -p -e 's/^7\$/7777/' file &&
-       test x\"\`sed -n -e 4p < file\`\" = x4444 &&
-       test x\"\`sed -n -e 7p < file\`\" = x7777 &&
-       git commit -m '4 => 4444, 7 => 7777' file &&
-       git svn dcommit &&
-       svn up tmp &&
-       cd tmp &&
-               test x\"\`sed -n -e 4p < file\`\" = x4444 &&
-               test x\"\`sed -n -e 7p < file\`\" = x7777 &&
-               test x\"\`sed -n -e 58p < file\`\" = x5588 &&
-               test x\"\`sed -n -e 61p < file\`\" = x6611
-       "
-
-test_expect_success 'attempt to dcommit with a dirty index' '
-       echo foo >>file &&
-       git add file &&
-       test_must_fail git svn dcommit
-'
-
-test_done
diff --git a/t/t9108-git-svn-multi-glob.sh b/t/t9108-git-svn-multi-glob.sh
deleted file mode 100755 (executable)
index 8f79c3f..0000000
+++ /dev/null
@@ -1,160 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2007 Eric Wong
-test_description='git svn globbing refspecs'
-. ./lib-git-svn.sh
-
-cat > expect.end <<EOF
-the end
-hi
-start a new branch
-initial
-EOF
-
-test_expect_success 'test refspec globbing' '
-       mkdir -p trunk/src/a trunk/src/b trunk/doc &&
-       echo "hello world" > trunk/src/a/readme &&
-       echo "goodbye world" > trunk/src/b/readme &&
-       svn import -m "initial" trunk "$svnrepo"/trunk &&
-       svn co "$svnrepo" tmp &&
-       (
-               cd tmp &&
-               mkdir branches branches/v1 tags &&
-               svn add branches tags &&
-               svn cp trunk branches/v1/start &&
-               svn commit -m "start a new branch" &&
-               svn up &&
-               echo "hi" >> branches/v1/start/src/b/readme &&
-               poke branches/v1/start/src/b/readme &&
-               echo "hey" >> branches/v1/start/src/a/readme &&
-               poke branches/v1/start/src/a/readme &&
-               svn commit -m "hi" &&
-               svn up &&
-               svn cp branches/v1/start tags/end &&
-               echo "bye" >> tags/end/src/b/readme &&
-               poke tags/end/src/b/readme &&
-               echo "aye" >> tags/end/src/a/readme &&
-               poke tags/end/src/a/readme &&
-               svn commit -m "the end" &&
-               echo "byebye" >> tags/end/src/b/readme &&
-               poke tags/end/src/b/readme &&
-               svn commit -m "nothing to see here"
-       ) &&
-       git config --add svn-remote.svn.url "$svnrepo" &&
-       git config --add svn-remote.svn.fetch \
-                        "trunk/src/a:refs/remotes/trunk" &&
-       git config --add svn-remote.svn.branches \
-                        "branches/*/*/src/a:refs/remotes/branches/*/*" &&
-       git config --add svn-remote.svn.tags\
-                        "tags/*/src/a:refs/remotes/tags/*" &&
-       git svn multi-fetch &&
-       git log --pretty=oneline refs/remotes/tags/end | \
-           sed -e "s/^.\{41\}//" > output.end &&
-       test_cmp expect.end output.end &&
-       test "`git rev-parse refs/remotes/tags/end~1`" = \
-               "`git rev-parse refs/remotes/branches/v1/start`" &&
-       test "`git rev-parse refs/remotes/branches/v1/start~2`" = \
-               "`git rev-parse refs/remotes/trunk`" &&
-       test_must_fail git rev-parse refs/remotes/tags/end@3
-       '
-
-echo try to try > expect.two
-echo nothing to see here >> expect.two
-cat expect.end >> expect.two
-
-test_expect_success 'test left-hand-side only globbing' '
-       git config --add svn-remote.two.url "$svnrepo" &&
-       git config --add svn-remote.two.fetch trunk:refs/remotes/two/trunk &&
-       git config --add svn-remote.two.branches \
-                        "branches/*/*:refs/remotes/two/branches/*/*" &&
-       git config --add svn-remote.two.tags \
-                        "tags/*:refs/remotes/two/tags/*" &&
-       (
-               cd tmp &&
-               echo "try try" >> tags/end/src/b/readme &&
-               poke tags/end/src/b/readme &&
-               svn commit -m "try to try"
-       ) &&
-       git svn fetch two &&
-       test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 &&
-       test `git rev-list refs/remotes/two/branches/v1/start | wc -l` -eq 3 &&
-       test `git rev-parse refs/remotes/two/branches/v1/start~2` = \
-            `git rev-parse refs/remotes/two/trunk` &&
-       test `git rev-parse refs/remotes/two/tags/end~3` = \
-            `git rev-parse refs/remotes/two/branches/v1/start` &&
-       git log --pretty=oneline refs/remotes/two/tags/end | \
-           sed -e "s/^.\{41\}//" > output.two &&
-       test_cmp expect.two output.two
-       '
-cat > expect.four <<EOF
-adios
-adding more
-Changed 2 in v2/start
-Another versioned branch
-initial
-EOF
-
-test_expect_success 'test another branch' '
-       (
-               cd tmp &&
-               mkdir branches/v2 &&
-               svn add branches/v2 &&
-               svn cp trunk branches/v2/start &&
-               svn commit -m "Another versioned branch" &&
-               svn up &&
-               echo "hello" >> branches/v2/start/src/b/readme &&
-               poke branches/v2/start/src/b/readme &&
-               echo "howdy" >> branches/v2/start/src/a/readme &&
-               poke branches/v2/start/src/a/readme &&
-               svn commit -m "Changed 2 in v2/start" &&
-               svn up &&
-               svn cp branches/v2/start tags/next &&
-               echo "bye" >> tags/next/src/b/readme &&
-               poke tags/next/src/b/readme &&
-               echo "aye" >> tags/next/src/a/readme &&
-               poke tags/next/src/a/readme &&
-               svn commit -m "adding more" &&
-               echo "byebye" >> tags/next/src/b/readme &&
-               poke tags/next/src/b/readme &&
-               svn commit -m "adios"
-       ) &&
-       git config --add svn-remote.four.url "$svnrepo" &&
-       git config --add svn-remote.four.fetch trunk:refs/remotes/four/trunk &&
-       git config --add svn-remote.four.branches \
-                        "branches/*/*:refs/remotes/four/branches/*/*" &&
-       git config --add svn-remote.four.tags \
-                        "tags/*:refs/remotes/four/tags/*" &&
-       git svn fetch four &&
-       test `git rev-list refs/remotes/four/tags/next | wc -l` -eq 5 &&
-       test `git rev-list refs/remotes/four/branches/v2/start | wc -l` -eq 3 &&
-       test `git rev-parse refs/remotes/four/branches/v2/start~2` = \
-            `git rev-parse refs/remotes/four/trunk` &&
-       test `git rev-parse refs/remotes/four/tags/next~2` = \
-            `git rev-parse refs/remotes/four/branches/v2/start` &&
-       git log --pretty=oneline refs/remotes/four/tags/next | \
-           sed -e "s/^.\{41\}//" > output.four &&
-       test_cmp expect.four output.four
-       '
-
-echo "Only one set of wildcard directories" \
-     "(e.g. '*' or '*/*/*') is supported: 'branches/*/t/*'" > expect.three
-echo "" >> expect.three
-
-test_expect_success 'test disallow multiple globs' '
-       git config --add svn-remote.three.url "$svnrepo" &&
-       git config --add svn-remote.three.fetch \
-                        trunk:refs/remotes/three/trunk &&
-       git config --add svn-remote.three.branches \
-                        "branches/*/t/*:refs/remotes/three/branches/*/*" &&
-       git config --add svn-remote.three.tags \
-                        "tags/*:refs/remotes/three/tags/*" &&
-       (
-               cd tmp &&
-               echo "try try" >> tags/end/src/b/readme &&
-               poke tags/end/src/b/readme &&
-               svn commit -m "try to try"
-       ) &&
-       test_must_fail git svn fetch three 2> stderr.three &&
-       test_cmp expect.three stderr.three
-       '
-
-test_done
diff --git a/t/t9109-git-svn-multi-glob.sh b/t/t9109-git-svn-multi-glob.sh
new file mode 100755 (executable)
index 0000000..8f79c3f
--- /dev/null
@@ -0,0 +1,160 @@
+#!/bin/sh
+# Copyright (c) 2007 Eric Wong
+test_description='git svn globbing refspecs'
+. ./lib-git-svn.sh
+
+cat > expect.end <<EOF
+the end
+hi
+start a new branch
+initial
+EOF
+
+test_expect_success 'test refspec globbing' '
+       mkdir -p trunk/src/a trunk/src/b trunk/doc &&
+       echo "hello world" > trunk/src/a/readme &&
+       echo "goodbye world" > trunk/src/b/readme &&
+       svn import -m "initial" trunk "$svnrepo"/trunk &&
+       svn co "$svnrepo" tmp &&
+       (
+               cd tmp &&
+               mkdir branches branches/v1 tags &&
+               svn add branches tags &&
+               svn cp trunk branches/v1/start &&
+               svn commit -m "start a new branch" &&
+               svn up &&
+               echo "hi" >> branches/v1/start/src/b/readme &&
+               poke branches/v1/start/src/b/readme &&
+               echo "hey" >> branches/v1/start/src/a/readme &&
+               poke branches/v1/start/src/a/readme &&
+               svn commit -m "hi" &&
+               svn up &&
+               svn cp branches/v1/start tags/end &&
+               echo "bye" >> tags/end/src/b/readme &&
+               poke tags/end/src/b/readme &&
+               echo "aye" >> tags/end/src/a/readme &&
+               poke tags/end/src/a/readme &&
+               svn commit -m "the end" &&
+               echo "byebye" >> tags/end/src/b/readme &&
+               poke tags/end/src/b/readme &&
+               svn commit -m "nothing to see here"
+       ) &&
+       git config --add svn-remote.svn.url "$svnrepo" &&
+       git config --add svn-remote.svn.fetch \
+                        "trunk/src/a:refs/remotes/trunk" &&
+       git config --add svn-remote.svn.branches \
+                        "branches/*/*/src/a:refs/remotes/branches/*/*" &&
+       git config --add svn-remote.svn.tags\
+                        "tags/*/src/a:refs/remotes/tags/*" &&
+       git svn multi-fetch &&
+       git log --pretty=oneline refs/remotes/tags/end | \
+           sed -e "s/^.\{41\}//" > output.end &&
+       test_cmp expect.end output.end &&
+       test "`git rev-parse refs/remotes/tags/end~1`" = \
+               "`git rev-parse refs/remotes/branches/v1/start`" &&
+       test "`git rev-parse refs/remotes/branches/v1/start~2`" = \
+               "`git rev-parse refs/remotes/trunk`" &&
+       test_must_fail git rev-parse refs/remotes/tags/end@3
+       '
+
+echo try to try > expect.two
+echo nothing to see here >> expect.two
+cat expect.end >> expect.two
+
+test_expect_success 'test left-hand-side only globbing' '
+       git config --add svn-remote.two.url "$svnrepo" &&
+       git config --add svn-remote.two.fetch trunk:refs/remotes/two/trunk &&
+       git config --add svn-remote.two.branches \
+                        "branches/*/*:refs/remotes/two/branches/*/*" &&
+       git config --add svn-remote.two.tags \
+                        "tags/*:refs/remotes/two/tags/*" &&
+       (
+               cd tmp &&
+               echo "try try" >> tags/end/src/b/readme &&
+               poke tags/end/src/b/readme &&
+               svn commit -m "try to try"
+       ) &&
+       git svn fetch two &&
+       test `git rev-list refs/remotes/two/tags/end | wc -l` -eq 6 &&
+       test `git rev-list refs/remotes/two/branches/v1/start | wc -l` -eq 3 &&
+       test `git rev-parse refs/remotes/two/branches/v1/start~2` = \
+            `git rev-parse refs/remotes/two/trunk` &&
+       test `git rev-parse refs/remotes/two/tags/end~3` = \
+            `git rev-parse refs/remotes/two/branches/v1/start` &&
+       git log --pretty=oneline refs/remotes/two/tags/end | \
+           sed -e "s/^.\{41\}//" > output.two &&
+       test_cmp expect.two output.two
+       '
+cat > expect.four <<EOF
+adios
+adding more
+Changed 2 in v2/start
+Another versioned branch
+initial
+EOF
+
+test_expect_success 'test another branch' '
+       (
+               cd tmp &&
+               mkdir branches/v2 &&
+               svn add branches/v2 &&
+               svn cp trunk branches/v2/start &&
+               svn commit -m "Another versioned branch" &&
+               svn up &&
+               echo "hello" >> branches/v2/start/src/b/readme &&
+               poke branches/v2/start/src/b/readme &&
+               echo "howdy" >> branches/v2/start/src/a/readme &&
+               poke branches/v2/start/src/a/readme &&
+               svn commit -m "Changed 2 in v2/start" &&
+               svn up &&
+               svn cp branches/v2/start tags/next &&
+               echo "bye" >> tags/next/src/b/readme &&
+               poke tags/next/src/b/readme &&
+               echo "aye" >> tags/next/src/a/readme &&
+               poke tags/next/src/a/readme &&
+               svn commit -m "adding more" &&
+               echo "byebye" >> tags/next/src/b/readme &&
+               poke tags/next/src/b/readme &&
+               svn commit -m "adios"
+       ) &&
+       git config --add svn-remote.four.url "$svnrepo" &&
+       git config --add svn-remote.four.fetch trunk:refs/remotes/four/trunk &&
+       git config --add svn-remote.four.branches \
+                        "branches/*/*:refs/remotes/four/branches/*/*" &&
+       git config --add svn-remote.four.tags \
+                        "tags/*:refs/remotes/four/tags/*" &&
+       git svn fetch four &&
+       test `git rev-list refs/remotes/four/tags/next | wc -l` -eq 5 &&
+       test `git rev-list refs/remotes/four/branches/v2/start | wc -l` -eq 3 &&
+       test `git rev-parse refs/remotes/four/branches/v2/start~2` = \
+            `git rev-parse refs/remotes/four/trunk` &&
+       test `git rev-parse refs/remotes/four/tags/next~2` = \
+            `git rev-parse refs/remotes/four/branches/v2/start` &&
+       git log --pretty=oneline refs/remotes/four/tags/next | \
+           sed -e "s/^.\{41\}//" > output.four &&
+       test_cmp expect.four output.four
+       '
+
+echo "Only one set of wildcard directories" \
+     "(e.g. '*' or '*/*/*') is supported: 'branches/*/t/*'" > expect.three
+echo "" >> expect.three
+
+test_expect_success 'test disallow multiple globs' '
+       git config --add svn-remote.three.url "$svnrepo" &&
+       git config --add svn-remote.three.fetch \
+                        trunk:refs/remotes/three/trunk &&
+       git config --add svn-remote.three.branches \
+                        "branches/*/t/*:refs/remotes/three/branches/*/*" &&
+       git config --add svn-remote.three.tags \
+                        "tags/*:refs/remotes/three/tags/*" &&
+       (
+               cd tmp &&
+               echo "try try" >> tags/end/src/b/readme &&
+               poke tags/end/src/b/readme &&
+               svn commit -m "try to try"
+       ) &&
+       test_must_fail git svn fetch three 2> stderr.three &&
+       test_cmp expect.three stderr.three
+       '
+
+test_done
index 8a9dde44d57b792d7142f082aa18274a13d532f3..3200ab38efa03076b81b94713e74205d740e38e8 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"
 }
 
@@ -61,24 +70,26 @@ do
 done
 
 if locale -a |grep -q en_US.utf8; then
-       test_expect_success 'ISO-8859-1 should match UTF-8 in svn' '
+       test_set_prereq UTF8
+else
+       say "UTF-8 locale not available, test skipped"
+fi
+
+test_expect_success UTF8 'ISO-8859-1 should match UTF-8 in svn' '
        (
                cd ISO-8859-1 &&
                compare_svn_head_with "$TEST_DIRECTORY"/t3900/1-UTF-8.txt
        )
-       '
+'
 
-       for H in EUCJP ISO-2022-JP
-       do
-               test_expect_success '$H should match UTF-8 in svn' '
+for H in EUCJP ISO-2022-JP
+do
+       test_expect_success UTF8 "$H should match UTF-8 in svn" '
                (
                        cd $H &&
                        compare_svn_head_with "$TEST_DIRECTORY"/t3900/2-UTF-8.txt
                )
-               '
-       done
-else
-       say "UTF-8 locale not available, test skipped"
-fi
+       '
+done
 
 test_done
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..9a24a65
--- /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 SYMLINKS '"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..6c4c90b
--- /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 SYMLINKS '"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 SYMLINKS '"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..71fdc4a
--- /dev/null
@@ -0,0 +1,147 @@
+#!/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 'init+fetch an SVN repository with ignored www directory' '
+       git svn init "$svnrepo" c &&
+       ( cd c && git svn fetch --ignore-paths="^www" ) &&
+       rm expect2 &&
+       echo test_qqq > expect &&
+       for i in c/*/*.txt; do cat $i >> expect2; done &&
+       test_cmp expect expect2
+'
+
+test_expect_success 'verify ignore-paths config saved by clone' '
+       (
+           cd g &&
+           git config --get svn-remote.svn.ignore-paths | fgrep "www"
+       )
+'
+
+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 (config ignore)' '
+       (
+               cd g &&
+               git svn rebase &&
+               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 'update git svn-cloned repo (option ignore)' '
+       (
+               cd c &&
+               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 (config ignore)' '
+       (
+               cd g &&
+               git svn rebase &&
+               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 'update git svn-cloned repo (option ignore)' '
+       (
+               cd c &&
+               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 (config ignore)' '
+       (
+               cd g &&
+               git svn rebase &&
+               printf "test_qqq\nb\nygg\n" > expect &&
+               for i in */*.txt; do cat $i >> expect2; done &&
+               test_cmp expect2 expect &&
+               rm expect expect2
+       )
+'
+
+test_expect_success 'update git svn-cloned repo again (option ignore)' '
+       (
+               cd c &&
+               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
+
+
diff --git a/t/t9137-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh
new file mode 100755 (executable)
index 0000000..fd18501
--- /dev/null
@@ -0,0 +1,63 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+test_description='git svn dcommit clobber series'
+. ./lib-git-svn.sh
+
+test_expect_success 'initialize repo' '
+       mkdir import &&
+       cd import &&
+       awk "BEGIN { for (i = 1; i < 64; i++) { print i } }" > file
+       svn import -m "initial" . "$svnrepo" &&
+       cd .. &&
+       git svn init "$svnrepo" &&
+       git svn fetch &&
+       test -e file
+       '
+
+test_expect_success '(supposedly) non-conflicting change from SVN' '
+       test x"`sed -n -e 58p < file`" = x58 &&
+       test x"`sed -n -e 61p < file`" = x61 &&
+       svn co "$svnrepo" tmp &&
+       cd tmp &&
+               perl -i.bak -p -e "s/^58$/5588/" file &&
+               perl -i.bak -p -e "s/^61$/6611/" file &&
+               poke file &&
+               test x"`sed -n -e 58p < file`" = x5588 &&
+               test x"`sed -n -e 61p < file`" = x6611 &&
+               svn commit -m "58 => 5588, 61 => 6611" &&
+               cd ..
+       '
+
+test_expect_success 'some unrelated changes to git' "
+       echo hi > life &&
+       git update-index --add life &&
+       git commit -m hi-life &&
+       echo bye >> life &&
+       git commit -m bye-life life
+       "
+
+test_expect_success 'change file but in unrelated area' "
+       test x\"\`sed -n -e 4p < file\`\" = x4 &&
+       test x\"\`sed -n -e 7p < file\`\" = x7 &&
+       perl -i.bak -p -e 's/^4\$/4444/' file &&
+       perl -i.bak -p -e 's/^7\$/7777/' file &&
+       test x\"\`sed -n -e 4p < file\`\" = x4444 &&
+       test x\"\`sed -n -e 7p < file\`\" = x7777 &&
+       git commit -m '4 => 4444, 7 => 7777' file &&
+       git svn dcommit &&
+       svn up tmp &&
+       cd tmp &&
+               test x\"\`sed -n -e 4p < file\`\" = x4444 &&
+               test x\"\`sed -n -e 7p < file\`\" = x7777 &&
+               test x\"\`sed -n -e 58p < file\`\" = x5588 &&
+               test x\"\`sed -n -e 61p < file\`\" = x6611
+       "
+
+test_expect_success 'attempt to dcommit with a dirty index' '
+       echo foo >>file &&
+       git add file &&
+       test_must_fail git svn dcommit
+'
+
+test_done
index 245a7c36628e955e04852a8c2beb75b8deaf4d7f..56b7c06921d9ef3b72ff3ee6f62f7a1c426b3028 100755 (executable)
@@ -6,12 +6,16 @@ test_description='Test export of commits to CVS'
 
 . ./test-lib.sh
 
+if ! test_have_prereq PERL; then
+       say 'skipping git cvsexportcommit tests, perl not available'
+       test_done
+fi
+
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then
-    test_expect_success 'skipping git cvsexportcommit tests, cvs not found' :
+    say 'skipping git cvsexportcommit tests, cvs not found'
     test_done
-    exit
 fi
 
 CVSROOT=$(pwd)/cvsroot
@@ -225,11 +229,12 @@ test_expect_success \
       test_must_fail git cvsexportcommit -c $id
       )'
 
-case "$(git config --bool core.filemode)" in
-false)
-       ;;
-*)
-test_expect_success \
+if ! test "$(git config --bool core.filemode)" = false
+then
+       test_set_prereq FILEMODE
+fi
+
+test_expect_success FILEMODE \
      'Retain execute bit' \
      'mkdir G &&
       echo executeon >G/on &&
@@ -243,8 +248,6 @@ test_expect_success \
       test -x G/on &&
       ! test -x G/off
       )'
-       ;;
-esac
 
 test_expect_success '-w option should work with relative GIT_DIR' '
       mkdir W &&
index 86c376088ccd04d0b0cbb14424eef7a9b89b45d3..8da9ce54596416af6fc9779a5ac3f7131958aec8 100755 (executable)
@@ -8,6 +8,9 @@ test_description='git fast-export'
 
 test_expect_success 'setup' '
 
+       echo break it > file0 &&
+       git add file0 &&
+       test_tick &&
        echo Wohlauf > file &&
        git add file &&
        test_tick &&
@@ -57,8 +60,8 @@ test_expect_success 'fast-export master~2..master' '
                (cd new &&
                 git fast-import &&
                 test $MASTER != $(git rev-parse --verify refs/heads/partial) &&
-                git diff master..partial &&
-                git diff master^..partial^ &&
+                git diff --exit-code master partial &&
+                git diff --exit-code master^ partial^ &&
                 test_must_fail git rev-parse partial~2)
 
 '
@@ -259,4 +262,19 @@ test_expect_success 'cope with tagger-less tags' '
 
 '
 
+test_expect_success 'set-up a few more tags for tag export tests' '
+       git checkout -f master &&
+       HEAD_TREE=`git show -s --pretty=raw HEAD | grep tree | sed "s/tree //"` &&
+       git tag    tree_tag        -m "tagging a tree" $HEAD_TREE &&
+       git tag -a tree_tag-obj    -m "tagging a tree" $HEAD_TREE &&
+       git tag    tag-obj_tag     -m "tagging a tag" tree_tag-obj &&
+       git tag -a tag-obj_tag-obj -m "tagging a tag" tree_tag-obj
+'
+
+# NEEDSWORK: not just check return status, but validate the output
+test_expect_success 'tree_tag'        'git fast-export tree_tag'
+test_expect_success 'tree_tag-obj'    'git fast-export tree_tag-obj'
+test_expect_success 'tag-obj_tag'     'git fast-export tag-obj_tag'
+test_expect_success 'tag-obj_tag-obj' 'git fast-export tag-obj_tag-obj'
+
 test_done
index 6a37f71d11800f92a117bfdcf38172bfc9000d77..64f947d75bc0700fc75e8c7c87d97ec4f3e62e44 100755 (executable)
@@ -10,17 +10,19 @@ cvs CLI client via git-cvsserver server'
 
 . ./test-lib.sh
 
+if ! test_have_prereq PERL; then
+       say 'skipping git cvsserver tests, perl not available'
+       test_done
+fi
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then
-    test_expect_success 'skipping git-cvsserver tests, cvs not found' :
+    say 'skipping git-cvsserver tests, cvs not found'
     test_done
-    exit
 fi
 perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
-    test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' :
+    say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
     test_done
-    exit
 }
 
 unset GIT_DIR GIT_CONFIG
@@ -44,7 +46,7 @@ test_expect_success 'setup' '
   git add secondrootfile &&
   git commit -m "second root") &&
   git pull secondroot master &&
-  git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+  git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
   GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
   GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
 '
@@ -267,7 +269,7 @@ test_expect_success 'gitcvs.ext.dbname' \
 
 rm -fr "$SERVERDIR"
 cd "$WORKDIR" &&
-git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
 GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
 GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
 exit 1
index e27a1c5f85bbecac652ce8d224f8fc5e99b02a4e..aca40c1b1ff0f957652f75383625367d85242c14 100755 (executable)
@@ -49,14 +49,17 @@ not_present() {
 cvs >/dev/null 2>&1
 if test $? -ne 1
 then
-    test_expect_success 'skipping git-cvsserver tests, cvs not found' :
+    say 'skipping git-cvsserver tests, cvs not found'
+    test_done
+fi
+if ! test_have_prereq PERL
+then
+    say 'skipping git-cvsserver tests, perl not available'
     test_done
-    exit
 fi
 perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
-    test_expect_success 'skipping git-cvsserver tests, Perl SQLite interface unavailable' :
+    say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
     test_done
-    exit
 }
 
 unset GIT_DIR GIT_CONFIG
@@ -84,7 +87,7 @@ test_expect_success 'setup' '
     echo "subdir/file.h crlf" >> .gitattributes &&
     git add .gitattributes textfile.c binfile.bin mixedUp.c subdir/* &&
     git commit -q -m "First Commit" &&
-    git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
+    git clone -q --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
     GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
     GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log"
 '
index 43cd6eecbac70f1ab63f19bf972f17c1f8188d5e..f4210fbb04065cfe32f26053eb5f5f054d52e3cf 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,27 +56,23 @@ 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
 
        # gitweb.log is left for debugging
 }
 
-safe_chmod () {
-       chmod "$1" "$2" &&
-       if [ "$(git config --get core.filemode)" = false ]
-       then
-               git update-index --chmod="$1" "$2"
-       fi
-}
-
 . ./test-lib.sh
 
+if ! test_have_prereq PERL; then
+       say 'skipping gitweb tests, perl not available'
+       test_done
+fi
+
 perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
-    test_expect_success 'skipping gitweb tests, perl version is too old' :
+    say 'skipping gitweb tests, perl version is too old'
     test_done
-    exit
 }
 
 gitweb_init
@@ -240,7 +238,7 @@ test_debug 'cat gitweb.log'
 
 test_expect_success \
        'commitdiff(0): mode change' \
-       'safe_chmod +x new_file &&
+       'test_chmod +x new_file &&
         git commit -a -m "Mode changed." &&
         gitweb_run "p=.git;a=commitdiff"'
 test_debug 'cat gitweb.log'
@@ -252,7 +250,7 @@ test_expect_success \
         gitweb_run "p=.git;a=commitdiff"'
 test_debug 'cat gitweb.log'
 
-test_expect_success \
+test_expect_success SYMLINKS \
        'commitdiff(0): file to symlink' \
        'rm renamed_file &&
         ln -s file renamed_file &&
@@ -279,7 +277,7 @@ test_debug 'cat gitweb.log'
 test_expect_success \
        'commitdiff(0): mode change and modified' \
        'echo "New line" >> file2 &&
-        safe_chmod +x file2 &&
+        test_chmod +x file2 &&
         git commit -a -m "Mode change and modification." &&
         gitweb_run "p=.git;a=commitdiff"'
 test_debug 'cat gitweb.log'
@@ -306,7 +304,7 @@ test_expect_success \
        'commitdiff(0): renamed, mode change and modified' \
        'git mv file3 file2 &&
         echo "Propter nomen suum." >> file2 &&
-        safe_chmod +x file2 &&
+        test_chmod +x file2 &&
         git commit -a -m "File rename, mode change and modification." &&
         gitweb_run "p=.git;a=commitdiff"'
 test_debug 'cat gitweb.log'
@@ -314,7 +312,7 @@ test_debug 'cat gitweb.log'
 # ----------------------------------------------------------------------
 # commitdiff testing (taken from t4114-apply-typechange.sh)
 
-test_expect_success 'setup typechange commits' '
+test_expect_success SYMLINKS 'setup typechange commits' '
        echo "hello world" > foo &&
        echo "hi planet" > bar &&
        git update-index --add foo bar &&
@@ -423,10 +421,15 @@ test_expect_success \
         git add 03-new &&
         git mv 04-rename-from 04-rename-to &&
         echo "Changed" >> 04-rename-to &&
-        safe_chmod +x 05-mode-change &&
-        rm -f 06-file-or-symlink && ln -s 01-change 06-file-or-symlink &&
+        test_chmod +x 05-mode-change &&
+        rm -f 06-file-or-symlink &&
+        if test_have_prereq SYMLINKS; then
+               ln -s 01-change 06-file-or-symlink
+        else
+               printf %s 01-change > 06-file-or-symlink
+        fi &&
         echo "Changed and have mode changed" > 07-change-mode-change   &&
-        safe_chmod +x 07-change-mode-change &&
+        test_chmod +x 07-change-mode-change &&
         git commit -a -m "Large commit" &&
         git checkout master'
 
@@ -659,6 +662,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 +675,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 d2379e7f62a4da76791e65dbc2c70f4dfe14ff3b..4322a0c1ed6d792cbba4b52a7ba8bad74986bbad 100755 (executable)
@@ -3,6 +3,11 @@
 test_description='git cvsimport basic tests'
 . ./test-lib.sh
 
+if ! test_have_prereq PERL; then
+       say 'skipping git cvsimport tests, perl not available'
+       test_done
+fi
+
 CVSROOT=$(pwd)/cvsroot
 export CVSROOT
 unset CVS_SERVER
@@ -14,7 +19,6 @@ if ! type cvs >/dev/null 2>&1
 then
        say 'skipping cvsimport tests, cvs not found'
        test_done
-       exit
 fi
 
 cvsps_version=`cvsps -h 2>&1 | sed -ne 's/cvsps version //p'`
@@ -24,12 +28,10 @@ case "$cvsps_version" in
 '')
        say 'skipping cvsimport tests, cvsps not found'
        test_done
-       exit
        ;;
 *)
        say 'skipping cvsimport tests, unsupported cvsps version'
        test_done
-       exit
        ;;
 esac
 
index b81d5dfc340e050815ad9b2fd0d0636c529ce8d3..b4ca244626a6635faa2587722c26f4c17692af65 100755 (executable)
@@ -6,8 +6,13 @@
 test_description='perl interface (Git.pm)'
 . ./test-lib.sh
 
+if ! test_have_prereq PERL; then
+       say 'skipping perl interface tests, perl not available'
+       test_done
+fi
+
 perl -MTest::More -e 0 2>/dev/null || {
-       say_color skip "Perl Test::More unavailable, skipping test"
+       say "Perl Test::More unavailable, skipping test"
        test_done
 }
 
index 1b4f21638574a5cd9eb70bd9ba5d16ff10ba4058..dad1437fa49596cf6f36b40b1ab18b008620a246 100644 (file)
@@ -3,6 +3,22 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
+# if --tee was passed, write the output not only to the terminal, but
+# additionally to the file test-results/$BASENAME.out, too.
+case "$GIT_TEST_TEE_STARTED, $* " in
+done,*)
+       # do not redirect again
+       ;;
+*' --tee '*|*' --va'*)
+       mkdir -p test-results
+       BASE=test-results/$(basename "$0" .sh)
+       (GIT_TEST_TEE_STARTED=done ${SHELL-sh} "$0" "$@" 2>&1;
+        echo $? > $BASE.exit) | tee $BASE.out
+       test "$(cat $BASE.exit)" = 0
+       exit
+       ;;
+esac
+
 # Keep the original TERM for say_color
 ORIGINAL_TERM=$TERM
 
@@ -94,6 +110,10 @@ do
        --no-python)
                # noop now...
                shift ;;
+       --va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
+               valgrind=t; verbose=t; shift ;;
+       --tee)
+               shift ;; # was handled already
        *)
                break ;;
        esac
@@ -127,7 +147,7 @@ fi
 
 error () {
        say_color error "error: $*"
-       trap - exit
+       trap - EXIT
        exit 1
 }
 
@@ -163,7 +183,7 @@ die () {
        exit 1
 }
 
-trap 'die' exit
+trap 'die' EXIT
 
 # The semantics of the editor variables are that of invoking
 # sh -c "$EDITOR \"$@\"" files ...
@@ -193,32 +213,87 @@ 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"
+}
+
+# This function helps systems where core.filemode=false is set.
+# Use it instead of plain 'chmod +x' to set or unset the executable bit
+# of a file in the working directory and add it to the index.
+
+test_chmod () {
+       chmod "$@" &&
+       git update-index --add "--chmod=$@"
+}
+
+# Use test_set_prereq to tell that a particular prerequisite is available.
+# The prerequisite can later be checked for in two ways:
+#
+# - Explicitly using test_have_prereq.
+#
+# - Implicitly by specifying the prerequisite tag in the calls to
+#   test_expect_{success,failure,code}.
+#
+# The single parameter is the prerequisite tag (a simple word, in all
+# capital letters by convention).
+
+test_set_prereq () {
+       satisfied="$satisfied$1 "
+}
+satisfied=" "
+
+test_have_prereq () {
+       case $satisfied in
+       *" $1 "*)
+               : yes, have it ;;
+       *)
+               ! : nope ;;
+       esac
+}
+
 # You are not expected to call test_ok_ and test_failure_ directly, use
 # the text_expect_* functions instead.
 
 test_ok_ () {
-       test_count=$(expr "$test_count" + 1)
-       test_success=$(expr "$test_success" + 1)
+       test_success=$(($test_success + 1))
        say_color "" "  ok $test_count: $@"
 }
 
 test_failure_ () {
-       test_count=$(expr "$test_count" + 1)
-       test_failure=$(expr "$test_failure" + 1);
+       test_failure=$(($test_failure + 1))
        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_ () {
-       test_count=$(expr "$test_count" + 1)
        test_fixed=$(($test_fixed+1))
        say_color "" "  FIXED $test_count: $@"
 }
 
 test_known_broken_failure_ () {
-       test_count=$(expr "$test_count" + 1)
        test_broken=$(($test_broken+1))
        say_color skip "  still broken $test_count: $@"
 }
@@ -234,20 +309,23 @@ test_run_ () {
 }
 
 test_skip () {
-       this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
-       this_test="$this_test.$(expr "$test_count" + 1)"
+       test_count=$(($test_count+1))
        to_skip=
        for skp in $GIT_SKIP_TESTS
        do
-               case "$this_test" in
+               case $this_test.$test_count in
                $skp)
                        to_skip=t
                esac
        done
+       if test -z "$to_skip" && test -n "$prereq" &&
+          ! test_have_prereq "$prereq"
+       then
+               to_skip=t
+       fi
        case "$to_skip" in
        t)
                say_color skip >&3 "skipping test: $@"
-               test_count=$(expr "$test_count" + 1)
                say_color skip "skip $test_count: $1"
                : true
                ;;
@@ -258,8 +336,9 @@ test_skip () {
 }
 
 test_expect_failure () {
+       test "$#" = 3 && { prereq=$1; shift; } || prereq=
        test "$#" = 2 ||
-       error "bug in the test script: not 2 parameters to test-expect-failure"
+       error "bug in the test script: not 2 or 3 parameters to test-expect-failure"
        if ! test_skip "$@"
        then
                say >&3 "checking known breakage: $2"
@@ -275,8 +354,9 @@ test_expect_failure () {
 }
 
 test_expect_success () {
+       test "$#" = 3 && { prereq=$1; shift; } || prereq=
        test "$#" = 2 ||
-       error "bug in the test script: not 2 parameters to test-expect-success"
+       error "bug in the test script: not 2 or 3 parameters to test-expect-success"
        if ! test_skip "$@"
        then
                say >&3 "expecting success: $2"
@@ -292,8 +372,9 @@ test_expect_success () {
 }
 
 test_expect_code () {
+       test "$#" = 4 && { prereq=$1; shift; } || prereq=
        test "$#" = 3 ||
-       error "bug in the test script: not 3 parameters to test-expect-code"
+       error "bug in the test script: not 3 or 4 parameters to test-expect-code"
        if ! test_skip "$@"
        then
                say >&3 "expecting exit code $1: $3"
@@ -317,15 +398,16 @@ test_expect_code () {
 # Usage: test_external description command arguments...
 # Example: test_external 'Perl API' perl ../path/to/test.pl
 test_external () {
-       test "$#" -eq 3 ||
-       error >&5 "bug in the test script: not 3 parameters to test_external"
+       test "$#" = 4 && { prereq=$1; shift; } || prereq=
+       test "$#" = 3 ||
+       error >&5 "bug in the test script: not 3 or 4 parameters to test_external"
        descr="$1"
        shift
        if ! test_skip "$descr" "$@"
        then
                # Announce the script to reduce confusion about the
                # test output that follows.
-               say_color "" " run $(expr "$test_count" + 1): $descr ($*)"
+               say_color "" " run $test_count: $descr ($*)"
                # Run command; redirect its stderr to &4 as in
                # test_run_, but keep its stdout on our stdout even in
                # non-verbose mode.
@@ -409,17 +491,17 @@ test_create_repo () {
        repo="$1"
        mkdir -p "$repo"
        cd "$repo" || error "Cannot setup test environment"
-       "$GIT_EXEC_PATH/git" init "--template=$GIT_EXEC_PATH/templates/blt/" >&3 2>&4 ||
+       "$GIT_EXEC_PATH/git-init" "--template=$TEST_DIRECTORY/../templates/blt/" >&3 2>&4 ||
        error "cannot run git init -- have you built things yet?"
        mv .git/hooks .git/hooks-disabled
        cd "$owd"
 }
 
 test_done () {
-       trap - exit
+       trap - EXIT
        test_results_dir="$TEST_DIRECTORY/test-results"
        mkdir -p "$test_results_dir"
-       test_results_path="$test_results_dir/${0%-*}-$$"
+       test_results_path="$test_results_dir/${0%.sh}-$$"
 
        echo "total $test_count" >> $test_results_path
        echo "success $test_success" >> $test_results_path
@@ -459,8 +541,81 @@ test_done () {
 # Test the binaries we have just built.  The tests are kept in
 # t/ subdirectory and are run in 'trash directory' subdirectory.
 TEST_DIRECTORY=$(pwd)
-PATH=$TEST_DIRECTORY/..:$PATH
-GIT_EXEC_PATH=$(pwd)/..
+if test -z "$valgrind"
+then
+       if test -z "$GIT_TEST_INSTALLED"
+       then
+               PATH=$TEST_DIRECTORY/..:$PATH
+               GIT_EXEC_PATH=$TEST_DIRECTORY/..
+       else
+               GIT_EXEC_PATH=$($GIT_TEST_INSTALLED/git --exec-path)  ||
+               error "Cannot run git from $GIT_TEST_INSTALLED."
+               PATH=$GIT_TEST_INSTALLED:$TEST_DIRECTORY/..:$PATH
+               GIT_EXEC_PATH=${GIT_TEST_EXEC_PATH:-$GIT_EXEC_PATH}
+       fi
+else
+       make_symlink () {
+               test -h "$2" &&
+               test "$1" = "$(readlink "$2")" || {
+                       # be super paranoid
+                       if mkdir "$2".lock
+                       then
+                               rm -f "$2" &&
+                               ln -s "$1" "$2" &&
+                               rm -r "$2".lock
+                       else
+                               while test -d "$2".lock
+                               do
+                                       say "Waiting for lock on $2."
+                                       sleep 1
+                               done
+                       fi
+               }
+       }
+
+       make_valgrind_symlink () {
+               # handle only executables
+               test -x "$1" || return
+
+               base=$(basename "$1")
+               symlink_target=$TEST_DIRECTORY/../$base
+               # do not override scripts
+               if test -x "$symlink_target" &&
+                   test ! -d "$symlink_target" &&
+                   test "#!" != "$(head -c 2 < "$symlink_target")"
+               then
+                       symlink_target=../valgrind.sh
+               fi
+               case "$base" in
+               *.sh|*.perl)
+                       symlink_target=../unprocessed-script
+               esac
+               # create the link, or replace it if it is out of date
+               make_symlink "$symlink_target" "$GIT_VALGRIND/bin/$base" || exit
+       }
+
+       # override all git executables in TEST_DIRECTORY/..
+       GIT_VALGRIND=$TEST_DIRECTORY/valgrind
+       mkdir -p "$GIT_VALGRIND"/bin
+       for file in $TEST_DIRECTORY/../git* $TEST_DIRECTORY/../test-*
+       do
+               make_valgrind_symlink $file
+       done
+       OLDIFS=$IFS
+       IFS=:
+       for path in $PATH
+       do
+               ls "$path"/git-* 2> /dev/null |
+               while read file
+               do
+                       make_valgrind_symlink "$file"
+               done
+       done
+       IFS=$OLDIFS
+       PATH=$GIT_VALGRIND/bin:$PATH
+       GIT_EXEC_PATH=$GIT_VALGRIND/bin
+       export GIT_VALGRIND
+fi
 GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
 unset GIT_CONFIG
 GIT_CONFIG_NOSYSTEM=1
@@ -485,7 +640,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
 }
@@ -495,7 +650,8 @@ test_create_repo "$test"
 # in subprocesses like git equals our $PWD (for pathname comparisons).
 cd -P "$test" || exit 1
 
-this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
+this_test=${0##*/}
+this_test=${this_test%%-*}
 for skp in $GIT_SKIP_TESTS
 do
        to_skip=
@@ -513,3 +669,37 @@ do
                test_done
        esac
 done
+
+# Fix some commands on Windows
+case $(uname -s) in
+*MINGW*)
+       # Windows has its own (incompatible) sort and find
+       sort () {
+               /usr/bin/sort "$@"
+       }
+       find () {
+               /usr/bin/find "$@"
+       }
+       sum () {
+               md5sum "$@"
+       }
+       # git sees Windows-style pwd
+       pwd () {
+               builtin pwd -W
+       }
+       # no POSIX permissions
+       # backslashes in pathspec are converted to '/'
+       # exec does not inherit the PID
+       ;;
+*)
+       test_set_prereq POSIXPERM
+       test_set_prereq BSLASHPSPEC
+       test_set_prereq EXECKEEPSPID
+       ;;
+esac
+
+test -z "$NO_PERL" && test_set_prereq PERL
+
+# test whether the filesystem supports symbolic links
+ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
+rm -f y
diff --git a/t/valgrind/.gitignore b/t/valgrind/.gitignore
new file mode 100644 (file)
index 0000000..d4ae667
--- /dev/null
@@ -0,0 +1,2 @@
+/bin/
+/templates
diff --git a/t/valgrind/analyze.sh b/t/valgrind/analyze.sh
new file mode 100755 (executable)
index 0000000..d8105d9
--- /dev/null
@@ -0,0 +1,123 @@
+#!/bin/sh
+
+out_prefix=$(dirname "$0")/../test-results/valgrind.out
+output=
+count=0
+total_count=0
+missing_message=
+new_line='
+'
+
+# start outputting the current valgrind error in $out_prefix.++$count,
+# and the test case which failed in the corresponding .message file
+start_output () {
+       test -z "$output" || return
+
+       # progress
+       total_count=$(($total_count+1))
+       test -t 2 && printf "\rFound %d errors" $total_count >&2
+
+       count=$(($count+1))
+       output=$out_prefix.$count
+       : > $output
+
+       echo "*** $1 ***" > $output.message
+}
+
+finish_output () {
+       test ! -z "$output" || return
+       output=
+
+       # if a test case has more than one valgrind error, we need to
+       # copy the last .message file to the previous errors
+       test -z "$missing_message" || {
+               while test $missing_message -lt $count
+               do
+                       cp $out_prefix.$count.message \
+                               $out_prefix.$missing_message.message
+                       missing_message=$(($missing_message+1))
+               done
+               missing_message=
+       }
+}
+
+# group the valgrind errors by backtrace
+output_all () {
+       last_line=
+       j=0
+       i=1
+       while test $i -le $count
+       do
+               # output <number> <backtrace-in-one-line>
+               echo "$i $(tr '\n' ' ' < $out_prefix.$i)"
+               i=$(($i+1))
+       done |
+       sort -t ' ' -k 2 | # order by <backtrace-in-one-line>
+       while read number line
+       do
+               # find duplicates, do not output backtrace twice
+               if test "$line" != "$last_line"
+               then
+                       last_line=$line
+                       j=$(($j+1))
+                       printf "\nValgrind error $j:\n\n"
+                       cat $out_prefix.$number
+                       printf "\nfound in:\n"
+               fi
+               # print the test case where this came from
+               printf "\n"
+               cat $out_prefix.$number.message
+       done
+}
+
+handle_one () {
+       OLDIFS=$IFS
+       IFS="$new_line"
+       while read line
+       do
+               case "$line" in
+               # backtrace, possibly a new one
+               ==[0-9]*)
+
+                       # Does the current valgrind error have a message yet?
+                       case "$output" in
+                       *.message)
+                               test -z "$missing_message" &&
+                               missing_message=$count
+                               output=
+                       esac
+
+                       start_output $(basename $1)
+                       echo "$line" |
+                       sed 's/==[0-9]*==/==valgrind==/' >> $output
+                       ;;
+               # end of backtrace
+               '}')
+                       test -z "$output" || {
+                               echo "$line" >> $output
+                               test $output = ${output%.message} &&
+                               output=$output.message
+                       }
+                       ;;
+               # end of test case
+               '')
+                       finish_output
+                       ;;
+               # normal line; if $output is set, print the line
+               *)
+                       test -z "$output" || echo "$line" >> $output
+                       ;;
+               esac
+       done < $1
+       IFS=$OLDIFS
+
+       # just to be safe
+       finish_output
+}
+
+for test_script in "$(dirname "$0")"/../test-results/*.out
+do
+       handle_one $test_script
+done
+
+output_all
diff --git a/t/valgrind/default.supp b/t/valgrind/default.supp
new file mode 100644 (file)
index 0000000..9e013fa
--- /dev/null
@@ -0,0 +1,45 @@
+{
+       ignore-zlib-errors-cond
+       Memcheck:Cond
+       obj:*libz.so*
+}
+
+{
+       ignore-zlib-errors-value8
+       Memcheck:Value8
+       obj:*libz.so*
+}
+
+{
+       ignore-zlib-errors-value4
+       Memcheck:Value4
+       obj:*libz.so*
+}
+
+{
+       ignore-ldso-cond
+       Memcheck:Cond
+       obj:*ld-*.so
+}
+
+{
+       ignore-ldso-addr8
+       Memcheck:Addr8
+       obj:*ld-*.so
+}
+
+{
+       ignore-ldso-addr4
+       Memcheck:Addr4
+       obj:*ld-*.so
+}
+
+{
+       writing-data-from-zlib-triggers-even-more-errors
+       Memcheck:Param
+       write(buf)
+       obj:/lib/ld-*.so
+       fun:write_in_full
+       fun:write_buffer
+       fun:write_loose_object
+}
diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh
new file mode 100755 (executable)
index 0000000..582b4dc
--- /dev/null
@@ -0,0 +1,22 @@
+#!/bin/sh
+
+base=$(basename "$0")
+
+TRACK_ORIGINS=
+
+VALGRIND_VERSION=$(valgrind --version)
+VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)')
+VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)')
+test 3 -gt "$VALGRIND_MAJOR" ||
+test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" ||
+TRACK_ORIGINS=--track-origins=yes
+
+exec valgrind -q --error-exitcode=126 \
+       --leak-check=no \
+       --suppressions="$GIT_VALGRIND/default.supp" \
+       --gen-suppressions=all \
+       $TRACK_ORIGINS \
+       --log-fd=4 \
+       --input-fd=4 \
+       $GIT_VALGRIND_OPTIONS \
+       "$GIT_VALGRIND"/../../"$base" "$@"
index 0e49279c7f7b805c78f1bc4760a0d1c70a84a0d9..0ba62076fb4a381e588eba90ed639dd560860fd3 100755 (executable)
@@ -7,7 +7,7 @@
 #
 # To enable this hook, rename this file to "pre-commit".
 
-if git-rev-parse --verify HEAD 2>/dev/null
+if git-rev-parse --verify HEAD >/dev/null 2>&1
 then
        against=HEAD
 else
index 93c605594fc06683088b934273873165215ccbb5..f8bf490cffa2d41d19ffc9a31839ca3c2686df16 100755 (executable)
@@ -16,6 +16,9 @@
 # hooks.allowdeletebranch
 #   This boolean sets whether deleting branches will be allowed in the
 #   repository.  By default they won't be.
+# hooks.denycreatebranch
+#   This boolean sets whether remotely creating branches will be denied
+#   in the repository.  By default this is allowed.
 #
 
 # --- Command line
@@ -39,18 +42,22 @@ fi
 # --- Config
 allowunannotated=$(git config --bool hooks.allowunannotated)
 allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --bool hooks.denycreatebranch)
 allowdeletetag=$(git config --bool hooks.allowdeletetag)
 
 # check for no description
 projectdesc=$(sed -e '1q' "$GIT_DIR/description")
-if [ -z "$projectdesc" -o "$projectdesc" = "Unnamed repository; edit this file to name it for gitweb." ]; then
+case "$projectdesc" in
+"Unnamed repository"* | "")
        echo "*** Project description file hasn't been set" >&2
        exit 1
-fi
+       ;;
+esac
 
 # --- Check types
 # if $newrev is 0000...0000, it's a commit to delete a ref.
-if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
        newrev_type=delete
 else
        newrev_type=$(git-cat-file -t $newrev)
@@ -78,6 +85,10 @@ case "$refname","$newrev_type" in
                ;;
        refs/heads/*,commit)
                # branch
+               if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+                       echo "*** Creating a branch is not allowed in this repository" >&2
+                       exit 1
+               fi
                ;;
        refs/heads/*,delete)
                # delete branch
index c6f25e80b8bcf0a21db2bea368b9e444c19bc0bf..498b267a8c7812490d6479839c5577eaaec79d62 100644 (file)
@@ -1 +1 @@
-Unnamed repository; edit this file to name it for gitweb.
+Unnamed repository; edit this file 'description' to name the repository.
diff --git a/test-ctype.c b/test-ctype.c
new file mode 100644 (file)
index 0000000..033c749
--- /dev/null
@@ -0,0 +1,78 @@
+#include "cache.h"
+
+
+static int test_isdigit(int c)
+{
+       return isdigit(c);
+}
+
+static int test_isspace(int c)
+{
+       return isspace(c);
+}
+
+static int test_isalpha(int c)
+{
+       return isalpha(c);
+}
+
+static int test_isalnum(int c)
+{
+       return isalnum(c);
+}
+
+static int test_is_glob_special(int c)
+{
+       return is_glob_special(c);
+}
+
+static int test_is_regex_special(int c)
+{
+       return is_regex_special(c);
+}
+
+#define DIGIT "0123456789"
+#define LOWER "abcdefghijklmnopqrstuvwxyz"
+#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
+static const struct ctype_class {
+       const char *name;
+       int (*test_fn)(int);
+       const char *members;
+} classes[] = {
+       { "isdigit", test_isdigit, DIGIT },
+       { "isspace", test_isspace, " \n\r\t" },
+       { "isalpha", test_isalpha, LOWER UPPER },
+       { "isalnum", test_isalnum, LOWER UPPER DIGIT },
+       { "is_glob_special", test_is_glob_special, "*?[\\" },
+       { "is_regex_special", test_is_regex_special, "$()*+.?[\\^{|" },
+       { NULL }
+};
+
+static int test_class(const struct ctype_class *test)
+{
+       int i, rc = 0;
+
+       for (i = 0; i < 256; i++) {
+               int expected = i ? !!strchr(test->members, i) : 0;
+               int actual = test->test_fn(i);
+
+               if (actual != expected) {
+                       rc = 1;
+                       printf("%s classifies char %d (0x%02x) wrongly\n",
+                              test->name, i, i);
+               }
+       }
+       return rc;
+}
+
+int main(int argc, char **argv)
+{
+       const struct ctype_class *test;
+       int rc = 0;
+
+       for (test = classes; test->name; test++)
+               rc |= test_class(test);
+
+       return rc;
+}
index 5168a8e3dfd5150de8def87980118e31e81296e7..d261398d6c50aeefa7450b99caae23ee5cdff0d0 100644 (file)
@@ -26,6 +26,12 @@ int main(int argc, char **argv)
                return 0;
        }
 
+       if (argc == 4 && !strcmp(argv[1], "strip_path_suffix")) {
+               char *prefix = strip_path_suffix(argv[2], argv[3]);
+               printf("%s\n", prefix ? prefix : "(null)");
+               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..efecb65258ba95ee743f0e3ca178e74e2756e7d1 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);
@@ -140,7 +138,12 @@ static void insert_packed_refs(const char *packed_refs, struct ref **list)
        }
 }
 
-static struct ref *get_refs_via_rsync(struct transport *transport)
+static const char *rsync_url(const char *url)
+{
+       return prefixcmp(url, "rsync://") ? skip_prefix(url, "rsync:") : url;
+}
+
+static struct ref *get_refs_via_rsync(struct transport *transport, int for_push)
 {
        struct strbuf buf = STRBUF_INIT, temp_dir = STRBUF_INIT;
        struct ref dummy, *tail = &dummy;
@@ -148,6 +151,9 @@ static struct ref *get_refs_via_rsync(struct transport *transport)
        const char *args[5];
        int temp_dir_len;
 
+       if (for_push)
+               return NULL;
+
        /* copy the refs to the temporary directory */
 
        strbuf_addstr(&temp_dir, git_path("rsync-refs-XXXXXX"));
@@ -155,7 +161,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport)
                die ("Could not make temporary directory");
        temp_dir_len = temp_dir.len;
 
-       strbuf_addstr(&buf, transport->url);
+       strbuf_addstr(&buf, rsync_url(transport->url));
        strbuf_addstr(&buf, "/refs");
 
        memset(&rsync, 0, sizeof(rsync));
@@ -171,7 +177,7 @@ static struct ref *get_refs_via_rsync(struct transport *transport)
                die ("Could not run rsync to get refs");
 
        strbuf_reset(&buf);
-       strbuf_addstr(&buf, transport->url);
+       strbuf_addstr(&buf, rsync_url(transport->url));
        strbuf_addstr(&buf, "/packed-refs");
 
        args[2] = buf.buf;
@@ -208,7 +214,7 @@ static int fetch_objs_via_rsync(struct transport *transport,
        const char *args[8];
        int result;
 
-       strbuf_addstr(&buf, transport->url);
+       strbuf_addstr(&buf, rsync_url(transport->url));
        strbuf_addstr(&buf, "/objects/");
 
        memset(&rsync, 0, sizeof(rsync));
@@ -287,7 +293,7 @@ static int rsync_transport_push(struct transport *transport,
 
        /* first push the objects */
 
-       strbuf_addstr(&buf, transport->url);
+       strbuf_addstr(&buf, rsync_url(transport->url));
        strbuf_addch(&buf, '/');
 
        memset(&rsync, 0, sizeof(rsync));
@@ -308,7 +314,8 @@ static int rsync_transport_push(struct transport *transport,
        args[i++] = NULL;
 
        if (run_command(&rsync))
-               return error("Could not push objects to %s", transport->url);
+               return error("Could not push objects to %s",
+                               rsync_url(transport->url));
 
        /* copy the refs to the temporary directory; they could be packed. */
 
@@ -329,10 +336,11 @@ static int rsync_transport_push(struct transport *transport,
        if (!(flags & TRANSPORT_PUSH_FORCE))
                args[i++] = "--ignore-existing";
        args[i++] = temp_dir.buf;
-       args[i++] = transport->url;
+       args[i++] = rsync_url(transport->url);
        args[i++] = NULL;
        if (run_command(&rsync))
-               result = error("Could not push to %s", transport->url);
+               result = error("Could not push to %s",
+                               rsync_url(transport->url));
 
        if (remove_dir_recursively(&temp_dir, 0))
                warning ("Could not remove temporary directory %s.",
@@ -424,7 +432,7 @@ static int curl_transport_push(struct transport *transport, int refspec_nr, cons
        return !!err;
 }
 
-static struct ref *get_refs_via_curl(struct transport *transport)
+static struct ref *get_refs_via_curl(struct transport *transport, int for_push)
 {
        struct strbuf buffer = STRBUF_INIT;
        char *data, *start, *mid;
@@ -441,6 +449,9 @@ static struct ref *get_refs_via_curl(struct transport *transport)
 
        struct walker *walker;
 
+       if (for_push)
+               return NULL;
+
        if (!transport->data)
                transport->data = get_http_walker(transport->url,
                                                transport->remote);
@@ -527,12 +538,15 @@ struct bundle_transport_data {
        struct bundle_header header;
 };
 
-static struct ref *get_refs_from_bundle(struct transport *transport)
+static struct ref *get_refs_from_bundle(struct transport *transport, int for_push)
 {
        struct bundle_transport_data *data = transport->data;
        struct ref *result = NULL;
        int i;
 
+       if (for_push)
+               return NULL;
+
        if (data->fd > 0)
                close(data->fd);
        data->fd = read_bundle_header(transport->url, &data->header);
@@ -573,6 +587,7 @@ struct git_transport_data {
        int fd[2];
        const char *uploadpack;
        const char *receivepack;
+       struct extra_have_objects extra_have;
 };
 
 static int set_git_option(struct transport *connection,
@@ -604,20 +619,23 @@ static int set_git_option(struct transport *connection,
        return 1;
 }
 
-static int connect_setup(struct transport *transport)
+static int connect_setup(struct transport *transport, int for_push, int verbose)
 {
        struct git_transport_data *data = transport->data;
-       data->conn = git_connect(data->fd, transport->url, data->uploadpack, 0);
+       data->conn = git_connect(data->fd, transport->url,
+                                for_push ? data->receivepack : data->uploadpack,
+                                verbose ? CONNECT_VERBOSE : 0);
        return 0;
 }
 
-static struct ref *get_refs_via_connect(struct transport *transport)
+static struct ref *get_refs_via_connect(struct transport *transport, int for_push)
 {
        struct git_transport_data *data = transport->data;
        struct ref *refs;
 
-       connect_setup(transport);
-       get_remote_heads(data->fd[0], &refs, 0, NULL, 0, NULL);
+       connect_setup(transport, for_push, 0);
+       get_remote_heads(data->fd[0], &refs, 0, NULL,
+                        for_push ? REF_NORMAL : 0, &data->extra_have);
 
        return refs;
 }
@@ -649,7 +667,7 @@ static int fetch_refs_via_pack(struct transport *transport,
                origh[i] = heads[i] = xstrdup(to_fetch[i]->name);
 
        if (!data->conn) {
-               connect_setup(transport);
+               connect_setup(transport, 0, 0);
                get_remote_heads(data->fd[0], &refs_tmp, 0, NULL, 0, NULL);
        }
 
@@ -672,20 +690,216 @@ static int fetch_refs_via_pack(struct transport *transport,
        return (refs ? 0 : -1);
 }
 
-static int git_transport_push(struct transport *transport, int refspec_nr, const char **refspec, int flags)
+static int refs_pushed(struct ref *ref)
+{
+       for (; ref; ref = ref->next) {
+               switch(ref->status) {
+               case REF_STATUS_NONE:
+               case REF_STATUS_UPTODATE:
+                       break;
+               default:
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static void update_tracking_ref(struct remote *remote, struct ref *ref, int verbose)
+{
+       struct refspec rs;
+
+       if (ref->status != REF_STATUS_OK && ref->status != REF_STATUS_UPTODATE)
+               return;
+
+       rs.src = ref->name;
+       rs.dst = NULL;
+
+       if (!remote_find_tracking(remote, &rs)) {
+               if (verbose)
+                       fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
+               if (ref->deletion) {
+                       delete_ref(rs.dst, NULL, 0);
+               } else
+                       update_ref("update by push", rs.dst,
+                                       ref->new_sha1, NULL, 0, 0);
+               free(rs.dst);
+       }
+}
+
+#define SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
+
+static void print_ref_status(char flag, const char *summary, struct ref *to, struct ref *from, const char *msg)
+{
+       fprintf(stderr, " %c %-*s ", flag, SUMMARY_WIDTH, summary);
+       if (from)
+               fprintf(stderr, "%s -> %s", prettify_ref(from), prettify_ref(to));
+       else
+               fputs(prettify_ref(to), stderr);
+       if (msg) {
+               fputs(" (", stderr);
+               fputs(msg, stderr);
+               fputc(')', stderr);
+       }
+       fputc('\n', stderr);
+}
+
+static const char *status_abbrev(unsigned char sha1[20])
+{
+       return find_unique_abbrev(sha1, DEFAULT_ABBREV);
+}
+
+static void print_ok_ref_status(struct ref *ref)
+{
+       if (ref->deletion)
+               print_ref_status('-', "[deleted]", ref, NULL, NULL);
+       else if (is_null_sha1(ref->old_sha1))
+               print_ref_status('*',
+                       (!prefixcmp(ref->name, "refs/tags/") ? "[new tag]" :
+                         "[new branch]"),
+                       ref, ref->peer_ref, NULL);
+       else {
+               char quickref[84];
+               char type;
+               const char *msg;
+
+               strcpy(quickref, status_abbrev(ref->old_sha1));
+               if (ref->nonfastforward) {
+                       strcat(quickref, "...");
+                       type = '+';
+                       msg = "forced update";
+               } else {
+                       strcat(quickref, "..");
+                       type = ' ';
+                       msg = NULL;
+               }
+               strcat(quickref, status_abbrev(ref->new_sha1));
+
+               print_ref_status(type, quickref, ref, ref->peer_ref, msg);
+       }
+}
+
+static int print_one_push_status(struct ref *ref, const char *dest, int count)
+{
+       if (!count)
+               fprintf(stderr, "To %s\n", dest);
+
+       switch(ref->status) {
+       case REF_STATUS_NONE:
+               print_ref_status('X', "[no match]", ref, NULL, NULL);
+               break;
+       case REF_STATUS_REJECT_NODELETE:
+               print_ref_status('!', "[rejected]", ref, NULL,
+                               "remote does not support deleting refs");
+               break;
+       case REF_STATUS_UPTODATE:
+               print_ref_status('=', "[up to date]", ref,
+                               ref->peer_ref, NULL);
+               break;
+       case REF_STATUS_REJECT_NONFASTFORWARD:
+               print_ref_status('!', "[rejected]", ref, ref->peer_ref,
+                               "non-fast forward");
+               break;
+       case REF_STATUS_REMOTE_REJECT:
+               print_ref_status('!', "[remote rejected]", ref,
+                               ref->deletion ? NULL : ref->peer_ref,
+                               ref->remote_status);
+               break;
+       case REF_STATUS_EXPECTING_REPORT:
+               print_ref_status('!', "[remote failure]", ref,
+                               ref->deletion ? NULL : ref->peer_ref,
+                               "remote failed to report status");
+               break;
+       case REF_STATUS_OK:
+               print_ok_ref_status(ref);
+               break;
+       }
+
+       return 1;
+}
+
+static void print_push_status(const char *dest, struct ref *refs, int verbose)
+{
+       struct ref *ref;
+       int n = 0;
+
+       if (verbose) {
+               for (ref = refs; ref; ref = ref->next)
+                       if (ref->status == REF_STATUS_UPTODATE)
+                               n += print_one_push_status(ref, dest, n);
+       }
+
+       for (ref = refs; ref; ref = ref->next)
+               if (ref->status == REF_STATUS_OK)
+                       n += print_one_push_status(ref, dest, n);
+
+       for (ref = refs; ref; ref = ref->next) {
+               if (ref->status != REF_STATUS_NONE &&
+                   ref->status != REF_STATUS_UPTODATE &&
+                   ref->status != REF_STATUS_OK)
+                       n += print_one_push_status(ref, dest, n);
+       }
+}
+
+static void verify_remote_names(int nr_heads, const char **heads)
+{
+       int i;
+
+       for (i = 0; i < nr_heads; i++) {
+               const char *local = heads[i];
+               const char *remote = strrchr(heads[i], ':');
+
+               if (*local == '+')
+                       local++;
+
+               /* A matching refspec is okay.  */
+               if (remote == local && remote[1] == '\0')
+                       continue;
+
+               remote = remote ? (remote + 1) : local;
+               switch (check_ref_format(remote)) {
+               case 0: /* ok */
+               case CHECK_REF_FORMAT_ONELEVEL:
+                       /* ok but a single level -- that is fine for
+                        * a match pattern.
+                        */
+               case CHECK_REF_FORMAT_WILDCARD:
+                       /* ok but ends with a pattern-match character */
+                       continue;
+               }
+               die("remote part of refspec is not a valid name in %s",
+                   heads[i]);
+       }
+}
+
+static int git_transport_push(struct transport *transport, struct ref *remote_refs, int flags)
 {
        struct git_transport_data *data = transport->data;
        struct send_pack_args args;
+       int ret;
+
+       if (!data->conn) {
+               struct ref *tmp_refs;
+               connect_setup(transport, 1, 0);
+
+               get_remote_heads(data->fd[0], &tmp_refs, 0, NULL, REF_NORMAL,
+                                NULL);
+       }
 
-       args.receivepack = data->receivepack;
-       args.send_all = !!(flags & TRANSPORT_PUSH_ALL);
        args.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);
        args.force_update = !!(flags & TRANSPORT_PUSH_FORCE);
        args.use_thin_pack = data->thin;
        args.verbose = !!(flags & TRANSPORT_PUSH_VERBOSE);
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
 
-       return send_pack(&args, transport->url, transport->remote, refspec_nr, refspec);
+       ret = send_pack(&args, data->fd, data->conn, remote_refs,
+                       &data->extra_have);
+
+       close(data->fd[1]);
+       close(data->fd[0]);
+       ret |= finish_connect(data->conn);
+       data->conn = NULL;
+
+       return ret;
 }
 
 static int disconnect_git(struct transport *transport)
@@ -725,7 +939,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
        ret->remote = remote;
        ret->url = url;
 
-       if (!prefixcmp(url, "rsync://")) {
+       if (!prefixcmp(url, "rsync:")) {
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
@@ -755,7 +969,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
                ret->set_option = set_git_option;
                ret->get_refs_list = get_refs_via_connect;
                ret->fetch = fetch_refs_via_pack;
-               ret->push = git_transport_push;
+               ret->push_refs = git_transport_push;
                ret->disconnect = disconnect_git;
 
                data->thin = 1;
@@ -782,15 +996,53 @@ int transport_set_option(struct transport *transport,
 int transport_push(struct transport *transport,
                   int refspec_nr, const char **refspec, int flags)
 {
-       if (!transport->push)
-               return 1;
-       return transport->push(transport, refspec_nr, refspec, flags);
+       verify_remote_names(refspec_nr, refspec);
+
+       if (transport->push)
+               return transport->push(transport, refspec_nr, refspec, flags);
+       if (transport->push_refs) {
+               struct ref *remote_refs =
+                       transport->get_refs_list(transport, 1);
+               struct ref **remote_tail;
+               struct ref *local_refs = get_local_heads();
+               int match_flags = MATCH_REFS_NONE;
+               int verbose = flags & TRANSPORT_PUSH_VERBOSE;
+               int ret;
+
+               if (flags & TRANSPORT_PUSH_ALL)
+                       match_flags |= MATCH_REFS_ALL;
+               if (flags & TRANSPORT_PUSH_MIRROR)
+                       match_flags |= MATCH_REFS_MIRROR;
+
+               remote_tail = &remote_refs;
+               while (*remote_tail)
+                       remote_tail = &((*remote_tail)->next);
+               if (match_refs(local_refs, remote_refs, &remote_tail,
+                              refspec_nr, refspec, match_flags)) {
+                       return -1;
+               }
+
+               ret = transport->push_refs(transport, remote_refs, flags);
+
+               print_push_status(transport->url, remote_refs, verbose);
+
+               if (!(flags & TRANSPORT_PUSH_DRY_RUN)) {
+                       struct ref *ref;
+                       for (ref = remote_refs; ref; ref = ref->next)
+                               update_tracking_ref(transport->remote, ref, verbose);
+               }
+
+               if (!ret && !refs_pushed(remote_refs))
+                       fprintf(stderr, "Everything up-to-date\n");
+               return ret;
+       }
+       return 1;
 }
 
 const struct ref *transport_get_remote_refs(struct transport *transport)
 {
        if (!transport->remote_refs)
-               transport->remote_refs = transport->get_refs_list(transport);
+               transport->remote_refs = transport->get_refs_list(transport, 0);
        return transport->remote_refs;
 }
 
@@ -817,7 +1069,7 @@ int transport_fetch_refs(struct transport *transport, const struct ref *refs)
 void transport_unlock_pack(struct transport *transport)
 {
        if (transport->pack_lockfile) {
-               unlink(transport->pack_lockfile);
+               unlink_or_warn(transport->pack_lockfile);
                free(transport->pack_lockfile);
                transport->pack_lockfile = NULL;
        }
index 6bbc1a82642ab9e5722cfe6ab34ec4246b3a9dd4..b1c225276619c3bc4dda5dcd0469e242ab421ea0 100644 (file)
@@ -18,8 +18,9 @@ struct transport {
        int (*set_option)(struct transport *connection, const char *name,
                          const char *value);
 
-       struct ref *(*get_refs_list)(struct transport *transport);
+       struct ref *(*get_refs_list)(struct transport *transport, int for_push);
        int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
+       int (*push_refs)(struct transport *transport, struct ref *refs, int flags);
        int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
 
        int (*disconnect)(struct transport *connection);
index b05d0f43555d28f872fd5225e6773eeb636ef302..edd83949bf0896bedff4cab8e2b037eba574a45e 100644 (file)
@@ -374,7 +374,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
        }
 
        /*
-        * Then, discard all the non-relevane file pairs...
+        * Then, discard all the non-relevant file pairs...
         */
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
diff --git a/tree.c b/tree.c
index d82a047e5550702bf6e1fd5a8930897124e7a96f..5ab90af256a664366f3f92b467f52634c0df3f79 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -62,6 +62,7 @@ static int match_tree_entry(const char *base, int baselen, const char *path, uns
                                continue;
                        /* pathspecs match only at the directory boundaries */
                        if (!matchlen ||
+                           baselen == matchlen ||
                            base[matchlen] == '/' ||
                            match[matchlen - 1] == '/')
                                return 1;
@@ -114,7 +115,7 @@ int read_tree_recursive(struct tree *tree,
                case 0:
                        continue;
                case READ_TREE_RECURSIVE:
-                       break;;
+                       break;
                default:
                        return -1;
                }
@@ -135,6 +136,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;
diff --git a/unimplemented.sh b/unimplemented.sh
new file mode 100644 (file)
index 0000000..5252de4
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+echo >&2 "fatal: git was built without support for `basename $0` (@@REASON@@)."
+exit 128
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..aaacaf1015ccf1f353151982a3018ad349663d76 100644 (file)
@@ -7,6 +7,7 @@
 #include "unpack-trees.h"
 #include "progress.h"
 #include "refs.h"
+#include "attr.h"
 
 /*
  * Error messages expected by scripts out of plumbing commands such as
@@ -49,39 +50,20 @@ static void add_entry(struct unpack_trees_options *o, struct cache_entry *ce,
        memcpy(new, ce, size);
        new->next = NULL;
        new->ce_flags = (new->ce_flags & ~clear) | set;
-       add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE|ADD_CACHE_SKIP_DFCHECK);
+       add_index_entry(&o->result, new, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
 }
 
-/* Unlink the last component and attempt to remove leading
- * directories, in case this unlink is the removal of the
- * last entry in the directory -- empty directories are removed.
+/*
+ * Unlink the last component and schedule the leading directories for
+ * removal, such that empty directories get removed.
  */
 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->name, ce_namelen(ce)))
                return;
-       if (unlink(name))
+       if (unlink_or_warn(ce->name))
                return;
-       prev = NULL;
-       while (1) {
-               int status;
-               cp = strrchr(name, '/');
-               if (prev)
-                       *prev = '/';
-               if (!cp)
-                       break;
-
-               *cp = 0;
-               status = rmdir(name);
-               if (status) {
-                       *cp = '/';
-                       break;
-               }
-               prev = cp;
-       }
+       schedule_dir_for_removal(ce->name, ce_namelen(ce));
 }
 
 static struct checkout state;
@@ -105,6 +87,8 @@ static int check_updates(struct unpack_trees_options *o)
                cnt = 0;
        }
 
+       if (o->update)
+               git_attr_set_direction(GIT_ATTR_CHECKOUT, &o->result);
        for (i = 0; i < index->cache_nr; i++) {
                struct cache_entry *ce = index->cache[i];
 
@@ -112,11 +96,10 @@ static int check_updates(struct unpack_trees_options *o)
                        display_progress(progress, ++cnt);
                        if (o->update)
                                unlink_entry(ce);
-                       remove_index_entry_at(&o->result, i);
-                       i--;
-                       continue;
                }
        }
+       remove_marked_cache_entries(&o->result);
+       remove_scheduled_dirs();
 
        for (i = 0; i < index->cache_nr; i++) {
                struct cache_entry *ce = index->cache[i];
@@ -130,6 +113,8 @@ static int check_updates(struct unpack_trees_options *o)
                }
        }
        stop_progress(&progress);
+       if (o->update)
+               git_attr_set_direction(GIT_ATTR_CHECKIN, NULL);
        return errs != 0;
 }
 
@@ -286,9 +271,9 @@ static int unpack_nondirectories(int n, unsigned long mask,
        if (o->merge)
                return call_unpack_fn(src, o);
 
-       n += o->merge;
        for (i = 0; i < n; i++)
-               add_entry(o, src[i], 0, 0);
+               if (src[i] && src[i] != o->df_conflict_entry)
+                       add_entry(o, src[i], 0, 0);
        return 0;
 }
 
@@ -380,8 +365,10 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
 
        memset(&o->result, 0, sizeof(o->result));
        o->result.initialized = 1;
-       if (o->src_index)
-               o->result.timestamp = o->src_index->timestamp;
+       if (o->src_index) {
+               o->result.timestamp.sec = o->src_index->timestamp.sec;
+               o->result.timestamp.nsec = o->src_index->timestamp.nsec;
+       }
        o->merge_size = len;
 
        if (!dfc)
@@ -446,7 +433,7 @@ static int verify_uptodate(struct cache_entry *ce,
 {
        struct stat st;
 
-       if (o->index_only || o->reset)
+       if (o->index_only || o->reset || ce_uptodate(ce))
                return 0;
 
        if (!lstat(ce->name, &st)) {
@@ -583,7 +570,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->name, ce_namelen(ce)))
                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..edc78612289c5bd774e18fe5f8e1b9ea80378a5f 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)
@@ -66,7 +66,7 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
 }
 
 static FILE *pack_pipe = NULL;
-static void show_commit(struct commit *commit)
+static void show_commit(struct commit *commit, void *data)
 {
        if (commit->object.flags & BOUNDARY)
                fputc('-', pack_pipe);
@@ -78,20 +78,22 @@ static void show_commit(struct commit *commit)
        commit->buffer = NULL;
 }
 
-static void show_object(struct object_array_entry *p)
+static void show_object(struct object *obj, const struct name_path *path, const char *component)
 {
        /* An object with name "foo\n0000000..." can be used to
         * confuse downstream git-pack-objects very badly.
         */
-       const char *ep = strchr(p->name, '\n');
+       const char *name = path_name(path, component);
+       const char *ep = strchr(name, '\n');
        if (ep) {
-               fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(p->item->sha1),
-                      (int) (ep - p->name),
-                      p->name);
+               fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(obj->sha1),
+                      (int) (ep - name),
+                      name);
        }
        else
                fprintf(pack_pipe, "%s %s\n",
-                               sha1_to_hex(p->item->sha1), p->name);
+                               sha1_to_hex(obj->sha1), name);
+       free((char *)name);
 }
 
 static void show_edge(struct commit *commit)
@@ -134,7 +136,7 @@ static int do_rev_list(int fd, void *create_full_pack)
        if (prepare_revision_walk(&revs))
                die("revision walk setup failed");
        mark_edges_uninteresting(revs.commits, &revs, show_edge);
-       traverse_commit_list(&revs, show_commit, show_object);
+       traverse_commit_list(&revs, show_commit, show_object, NULL);
        fflush(pack_pipe);
        fclose(pack_pipe);
        return 0;
@@ -397,12 +399,11 @@ static int get_common_commits(void)
        static char line[1000];
        unsigned char sha1[20];
        char hex[41], last_hex[41];
-       int len;
 
        save_commit_buffer = 0;
 
        for(;;) {
-               len = packet_read_line(0, line, sizeof(line));
+               int len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
 
                if (!len) {
@@ -410,7 +411,7 @@ static int get_common_commits(void)
                                packet_write(1, "NAK\n");
                        continue;
                }
-               len = strip(line, len);
+               strip(line, len);
                if (!prefixcmp(line, "have ")) {
                        switch (got_sha1(line+5, sha1)) {
                        case -1: /* they have what we do not */
@@ -616,6 +617,8 @@ int main(int argc, char **argv)
        int i;
        int strict = 0;
 
+       git_extract_argv0_path(argv[0]);
+
        for (i = 1; i < argc; i++) {
                char *arg = argv[i];
 
@@ -643,7 +646,7 @@ int main(int argc, char **argv)
        dir = argv[i];
 
        if (!enter_repo(dir, strict))
-               die("'%s': unable to chdir or not a git archive", dir);
+               die("'%s' does not appear to be a git repository", dir);
        if (is_repository_shallow())
                die("attempt to fetch/clone from a shallow repository");
        if (getenv("GIT_DEBUG_SEND_PACK"))
diff --git a/usage.c b/usage.c
index 24f5fc00c2501c1a1cc317f0b74b458ea50c8b0e..820d09f92b03b6cc96fbe9a954c37fd2d5e5a417 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -7,7 +7,7 @@
 
 static void report(const char *prefix, const char *err, va_list params)
 {
-       char msg[256];
+       char msg[1024];
        vsnprintf(msg, sizeof(msg), err, params);
        fprintf(stderr, "%s%s\n", prefix, msg);
 }
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/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 d8efb1365a01104db568633fa8f6aef0c67b4cd1..7eb3218ee995991881252e4bb599452d7214ae4f 100644 (file)
--- a/wrapper.c
+++ b/wrapper.c
@@ -289,3 +289,19 @@ int odb_pack_keep(char *name, size_t namesz, unsigned char *sha1)
        safe_create_leading_directories(name);
        return open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
 }
+
+int unlink_or_warn(const char *file)
+{
+       int rc = unlink(file);
+
+       if (rc < 0) {
+               int err = errno;
+               if (ENOENT != err) {
+                       warning("unable to unlink %s: %s",
+                               file, strerror(errno));
+                       errno = err;
+               }
+       }
+       return rc;
+}
+
index 96ff2f8f564b907b9ef77bd2ea41b5e854a13085..1b6df45450c39c0f456df68485b5735950816ac8 100644 (file)
@@ -15,11 +15,11 @@ int wt_status_relative_paths = 1;
 int wt_status_use_color = -1;
 int wt_status_submodule_summary;
 static char wt_status_colors[][COLOR_MAXLEN] = {
-       "",         /* WT_STATUS_HEADER: normal */
-       "\033[32m", /* WT_STATUS_UPDATED: green */
-       "\033[31m", /* WT_STATUS_CHANGED: red */
-       "\033[31m", /* WT_STATUS_UNTRACKED: red */
-       "\033[31m", /* WT_STATUS_NOBRANCH: red */
+       GIT_COLOR_NORMAL, /* WT_STATUS_HEADER */
+       GIT_COLOR_GREEN,  /* WT_STATUS_UPDATED */
+       GIT_COLOR_RED,    /* WT_STATUS_CHANGED */
+       GIT_COLOR_RED,    /* WT_STATUS_UNTRACKED */
+       GIT_COLOR_RED,    /* WT_STATUS_NOBRANCH */
 };
 
 enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
@@ -40,7 +40,7 @@ static int parse_status_slot(const char *var, int offset)
        die("bad config variable '%s'", var);
 }
 
-static const charcolor(int slot)
+static const char *color(int slot)
 {
        return wt_status_use_color > 0 ? wt_status_colors[slot] : "";
 }
@@ -250,10 +250,9 @@ static void wt_status_print_untracked(struct wt_status *s)
 
        memset(&dir, 0, sizeof(dir));
 
-       if (!s->untracked) {
-               dir.show_other_directories = 1;
-               dir.hide_empty_directories = 1;
-       }
+       if (!s->untracked)
+               dir.flags |=
+                       DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
        setup_standard_excludes(&dir);
 
        read_directory(&dir, ".", "", 0, NULL);
index d782f06d9916bdde33dc3f7312f9eac4e14ef3a1..b9b0db8d86615d6ca1046b932772f3d9750a8062 100644 (file)
@@ -15,11 +15,10 @@ static int parse_num(char **cp_p, int *num_p)
 {
        char *cp = *cp_p;
        int num = 0;
-       int read_some;
 
        while ('0' <= *cp && *cp <= '9')
                num = num * 10 + *cp++ - '0';
-       if (!(read_some = cp - *cp_p))
+       if (!(cp - *cp_p))
                return -1;
        *cp_p = cp;
        *num_p = num;
index 84fff583e2a6e3411da4260b9af98043fade1e49..4da052a3fff6768fca05b2f3399c5d87ec1daa2e 100644 (file)
@@ -32,6 +32,7 @@ extern "C" {
 #define XDF_IGNORE_WHITESPACE (1 << 2)
 #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3)
 #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4)
+#define XDF_PATIENCE_DIFF (1 << 5)
 #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL)
 
 #define XDL_PATCH_NORMAL '-'
@@ -84,6 +85,7 @@ typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long
 
 typedef struct s_xdemitconf {
        long ctxlen;
+       long interhunkctxlen;
        unsigned long flags;
        find_func_t find_func;
        void *find_func_priv;
index 9d0324a38c2a1974648e67161fa0ed1b0f811233..1ebab687f77218d1520a4527214170eeab176d31 100644 (file)
@@ -293,15 +293,14 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
                for (; off1 < lim1; off1++)
                        rchg1[rindex1[off1]] = 1;
        } else {
-               long ec;
                xdpsplit_t spl;
                spl.i1 = spl.i2 = 0;
 
                /*
                 * Divide ...
                 */
-               if ((ec = xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb,
-                                   need_min, &spl, xenv)) < 0) {
+               if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb,
+                             need_min, &spl, xenv) < 0) {
 
                        return -1;
                }
@@ -329,6 +328,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;
@@ -454,7 +456,7 @@ int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
                        /*
                         * Record the end-of-group position in case we are matched
                         * with a group of changes in the other file (that is, the
-                        * change record before the enf-of-group index in the other
+                        * change record before the end-of-group index in the other
                         * file is set).
                         */
                        ixref = rchgo[ixo - 1] ? ix: nrec;
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..c4bedf0d1ce1252563d7f36da7d846f5943343b0 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;
@@ -131,7 +132,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
        if (xecfg->flags & XDL_EMIT_COMMON)
                return xdl_emit_common(xe, xscr, ecb, xecfg);
 
-       for (xch = xche = xscr; xch; xch = xche->next) {
+       for (xch = xscr; xch; xch = xche->next) {
                xche = xdl_get_hunk(xch, xecfg);
 
                s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
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);