Code

Merge branch 'maint' to sync with 1.5.2.5
authorJunio C Hamano <gitster@pobox.com>
Thu, 16 Aug 2007 04:38:38 +0000 (21:38 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 16 Aug 2007 04:38:38 +0000 (21:38 -0700)
* maint:
  GIT 1.5.2.5
  git-add -u paths... now works from subdirectory
  Fix "git add -u" data corruption.

611 files changed:
.gitignore
.mailmap
Documentation/Makefile
Documentation/RelNotes-1.5.0.4.txt
Documentation/RelNotes-1.5.0.5.txt
Documentation/RelNotes-1.5.0.6.txt
Documentation/RelNotes-1.5.1.3.txt
Documentation/RelNotes-1.5.3.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/asciidoc.conf
Documentation/blame-options.txt
Documentation/cmd-list.perl
Documentation/config.txt
Documentation/core-intro.txt
Documentation/core-tutorial.txt
Documentation/diff-format.txt
Documentation/diff-options.txt
Documentation/diffcore.txt
Documentation/docbook-xsl.css
Documentation/docbook.xsl [new file with mode: 0644]
Documentation/fetch-options.txt
Documentation/fix-texi.perl [new file with mode: 0755]
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-applymbox.txt [deleted file]
Documentation/git-applypatch.txt [deleted file]
Documentation/git-archimport.txt
Documentation/git-bisect.txt
Documentation/git-blame.txt
Documentation/git-branch.txt
Documentation/git-cat-file.txt
Documentation/git-check-attr.txt
Documentation/git-checkout-index.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-cherry.txt
Documentation/git-citool.txt [new file with mode: 0644]
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-config.txt
Documentation/git-convert-objects.txt
Documentation/git-count-objects.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-cvsimport.txt
Documentation/git-cvsserver.txt
Documentation/git-daemon.txt
Documentation/git-describe.txt
Documentation/git-diff-files.txt
Documentation/git-diff-index.txt
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
Documentation/git-fast-import.txt
Documentation/git-fetch.txt
Documentation/git-filter-branch.txt [new file with mode: 0644]
Documentation/git-fmt-merge-msg.txt
Documentation/git-format-patch.txt
Documentation/git-fsck.txt
Documentation/git-gc.txt
Documentation/git-get-tar-commit-id.txt
Documentation/git-grep.txt
Documentation/git-gui.txt [new file with mode: 0644]
Documentation/git-hash-object.txt
Documentation/git-http-fetch.txt
Documentation/git-http-push.txt
Documentation/git-index-pack.txt
Documentation/git-init-db.txt
Documentation/git-init.txt
Documentation/git-instaweb.txt
Documentation/git-local-fetch.txt
Documentation/git-log.txt
Documentation/git-ls-files.txt
Documentation/git-ls-remote.txt
Documentation/git-ls-tree.txt
Documentation/git-mailinfo.txt
Documentation/git-mailsplit.txt
Documentation/git-merge-base.txt
Documentation/git-merge-index.txt
Documentation/git-merge-one-file.txt
Documentation/git-merge-tree.txt
Documentation/git-merge.txt
Documentation/git-mergetool.txt
Documentation/git-mktag.txt
Documentation/git-mktree.txt
Documentation/git-mv.txt
Documentation/git-name-rev.txt
Documentation/git-p4import.txt [deleted file]
Documentation/git-pack-objects.txt
Documentation/git-pack-redundant.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune-packed.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-quiltimport.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-reflog.txt
Documentation/git-relink.txt
Documentation/git-remote.txt
Documentation/git-repack.txt
Documentation/git-request-pull.txt
Documentation/git-rerere.txt
Documentation/git-reset.txt
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-rm.txt
Documentation/git-runstatus.txt
Documentation/git-send-email.txt
Documentation/git-sh-setup.txt
Documentation/git-shell.txt
Documentation/git-shortlog.txt
Documentation/git-show-index.txt
Documentation/git-show.txt
Documentation/git-ssh-fetch.txt
Documentation/git-ssh-upload.txt
Documentation/git-stash.txt [new file with mode: 0644]
Documentation/git-status.txt
Documentation/git-stripspace.txt
Documentation/git-submodule.txt [new file with mode: 0644]
Documentation/git-svn.txt
Documentation/git-svnimport.txt
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-unpack-file.txt
Documentation/git-unpack-objects.txt
Documentation/git-update-index.txt
Documentation/git-update-ref.txt
Documentation/git-update-server-info.txt
Documentation/git-var.txt
Documentation/git-verify-pack.txt
Documentation/git-verify-tag.txt
Documentation/git-whatchanged.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitignore.txt
Documentation/gitk.txt
Documentation/gitmodules.txt [new file with mode: 0644]
Documentation/hooks.txt
Documentation/howto/rebase-and-edit.txt
Documentation/howto/rebase-from-internal-branch.txt
Documentation/howto/rebuild-from-update-hook.txt
Documentation/howto/revert-branch-rebase.txt
Documentation/howto/separating-topic-branches.txt
Documentation/howto/use-git-daemon.txt
Documentation/install-doc-quick.sh
Documentation/merge-options.txt
Documentation/pretty-formats.txt
Documentation/pretty-options.txt
Documentation/pull-fetch-param.txt
Documentation/repository-layout.txt
Documentation/technical/pack-format.txt
Documentation/tutorial.txt
Documentation/urls-remotes.txt [new file with mode: 0644]
Documentation/urls.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
INSTALL
Makefile
RelNotes
archive-tar.c
archive-zip.c
arm/sha1.c
arm/sha1_arm.S
attr.c
builtin-add.c
builtin-annotate.c
builtin-apply.c
builtin-archive.c
builtin-blame.c
builtin-branch.c
builtin-bundle.c
builtin-check-attr.c
builtin-checkout-index.c
builtin-config.c
builtin-count-objects.c
builtin-describe.c
builtin-diff-index.c
builtin-diff.c
builtin-fetch--tool.c
builtin-fmt-merge-msg.c
builtin-for-each-ref.c
builtin-fsck.c
builtin-gc.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-ls-tree.c
builtin-mailinfo.c
builtin-mailsplit.c
builtin-merge-base.c
builtin-merge-file.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-pack-refs.c
builtin-push.c
builtin-read-tree.c
builtin-reflog.c
builtin-rerere.c
builtin-rev-list.c
builtin-rev-parse.c
builtin-revert.c
builtin-rm.c
builtin-runstatus.c
builtin-shortlog.c
builtin-show-branch.c
builtin-show-ref.c
builtin-stripspace.c
builtin-symbolic-ref.c
builtin-tag.c [new file with mode: 0644]
builtin-update-index.c
builtin-update-ref.c
builtin-verify-tag.c [new file with mode: 0644]
builtin-write-tree.c
builtin.h
cache-tree.c
cache.h
combine-diff.c
commit.c
commit.h
compat/mmap.c
config.c
config.mak.in
configure.ac
connect.c
contrib/README
contrib/blameview/README
contrib/completion/git-completion.bash
contrib/emacs/Makefile
contrib/emacs/git.el
contrib/emacs/vc-git.el
contrib/examples/git-gc.sh
contrib/examples/git-resolve.sh
contrib/examples/git-tag.sh [new file with mode: 0755]
contrib/examples/git-verify-tag.sh [new file with mode: 0755]
contrib/fast-import/git-p4 [new file with mode: 0755]
contrib/fast-import/git-p4.bat [new file with mode: 0644]
contrib/fast-import/git-p4.txt [new file with mode: 0644]
contrib/gitview/gitview
contrib/hooks/post-receive-email
contrib/hooks/update-paranoid
contrib/p4import/README [new file with mode: 0644]
contrib/p4import/git-p4import.py [new file with mode: 0644]
contrib/p4import/git-p4import.txt [new file with mode: 0644]
contrib/patches/docbook-xsl-manpages-charmap.patch [new file with mode: 0644]
contrib/remotes2config.sh
contrib/stats/git-common-hash [new file with mode: 0755]
contrib/stats/mailmap.pl [new file with mode: 0755]
contrib/stats/packinfo.pl [new file with mode: 0755]
contrib/workdir/git-new-workdir
convert-objects.c
copy.c
csum-file.c
csum-file.h
ctype.c
daemon.c
date.c
delta.h
diff-delta.c
diff-lib.c
diff.c
diff.h
diffcore-break.c
diffcore-delta.c
diffcore-pickaxe.c
diffcore-rename.c
diffcore.h
dir.c
dir.h
entry.c
environment.c
fast-import.c
fetch-pack.c
fetch.c
fixup-builtins [new file with mode: 0755]
generate-cmdlist.sh
git-add--interactive.perl
git-am.sh
git-applymbox.sh [deleted file]
git-applypatch.sh [deleted file]
git-archimport.perl
git-bisect.sh
git-checkout.sh
git-clean.sh
git-clone.sh
git-commit.sh
git-compat-util.h
git-cvsexportcommit.perl
git-cvsimport.perl
git-cvsserver.perl
git-fetch.sh
git-filter-branch.sh [new file with mode: 0755]
git-gui/GIT-VERSION-GEN
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/blame.tcl
git-gui/lib/branch.tcl
git-gui/lib/branch_checkout.tcl [new file with mode: 0644]
git-gui/lib/branch_create.tcl [new file with mode: 0644]
git-gui/lib/branch_delete.tcl [new file with mode: 0644]
git-gui/lib/branch_rename.tcl [new file with mode: 0644]
git-gui/lib/browser.tcl
git-gui/lib/checkout_op.tcl [new file with mode: 0644]
git-gui/lib/choose_rev.tcl [new file with mode: 0644]
git-gui/lib/class.tcl
git-gui/lib/commit.tcl
git-gui/lib/console.tcl
git-gui/lib/database.tcl
git-gui/lib/diff.tcl
git-gui/lib/encoding.tcl [new file with mode: 0644]
git-gui/lib/error.tcl
git-gui/lib/index.tcl
git-gui/lib/merge.tcl
git-gui/lib/option.tcl
git-gui/lib/remote.tcl
git-gui/lib/remote_branch_delete.tcl [new file with mode: 0644]
git-gui/lib/shortcut.tcl
git-gui/lib/status_bar.tcl [new file with mode: 0644]
git-gui/lib/transport.tcl
git-instaweb.sh
git-lost-found.sh
git-ls-remote.sh
git-merge-octopus.sh
git-merge-one-file.sh
git-merge-ours.sh
git-merge-resolve.sh
git-merge-stupid.sh
git-merge.sh
git-mergetool.sh
git-p4import.py [deleted file]
git-parse-remote.sh
git-pull.sh
git-quiltimport.sh
git-rebase--interactive.sh [new file with mode: 0755]
git-rebase.sh
git-remote.perl
git-repack.sh
git-request-pull.sh
git-reset.sh
git-send-email.perl
git-sh-setup.sh
git-stash.sh [new file with mode: 0755]
git-submodule.sh [new file with mode: 0755]
git-svn.perl
git-svnimport.perl
git-tag.sh [deleted file]
git-verify-tag.sh [deleted file]
git.c
git.spec.in
gitk
gitweb/README
gitweb/gitweb.css
gitweb/gitweb.perl
help.c
http-fetch.c
http-push.c
http.c
ident.c
imap-send.c
index-pack.c
interpolate.c
interpolate.h
list-objects.c
local-fetch.c
lockfile.c
log-tree.c
mailmap.c
match-trees.c
merge-file.c
merge-index.c
merge-recursive.c
merge-tree.c
mktag.c
mozilla-sha1/sha1.c
mozilla-sha1/sha1.h
object-refs.c
object.h
pack-check.c
pack-redundant.c
pack-write.c
pack.h
pager.c
patch-id.c
path-list.c
path-list.h
path.c
peek-remote.c
perl/Makefile
pkt-line.c
ppc/sha1.c
progress.c
progress.h
quote.c
quote.h
reachable.c
read-cache.c
receive-pack.c
reflog-walk.c
reflog-walk.h
refs.c
refs.h
remote.c [new file with mode: 0644]
remote.h [new file with mode: 0644]
revision.c
revision.h
rsh.h
run-command.c
run-command.h
send-pack.c
server-info.c
setup.c
sha1_file.c
sha1_name.c
shallow.c
ssh-upload.c
strbuf.c
t/Makefile
t/lib-git-svn.sh
t/lib-read-tree-m-3way.sh
t/t0000-basic.sh
t/t0020-crlf.sh
t/t0022-crlf-rename.sh [new file with mode: 0755]
t/t0030-stripspace.sh [new file with mode: 0755]
t/t1000-read-tree-m-3way.sh
t/t1001-read-tree-m-2way.sh
t/t1002-read-tree-m-u-2way.sh
t/t1003-read-tree-prefix.sh
t/t1004-read-tree-m-u-wf.sh
t/t1020-subdirectory.sh
t/t1100-commit-tree-options.sh
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1301-shared-repo.sh [new file with mode: 0755]
t/t1400-update-ref.sh
t/t1420-lost-found.sh [new file with mode: 0755]
t/t1500-rev-parse.sh [new file with mode: 0755]
t/t1501-worktree.sh [new file with mode: 0755]
t/t2000-checkout-cache-clash.sh
t/t2001-checkout-cache-clash.sh
t/t2002-checkout-cache-u.sh
t/t2003-checkout-cache-mkdir.sh
t/t2004-checkout-cache-temp.sh
t/t2005-checkout-index-symlinks.sh
t/t2050-git-dir-relative.sh [new file with mode: 0755]
t/t2100-update-cache-badpath.sh
t/t2101-update-index-reupdate.sh
t/t2102-update-index-symlinks.sh
t/t2200-add-update.sh
t/t3000-ls-files-others.sh
t/t3001-ls-files-others-exclude.sh
t/t3002-ls-files-dashpath.sh
t/t3010-ls-files-killed-modified.sh
t/t3020-ls-files-error-unmatch.sh
t/t3030-merge-recursive.sh
t/t3100-ls-tree-restrict.sh
t/t3101-ls-tree-dirname.sh
t/t3200-branch.sh
t/t3210-pack-refs.sh
t/t3300-funny-names.sh
t/t3400-rebase.sh
t/t3401-rebase-partial.sh
t/t3403-rebase-skip.sh
t/t3404-rebase-interactive.sh [new file with mode: 0755]
t/t3405-rebase-malformed.sh [new file with mode: 0755]
t/t3500-cherry.sh
t/t3600-rm.sh
t/t3700-add.sh
t/t3800-mktag.sh
t/t3900-i18n-commit.sh
t/t3901-i18n-patch.sh
t/t3902-quoted.sh [new file with mode: 0755]
t/t3903-stash.sh [new file with mode: 0755]
t/t4000-diff-format.sh
t/t4001-diff-rename.sh
t/t4002-diff-basic.sh
t/t4003-diff-rename-1.sh
t/t4004-diff-rename-symlink.sh
t/t4005-diff-rename-2.sh
t/t4006-diff-mode.sh
t/t4007-rename-3.sh
t/t4008-diff-break-rewrite.sh
t/t4009-diff-rename-4.sh
t/t4010-diff-pathspec.sh
t/t4011-diff-symlink.sh
t/t4012-diff-binary.sh
t/t4013-diff-various.sh
t/t4013/diff.config_format.subjectprefix_DIFFERENT_PREFIX [new file with mode: 0644]
t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ [new file with mode: 0644]
t/t4015-diff-whitespace.sh
t/t4018-diff-funcname.sh [new file with mode: 0644]
t/t4020-diff-external.sh
t/t4100-apply-stat.sh
t/t4101-apply-nonl.sh
t/t4102-apply-rename.sh
t/t4103-apply-binary.sh
t/t4104-apply-boundary.sh
t/t4109-apply-multifrag.sh
t/t4110-apply-scan.sh
t/t4112-apply-renames.sh
t/t4113-apply-ending.sh
t/t4114-apply-typechange.sh
t/t4115-apply-symlink.sh
t/t4116-apply-reverse.sh
t/t4117-apply-reject.sh
t/t4118-apply-empty-context.sh
t/t4119-apply-config.sh
t/t4120-apply-popt.sh
t/t4121-apply-diffs.sh
t/t4122-apply-symlink-inside.sh
t/t4200-rerere.sh
t/t4201-shortlog.sh
t/t5000-tar-tree.sh
t/t5100-mailinfo.sh
t/t5300-pack-object.sh
t/t5301-sliding-window.sh
t/t5302-pack-index.sh
t/t5400-send-pack.sh
t/t5401-update-hooks.sh
t/t5500-fetch-pack.sh
t/t5510-fetch.sh
t/t5516-fetch-push.sh [new file with mode: 0755]
t/t5520-pull.sh
t/t5700-clone-reference.sh
t/t5701-clone-local.sh
t/t5710-info-alternate.sh
t/t6000lib.sh
t/t6002-rev-list-bisect.sh
t/t6003-rev-list-topo-order.sh
t/t6004-rev-list-path-optim.sh
t/t6005-rev-list-count.sh
t/t6006-rev-list-format.sh
t/t6007-rev-list-cherry-pick-file.sh [new file with mode: 0755]
t/t6010-merge-base.sh
t/t6021-merge-criss-cross.sh
t/t6023-merge-file.sh
t/t6024-recursive-merge.sh
t/t6025-merge-symlinks.sh
t/t6027-merge-binary.sh [new file with mode: 0755]
t/t6030-bisect-porcelain.sh
t/t6101-rev-parse-parents.sh
t/t6120-describe.sh
t/t7001-mv.sh
t/t7003-filter-branch.sh [new file with mode: 0755]
t/t7004-tag.sh [new file with mode: 0755]
t/t7004/pubring.gpg [new file with mode: 0644]
t/t7004/random_seed [new file with mode: 0644]
t/t7004/secring.gpg [new file with mode: 0644]
t/t7004/trustdb.gpg [new file with mode: 0644]
t/t7005-editor.sh [new file with mode: 0755]
t/t7101-reset.sh
t/t7300-clean.sh
t/t7400-submodule-basic.sh [new file with mode: 0755]
t/t7500-commit.sh [new file with mode: 0755]
t/t7500/add-comments [new file with mode: 0755]
t/t7500/add-content [new file with mode: 0755]
t/t7500/add-signed-off [new file with mode: 0755]
t/t7501-commit.sh [new file with mode: 0644]
t/t8001-annotate.sh
t/t8002-blame.sh
t/t9100-git-svn-basic.sh
t/t9104-git-svn-follow-parent.sh
t/t9107-git-svn-migrate.sh
t/t9110-git-svn-use-svm-props.sh
t/t9111-git-svn-use-svnsync-props.sh
t/t9111/svnsync.dump
t/t9113-git-svn-dcommit-new-file.sh [new file with mode: 0755]
t/t9114-git-svn-dcommit-merge.sh [new file with mode: 0755]
t/t9115-git-svn-dcommit-funky-renames.sh [new file with mode: 0755]
t/t9115/funky-names.dump [new file with mode: 0644]
t/t9116-git-svn-log.sh [new file with mode: 0755]
t/t9200-git-cvsexportcommit.sh
t/t9300-fast-import.sh
t/t9400-git-cvsserver-server.sh
t/t9500-gitweb-standalone-no-errors.sh [new file with mode: 0755]
t/test-lib.sh
templates/Makefile
templates/hooks--commit-msg
templates/hooks--post-receive
templates/hooks--pre-applypatch
templates/hooks--pre-commit
test-absolute-path.c [new file with mode: 0644]
tree-diff.c
tree-walk.c
tree-walk.h
tree.c
unpack-file.c
unpack-trees.c
unpack-trees.h
upload-pack.c
var.c
write_or_die.c
wt-status.c
xdiff-interface.c
xdiff-interface.h
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xemit.c
xdiff/xemit.h
xdiff/xinclude.h
xdiff/xmacros.h
xdiff/xprepare.c
xdiff/xprepare.h
xdiff/xtypes.h
xdiff/xutils.c
xdiff/xutils.h

index b7546a5591c7e70f8b0b87f528215cbfdf878da1..63c918c667fa005ff12ad89437f2fdc80926e21c 100644 (file)
@@ -7,8 +7,6 @@ git-add--interactive
 git-am
 git-annotate
 git-apply
-git-applymbox
-git-applypatch
 git-archimport
 git-archive
 git-bisect
@@ -42,6 +40,7 @@ git-fast-import
 git-fetch
 git-fetch--tool
 git-fetch-pack
+git-filter-branch
 git-findtags
 git-fmt-merge-msg
 git-for-each-ref
@@ -97,6 +96,7 @@ git-push
 git-quiltimport
 git-read-tree
 git-rebase
+git-rebase--interactive
 git-receive-pack
 git-reflog
 git-relink
@@ -124,8 +124,10 @@ git-ssh-fetch
 git-ssh-pull
 git-ssh-push
 git-ssh-upload
+git-stash
 git-status
 git-stripspace
+git-submodule
 git-svn
 git-svnimport
 git-symbolic-ref
@@ -146,19 +148,21 @@ git-write-tree
 git-core-*/?*
 gitk-wish
 gitweb/gitweb.cgi
+test-absolute-path
 test-chmtime
 test-date
 test-delta
 test-dump-cache-tree
 test-genrandom
 test-match-trees
+test-sha1
 common-cmds.h
 *.tar.gz
 *.dsc
 *.deb
 git.spec
 *.exe
-*.[ao]
+*.[aos]
 *.py[co]
 config.mak
 autom4te.cache
index 4e0615e9beb06928e9440e3dd60014cc265f9ee5..5529b198e8d14decbe4ad99db3f7fb632de0439d 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -7,6 +7,8 @@
 
 Aneesh Kumar K.V <aneesh.kumar@gmail.com>
 Chris Shoemaker <c.shoemaker@cox.net>
+Dana L. How <danahow@gmail.com>
+Dana L. How <how@deathvalley.cswitch.com>
 Daniel Barkalow <barkalow@iabervon.org>
 David Kågedal <davidk@lysator.liu.se>
 Fredrik Kuivinen <freku045@student.liu.se>
@@ -17,13 +19,16 @@ Horst H. von Brand <vonbrand@inf.utfsm.cl>
 Joachim Berdal Haga <cjhaga@fys.uio.no>
 Jon Loeliger <jdl@freescale.com>
 Jon Seymour <jon@blackcubes.dyndns.org>
+Junio C Hamano <junio@twinsun.com>
 Karl Hasselström <kha@treskal.com>
 Kent Engstrom <kent@lysator.liu.se>
-Lars Doelle <lars.doelle@on-line.de>
 Lars Doelle <lars.doelle@on-line ! de>
+Lars Doelle <lars.doelle@on-line.de>
 Lukas Sandström <lukass@etek.chalmers.se>
 Martin Langhoff <martin@catalyst.net.nz>
+Michael Coleman <tutufan@gmail.com>
 Michele Ballabio <barra_cuda@katamail.com>
+Nanako Shiraishi <nanako3@bluebottle.com>
 Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
 Ramsay Allan Jones <ramsay@ramsay1.demon.co.uk>
 René Scharfe <rene.scharfe@lsrfire.ath.cx>
@@ -34,12 +39,12 @@ Sean Estabrooks <seanlkml@sympatico.ca>
 Shawn O. Pearce <spearce@spearce.org>
 Theodore Ts'o <tytso@mit.edu>
 Tony Luck <tony.luck@intel.com>
-Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de>
 Uwe Kleine-König <Uwe_Zeisberger@digi.com>
-Uwe Kleine-König <uzeisberger@io.fsforth.de>
 Uwe Kleine-König <ukleinek@informatik.uni-freiburg.de>
+Uwe Kleine-König <uzeisberger@io.fsforth.de>
+Uwe Kleine-König <zeisberg@informatik.uni-freiburg.de>
 Ville Skyttä <scop@xemacs.org>
+William Pursell <bill.pursell@gmail.com>
 YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
 anonymous <linux@horizon.com>
 anonymous <linux@horizon.net>
-Dana L. How <how@deathvalley.cswitch.com>
index 4edf788c3a8ddf39638028447ca78624305da778..fbefe9a45b00a54b58d94d06eca48b03d40a50e0 100644 (file)
@@ -2,7 +2,7 @@ MAN1_TXT= \
        $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
                $(wildcard git-*.txt)) \
        gitk.txt
-MAN5_TXT=gitattributes.txt gitignore.txt
+MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt
 MAN7_TXT=git.txt
 
 DOC_HTML=$(patsubst %.txt,%.html,$(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT))
@@ -29,7 +29,7 @@ DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
 
 prefix?=$(HOME)
 bindir?=$(prefix)/bin
-mandir?=$(prefix)/man
+mandir?=$(prefix)/share/man
 man1dir=$(mandir)/man1
 man5dir=$(mandir)/man5
 man7dir=$(mandir)/man7
@@ -41,8 +41,14 @@ ifdef ASCIIDOC8
 ASCIIDOC_EXTRA += -a asciidoc7compatible
 endif
 INSTALL?=install
+RM ?= rm -f
 DOC_REF = origin/man
 
+infodir?=$(prefix)/share/info
+MAKEINFO=makeinfo
+INSTALL_INFO=install-info
+DOCBOOK2X_TEXI=docbook2x-texi
+
 -include ../config.mak.autogen
 -include ../config.mak
 
@@ -66,6 +72,8 @@ man1: $(DOC_MAN1)
 man5: $(DOC_MAN5)
 man7: $(DOC_MAN7)
 
+info: git.info
+
 install: man
        $(INSTALL) -d -m755 $(DESTDIR)$(man1dir)
        $(INSTALL) -d -m755 $(DESTDIR)$(man5dir)
@@ -74,6 +82,14 @@ install: man
        $(INSTALL) -m644 $(DOC_MAN5) $(DESTDIR)$(man5dir)
        $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
 
+install-info: info
+       $(INSTALL) -d -m755 $(DESTDIR)$(infodir)
+       $(INSTALL) -m644 git.info $(DESTDIR)$(infodir)
+       if test -r $(DESTDIR)$(infodir)/dir; then \
+         $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) git.info ;\
+       else \
+         echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \
+       fi
 
 ../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        $(MAKE) -C ../ GIT-VERSION-FILE
@@ -84,7 +100,7 @@ install: man
 # Determine "include::" file references in asciidoc files.
 #
 doc.dep : $(wildcard *.txt) build-docdep.perl
-       rm -f $@+ $@
+       $(RM) $@+ $@
        perl ./build-docdep.perl >$@+
        mv $@+ $@
 
@@ -103,26 +119,28 @@ cmds_txt = cmds-ancillaryinterrogators.txt \
 $(cmds_txt): cmd-list.made
 
 cmd-list.made: cmd-list.perl $(MAN1_TXT)
+       $(RM) $@
        perl ./cmd-list.perl
        date >$@
 
 git.7 git.html: git.txt core-intro.txt
 
 clean:
-       rm -f *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 howto-index.txt howto/*.html doc.dep
-       rm -f $(cmds_txt) *.made
+       $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 *.texi *.texi+ howto-index.txt howto/*.html doc.dep
+       $(RM) $(cmds_txt) *.made
 
 %.html : %.txt
-       rm -f $@+ $@
+       $(RM) $@+ $@
        $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
                $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
        mv $@+ $@
 
 %.1 %.5 %.7 : %.xml
+       $(RM) $@
        xmlto -m callouts.xsl man $<
 
 %.xml : %.txt
-       rm -f $@+ $@
+       $(RM) $@+ $@
        $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \
                $(ASCIIDOC_EXTRA) -agit_version=$(GIT_VERSION) -o $@+ $<
        mv $@+ $@
@@ -130,14 +148,21 @@ clean:
 user-manual.xml: user-manual.txt user-manual.conf
        $(ASCIIDOC) -b docbook -d book $<
 
-XSLT = http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl
+XSLT = docbook.xsl
 XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
 
 user-manual.html: user-manual.xml
        xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
 
+git.info: user-manual.xml
+       $(RM) $@ $*.texi $*.texi+
+       $(DOCBOOK2X_TEXI) user-manual.xml --to-stdout >$*.texi+
+       perl fix-texi.perl <$*.texi+ >$*.texi
+       $(MAKEINFO) --no-split $*.texi
+       $(RM) $*.texi $*.texi+
+
 howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
-       rm -f $@+ $@
+       $(RM) $@+ $@
        sh ./howto-index.sh $(wildcard howto/*.txt) >$@+
        mv $@+ $@
 
@@ -147,7 +172,7 @@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
 WEBDOC_DEST = /pub/software/scm/git/docs
 
 $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
-       rm -f $@+ $@
+       $(RM) $@+ $@
        sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+
        mv $@+ $@
 
index b727a8d1e57cf8fb5a6c591a5f35ceddbbd3f6af..feefa5dfd4eee5f493fcf3091ec0de116d414fc1 100644 (file)
@@ -20,5 +20,3 @@ Fixes since v1.5.0.3
 * Documentation updates
 
 * User manual updates
-
-
index aa86149d4f3649721f206b94c26854ce30dfc101..eeec3d73d01a0e9f61df666bad8a48a4f423438a 100644 (file)
@@ -24,5 +24,3 @@ Fixes since v1.5.0.3
 * Documentation updates
 
 * User manual updates
-
-
index e15447ffdba41ec082e249b13bc31248b6d2dbbe..c02015ad5fdb1953ad1a8cf5b0edd7ef7c04fbac 100644 (file)
@@ -19,4 +19,3 @@ Fixes since v1.5.0.5
   - user-manual has better cross references.
 
   - gitweb installation/deployment procedure is now documented.
-
index 2ddeabd0296848eeb6344b5487f85e9c484eaf0a..876408b65a0e0841567a2c9b97c6f6e62cb86b81 100644 (file)
@@ -43,4 +43,3 @@ Fixes since v1.5.1.2
     description was given by the caller.
 
 Also contains various documentation updates.
-
diff --git a/Documentation/RelNotes-1.5.3.txt b/Documentation/RelNotes-1.5.3.txt
new file mode 100644 (file)
index 0000000..9c36e8b
--- /dev/null
@@ -0,0 +1,322 @@
+GIT v1.5.3 Release Notes
+========================
+
+Updates since v1.5.2
+--------------------
+
+* The commit walkers other than http are officially deprecated,
+  but still supported for now.
+
+* The submodule support has Porcelain layer.
+
+  Note that the current submodule support is minimal and this is
+  deliberately so.  A design decision we made is that operations
+  at the supermodule level do not recurse into submodules by
+  default.  The expectation is that later we would add a
+  mechanism to tell git which submodules the user is interested
+  in, and this information might be used to determine the
+  recursive behaviour of certain commands (e.g. "git checkout"
+  and "git diff"), but currently we haven't agreed on what that
+  mechanism should look like.  Therefore, if you use submodules,
+  you would probably need "git submodule update" on the
+  submodules you care about after running a "git checkout" at
+  the supermodule level.
+
+* There are a handful pack-objects changes to help you cope better
+  with repositories with pathologically large blobs in them.
+
+* For people who need to import from Perforce, a front-end for
+  fast-import is in contrib/fast-import/.
+
+* Comes with git-gui 0.8.0.
+
+* Comes with updated gitk.
+
+* New commands and options.
+
+  - "git log --date=<format>" can use more formats: iso8601, rfc2822.
+
+  - The hunk header output from "git diff" family can be customized
+    with the attributes mechanism.  See gitattributes(5) for details.
+
+  - "git stash" allows you to quickly save away your work in
+    progress and replay it later on an updated state.
+
+  - "git rebase" learned an "interactive" mode that let you
+    pick and reorder which commits to rebuild.
+
+  - "git fsck" can save its findings in $GIT_DIR/lost-found, without a
+    separate invocation of "git lost-found" command.  The blobs stored by
+    lost-found are stored in plain format to allow you to grep in them.
+
+  - $GIT_WORK_TREE environment variable can be used together with
+    $GIT_DIR to work in a subdirectory of a working tree that is
+    not located at "$GIT_DIR/..".
+
+  - Giving "--file=<file>" option to "git config" is the same as
+    running the command with GIT_CONFIG=<file> environment.
+
+  - "git log" learned a new option "--follow", to follow
+    renaming history of a single file.
+
+  - "git filter-branch" lets you rewrite the revision history of
+    specified branches. You can specify a number of filters to
+    modify the commits, files and trees.
+
+  - "git cvsserver" learned new options (--base-path, --export-all,
+    --strict-paths) inspired by "git daemon".
+
+  - "git daemon --base-path-relaxed" can help migrating a repository URL
+    that did not use to use --base-path to use --base-path.
+
+  - "git commit" can use "-t templatefile" option and commit.template
+    configuration variable to prime the commit message given to you in the
+    editor.
+
+  - "git submodule" command helps you manage the projects from
+    the superproject that contain them.
+
+  - In addition to core.compression configuration option,
+    core.loosecompression and pack.compression options can
+    independently tweak zlib compression levels used for loose
+    and packed objects.
+
+  - "git ls-tree -l" shows size of blobs pointed at by the
+    tree entries, similar to "/bin/ls -l".
+
+  - "git rev-list" learned --regexp-ignore-case and
+    --extended-regexp options to tweak its matching logic used
+    for --grep fitering.
+
+  - "git describe --contains" is a handier way to call more
+    obscure command "git name-rev --tags".
+
+  - "git gc --aggressive" tells the command to spend more cycles
+    to optimize the repository harder.
+
+  - "git repack" learned a "window-memory" limit which
+    dynamically reduces the window size to stay within the
+    specified memory usage.
+
+  - "git repack" can be told to split resulting packs to avoid
+    exceeding limit specified with "--max-pack-size".
+
+  - "git fsck" gained --verbose option.  This is really really
+    verbose but it might help you identify exact commit that is
+    corrupt in your repository.
+
+  - "git format-patch" learned --numbered-files option.  This
+    may be useful for MH users.
+
+  - "git format-patch" learned format.subjectprefix configuration
+    variable, which serves the same purpose as "--subject-prefix"
+    option.
+
+  - "git tag -n -l" shows tag annotations while listing tags.
+
+  - "git cvsimport" can optionally use the separate-remote layout.
+
+  - "git blame" can be told to see through commits that change
+    whitespaces and indentation levels with "-w" option.
+
+  - "git send-email" can be told not to thread the messages when
+    sending out more than one patches.
+
+  - "git config" learned NUL terminated output format via -z to
+    help scripts.
+
+  - "git add" learned "--refresh <paths>..." option to selectively refresh
+    the cached stat information.
+
+  - "git init -q" makes the command quieter.
+
+* Updated behavior of existing commands.
+
+  - "gitweb" can offer multiple snapshot formats.
+
+    ***NOTE*** Unfortunately, this changes the format of the
+    $feature{snapshot}{default} entry in the per-site
+    configuration file 'gitweb_config.perl'.  It used to be a
+    three-element tuple that describe a single format; with the
+    new configuration item format, you only have to say the name
+    of the format ('tgz', 'tbz2' or 'zip').  Please update the
+    your configuration file accordingly.
+
+  - "git clone" uses -l (hardlink files under .git) by default when
+    cloning locally.
+
+  - "git bundle create" can now create a bundle without negative refs,
+    i.e. "everything since the beginning up to certain points".
+
+  - "git diff" (but not the plumbing level "git diff-tree") now
+    recursively descends into trees by default.
+
+  - "git diff" does not show differences that come only from
+    stat-dirtiness in the form of "diff --git" header anymore.  When
+    generating a textual diff, it shows a warning message at the end.
+
+  - The editor to use with many interactive commands can be
+    overridden with GIT_EDITOR environment variable, or if it
+    does not exist, with core.editor configuration variable.  As
+    before, if you have neither, environment variables VISUAL
+    and EDITOR are consulted in this order, and then finally we
+    fall back on "vi".
+
+  - "git rm --cached" does not complain when removing a newly
+    added file from the index anymore.
+
+  - Options to "git log" to affect how --grep/--author options look for
+    given strings now have shorter abbreviations.  -i is for ignore case,
+    and -E is for extended regexp.
+
+  - "git log" learned --log-size to show the number of bytes in
+    the log message part of the output to help qgit.
+
+  - "git svn dcommit" retains local merge information.
+
+  - "git svnimport" allows an empty string to be specified as the
+    trunk/ directory.  This is necessary to suck data from a SVN
+    repository that doe not have trunk/ branches/ and tags/ organization
+    at all.
+
+  - "git config" to set values also honors type flags like --bool
+    and --int.
+
+  - core.quotepath configuration can be used to make textual git
+    output to emit most of the characters in the path literally.
+
+  - "git mergetool" chooses its backend more wisely, taking
+    notice of its environment such as use of X, Gnome/KDE, etc.
+
+  - "gitweb" shows merge commits a lot nicer than before.  The
+    default view uses more compact --cc format, while the UI
+    allows to choose normal diff with any parent.
+
+  - snapshot files "gitweb" creates from a repository at
+    $path/$project/.git are more useful.  We use $project part
+    in the filename, which we used to discard.
+
+  - "git cvsimport" creates lightweight tags; there is no
+    interesting information we can record in an annotated tag,
+    and the handcrafted ones the old code created was not
+    properly formed anyway.
+
+  - "git push" pretends that you immediately fetched back from
+    the remote by updating corresponding remote tracking
+    branches if you have any.
+
+  - The diffstat given after a merge (or a pull) honors the
+    color.diff configuration.
+
+  - "git commit --amend" is now compatible with various message source
+    options such as -m/-C/-c/-F.
+
+  - "git apply --whitespace=strip" removes blank lines added at
+    the end of the file.
+
+  - "git fetch" over git native protocols with "-v" option shows
+    connection status, and the IP address of the other end, to
+    help diagnosing problems.
+
+  - We used to have core.legacyheaders configuration, when
+    set to false, allowed git to write loose objects in a format
+    that mimicks the format used by objects stored in packs.  It
+    turns out that this was not so useful.  Although we will
+    continue to read objects written in that format, we do not
+    honor that configuration anymore and create loose objects in
+    the legacy/traditional format.
+
+  - "--find-copies-harder" option to diff family can now be
+    spelled as "-C -C" for brevity.
+
+  - "git mailsplit" (hence "git am") can read from Maildir
+    formatted mailboxes.
+
+  - "git cvsserver" does not barf upon seeing "cvs login"
+    request.
+
+  - "pack-objects" honors "delta" attribute set in
+    .gitattributes.  It does not attempt to deltify blobs that
+    come from paths with delta attribute set to false.
+
+  - "new-workdir" script (in contrib) can now be used with a
+    bare repository.
+
+  - "git mergetool" learned to use gvimdiff.
+
+  - "gitview" (in contrib) has a better blame interface.
+
+  - "git log" and friends did not handle a commit log message
+    that is larger than 16kB; they do now.
+
+  - "--pretty=oneline" output format for "git log" and friends
+    deals with "malformed" commit log messages that have more
+    than one lines in the first paragraph better.  We used to
+    show the first line, cutting the title at mid-sentence; we
+    concatenate them into a single line and treat the result as
+    "oneline".
+
+  - "git p4import" has been demoted to contrib status.  For
+    a superior option, checkout the "git p4" front end to
+    "git fast-import" (also in contrib).  The man page and p4
+    rpm have been removed as well.
+
+  - "git mailinfo" (hence "am") now tries to see if the message
+    is in utf-8 first, instead of assuming iso-8859-1, if
+    incoming e-mail does not say what encoding it is in.
+
+* Builds
+
+  - old-style function definitions (most notably, a function
+    without parameter defined with "func()", not "func(void)")
+    have been eradicated.
+
+  - "git tag" and "git verify-tag" have been rewritten in C.
+
+* Performance Tweaks
+
+  - "git pack-objects" avoids re-deltification cost by caching
+    small enough delta results it creates while looking for the
+    best delta candidates.
+
+  - "git pack-objects" learned a new heuristcs to prefer delta
+    that is shallower in depth over the smallest delta
+    possible.  This improves both overall packfile access
+    performance and packfile density.
+
+  - diff-delta code that is used for packing has been improved
+    to work better on big files.
+
+  - when there are more than one pack files in the repository,
+    the runtime used to try finding an object always from the
+    newest packfile; it now tries the same packfile as we found
+    the object requested the last time, which exploits the
+    locality of references.
+
+  - verifying pack contents done by "git fsck --full" got boost
+    by carefully choosing the order to verify objects in them.
+
+  - "git read-tree -m" to read into an already populated index
+    has been optimized vastly.  The effect of this can be seen
+    when switching branches that have differences in only a
+    handful paths.
+
+  - "git commit paths..." has also been optimized.
+
+
+Fixes since v1.5.2
+------------------
+
+All of the fixes in v1.5.2 maintenance series are included in
+this release, unless otherwise noted.
+
+* Bugfixes
+
+  - "gitweb" had trouble handling non UTF-8 text with older
+    Encode.pm Perl module.
+
+--
+exec >/var/tmp/1
+O=v1.5.3-rc4
+echo O=`git describe refs/heads/master`
+git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
index b94d9a816664333e0b1767c22d1639b8839cee24..01354c2bb5da56181de3b88052e1d921986f1f95 100644 (file)
@@ -14,6 +14,8 @@ Checklist (and a short version for the impatient):
          commit message (or just use the option "-s" when
          committing) to confirm that you agree to the Developer's
          Certificate of Origin
+       - make sure that you have tests for the bug you are fixing
+       - make sure that the test suite passes after your commit
 
        Patch:
 
@@ -33,6 +35,8 @@ Checklist (and a short version for the impatient):
        - if you change, add, or remove a command line option or
          make some other user interface change, the associated
          documentation should be updated as well.
+       - if your name is not writable in ASCII, make sure that
+         you send off a message in the correct encoding.
 
 Long version:
 
@@ -239,7 +243,7 @@ One test you could do yourself if your MUA is set up correctly is:
     $ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply
     $ git checkout test-apply
     $ git reset --hard
-    $ git applymbox a.patch
+    $ git am a.patch
 
 If it does not apply correctly, there can be various reasons.
 
@@ -247,7 +251,7 @@ If it does not apply correctly, there can be various reasons.
   does not have much to do with your MUA.  Please rebase the
   patch appropriately.
 
-* Your MUA corrupted your patch; applymbox would complain that
+* Your MUA corrupted your patch; "am" would complain that
   the patch does not apply.  Look at .dotest/ subdirectory and
   see what 'patch' file contains and check for the common
   corruption patterns mentioned above.
@@ -292,15 +296,15 @@ diff --git a/pico/pico.c b/pico/pico.c
 --- a/pico/pico.c
 +++ b/pico/pico.c
 @@ -219,7 +219,9 @@ PICO *pm;
-           switch(pico_all_done){      /* prepare for/handle final events */
-             case COMP_EXIT :          /* already confirmed */
-               packheader();
+           switch(pico_all_done){      /* prepare for/handle final events */
+             case COMP_EXIT :          /* already confirmed */
+               packheader();
 +#if 0
-               stripwhitespace();
+               stripwhitespace();
 +#endif
-               c |= COMP_EXIT;
-               break;
+               c |= COMP_EXIT;
+               break;
+
 
 (Daniel Barkalow)
 
index a0d8459e21e8ba91df8f3458c49ba1cd825d7706..af5b1558a63219a3eac2570e1cecc97e3008f96f 100644 (file)
@@ -61,5 +61,3 @@ ifdef::backend-xhtml11[]
 [gitlink-inlinemacro]
 <a href="{target}.html">{target}{0?({0})}</a>
 endif::backend-xhtml11[]
-
-
index a46bf6ce703f4bcf8b5f8066a7d7d2c3f21b4ec2..17379f057613f2dc82bf6d5d4f8181d4e29dee50 100644 (file)
@@ -64,11 +64,11 @@ of lines before or after the line given by <start>.
        assigns blame to the lines that were moved down (i.e. A)
        to the child commit.  With this option, both groups of lines
        are blamed on the parent.
-
-       <num> is optional but it is the lower bound on the number of
-       alphanumeric characters that git must detect as moving
-       within a file for it to associate those lines with the parent
-       commit.
++
+<num> is optional but it is the lower bound on the number of
+alphanumeric characters that git must detect as moving
+within a file for it to associate those lines with the parent
+commit.
 
 -C|<num>|::
        In addition to `-M`, detect lines copied from other
@@ -77,11 +77,11 @@ of lines before or after the line given by <start>.
        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.
-
-       <num> is optional but it is the lower bound on the number of
-       alphanumeric characters that git must detect as moving
-       between files for it to associate those lines with the parent
-       commit.
++
+<num> is optional but it is the lower bound on the number of
+alphanumeric characters that git must detect as moving
+between files for it to associate those lines with the parent
+commit.
 
 -h, --help::
        Show help message.
index 443802a9a3e4982c33f831217602ea600f0f2775..4ee76eaf9925f98dcd77dc74f1f7a9eb9030fc74 100755 (executable)
@@ -68,12 +68,12 @@ for my $cat (qw(ancillaryinterrogators
        }
 }
 
+# The following list is sorted with "sort -d" to make it easier
+# to find entry in the resulting git.html manual page.
 __DATA__
 git-add                                 mainporcelain
 git-am                                  mainporcelain
 git-annotate                            ancillaryinterrogators
-git-applymbox                           ancillaryinterrogators
-git-applypatch                          purehelpers
 git-apply                               plumbingmanipulators
 git-archimport                          foreignscminterface
 git-archive                             mainporcelain
@@ -82,16 +82,18 @@ git-blame                               ancillaryinterrogators
 git-branch                              mainporcelain
 git-bundle                              mainporcelain
 git-cat-file                            plumbinginterrogators
-git-checkout-index                      plumbingmanipulators
-git-checkout                            mainporcelain
 git-check-attr                          purehelpers
+git-checkout                            mainporcelain
+git-checkout-index                      plumbingmanipulators
 git-check-ref-format                    purehelpers
 git-cherry                              ancillaryinterrogators
 git-cherry-pick                         mainporcelain
+git-citool                              mainporcelain
 git-clean                               mainporcelain
 git-clone                               mainporcelain
 git-commit                              mainporcelain
 git-commit-tree                         plumbingmanipulators
+git-config                              ancillarymanipulators
 git-convert-objects                     ancillarymanipulators
 git-count-objects                       ancillaryinterrogators
 git-cvsexportcommit                     foreignscminterface
@@ -99,13 +101,14 @@ git-cvsimport                           foreignscminterface
 git-cvsserver                           foreignscminterface
 git-daemon                              synchingrepositories
 git-describe                            mainporcelain
+git-diff                                mainporcelain
 git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
-git-diff                                mainporcelain
 git-diff-tree                           plumbinginterrogators
 git-fast-import                                ancillarymanipulators
 git-fetch                               mainporcelain
 git-fetch-pack                          synchingrepositories
+git-filter-branch                       ancillarymanipulators
 git-fmt-merge-msg                       purehelpers
 git-for-each-ref                        plumbinginterrogators
 git-format-patch                        mainporcelain
@@ -113,6 +116,7 @@ git-fsck                            ancillaryinterrogators
 git-gc                                  mainporcelain
 git-get-tar-commit-id                   ancillaryinterrogators
 git-grep                                mainporcelain
+git-gui                                 mainporcelain
 git-hash-object                         plumbingmanipulators
 git-http-fetch                          synchelpers
 git-http-push                           synchelpers
@@ -129,13 +133,13 @@ git-ls-remote                           plumbinginterrogators
 git-ls-tree                             plumbinginterrogators
 git-mailinfo                            purehelpers
 git-mailsplit                           purehelpers
+git-merge                               mainporcelain
 git-merge-base                          plumbinginterrogators
 git-merge-file                          plumbingmanipulators
 git-merge-index                         plumbingmanipulators
-git-merge                               mainporcelain
 git-merge-one-file                      purehelpers
-git-merge-tree                          ancillaryinterrogators
 git-mergetool                           ancillarymanipulators
+git-merge-tree                          ancillaryinterrogators
 git-mktag                               plumbingmanipulators
 git-mktree                              plumbingmanipulators
 git-mv                                  mainporcelain
@@ -156,9 +160,8 @@ git-rebase                              mainporcelain
 git-receive-pack                        synchelpers
 git-reflog                              ancillarymanipulators
 git-relink                              ancillarymanipulators
-git-repack                              ancillarymanipulators
-git-config                              ancillarymanipulators
 git-remote                              ancillarymanipulators
+git-repack                              ancillarymanipulators
 git-request-pull                        foreignscminterface
 git-rerere                              ancillaryinterrogators
 git-reset                               mainporcelain
@@ -178,8 +181,10 @@ git-show-ref                            plumbinginterrogators
 git-sh-setup                            purehelpers
 git-ssh-fetch                           synchingrepositories
 git-ssh-upload                          synchingrepositories
+git-stash                               mainporcelain
 git-status                              mainporcelain
 git-stripspace                          purehelpers
+git-submodule                           mainporcelain
 git-svn                                 foreignscminterface
 git-svnimport                           foreignscminterface
 git-symbolic-ref                        plumbingmanipulators
index a75f7f766442584f06155bb47008d1a4eb5ac640..de9e72b562eec6310af23cb4e5ef237268e8df2a 100644 (file)
@@ -101,7 +101,7 @@ Example
 
        # Proxy settings
        [core]
-               gitProxy="ssh" for "ssh://kernel.org/"
+               gitProxy="ssh" for "kernel.org"
                gitProxy=default-proxy ; for the rest
 
 Variables
@@ -117,6 +117,18 @@ core.fileMode::
        the working copy are ignored; useful on broken filesystems like FAT.
        See gitlink:git-update-index[1]. True by default.
 
+core.quotepath::
+       The commands that output paths (e.g. `ls-files`,
+       `diff`), when not given the `-z` option, will quote
+       "unusual" characters in the pathname by enclosing the
+       pathname in a double-quote pair and with backslashes the
+       same way strings in C source code are quoted.  If this
+       variable is set to false, the bytes higher than 0x80 are
+       not quoted but output as verbatim.  Note that double
+       quote, backslash and control characters are always
+       quoted without `-z` regardless of the setting of this
+       variable.
+
 core.autocrlf::
        If true, makes git convert `CRLF` at the end of lines in text files to
        `LF` when reading from the filesystem, and convert in reverse when
@@ -172,6 +184,13 @@ repository that ends in "/.git" is assumed to be not bare (bare =
 false), while all other repositories are assumed to be bare (bare
 = true).
 
+core.worktree::
+       Set the path to the working tree.  The value will not be
+       used in combination with repositories found automatically in
+       a .git directory (i.e. $GIT_DIR is not set).
+       This can be overriden by the GIT_WORK_TREE environment
+       variable and the '--work-tree' command line option.
+
 core.logAllRefUpdates::
        Updates to a ref <ref> is logged to the file
        "$GIT_DIR/logs/<ref>", by appending the new and old
@@ -204,23 +223,16 @@ core.warnAmbiguousRefs::
        and might match multiple refs in the .git/refs/ tree. True by default.
 
 core.compression::
+       An integer -1..9, indicating a default compression level.
+       -1 is the zlib default. 0 means no compression,
+       and 1..9 are various speed/size tradeoffs, 9 being slowest.
+
+core.loosecompression::
        An integer -1..9, indicating the compression level for objects that
-       are not in a pack file. -1 is the zlib and git default. 0 means no
+       are not in a pack file. -1 is the zlib default. 0 means no
        compression, and 1..9 are various speed/size tradeoffs, 9 being
-       slowest.
-
-core.legacyheaders::
-       A boolean which
-       changes the format of loose objects so that they are more
-       efficient to pack and to send out of the repository over git
-       native protocol, since v1.4.2.  However, loose objects
-       written in the new format cannot be read by git older than
-       that version; people fetching from your repository using
-       older versions of git over dumb transports (e.g. http)
-       will also be affected.
-+
-To let git use the new loose object format, you have to
-set core.legacyheaders to false.
+       slowest.  If not set,  defaults to core.compression.  If that is
+       not set,  defaults to 0 (best speed).
 
 core.packedGitWindowSize::
        Number of bytes of a pack file to map into memory in a
@@ -269,6 +281,18 @@ core.excludesfile::
        of files which are not meant to be tracked.  See
        gitlink:gitignore[5].
 
+core.editor::
+       Commands such as `commit` and `tag` that lets you edit
+       messages by lauching an editor uses the value of this
+       variable when it is set, and the environment variable
+       `GIT_EDITOR` is not set.  The order of preference is
+       `GIT_EDITOR` environment, `core.editor`, `VISUAL` and
+       `EDITOR` environment variables and then finally `vi`.
+
+core.pager::
+       The command that git will use to paginate output.  Can be overridden
+       with the `GIT_PAGER` environment variable.
+
 alias.*::
        Command aliases for the gitlink:git[1] command wrapper - e.g.
        after defining "alias.last = cat-file commit HEAD", the invocation
@@ -369,6 +393,9 @@ color.status.<slot>::
        or `untracked` (files which are not tracked by git). The values of
        these variables may be specified as in color.branch.<slot>.
 
+commit.template::
+       Specify a file to use as the template for new commit messages.
+
 diff.renameLimit::
        The number of files to consider when performing the copy/rename
        detection; equivalent to the git diff option '-l'.
@@ -397,6 +424,11 @@ format.suffix::
        `.patch`. Use this variable to change that suffix (make sure to
        include the dot if you want it).
 
+gc.aggressiveWindow::
+       The window size parameter used in the delta compression
+       algorithm used by 'git gc --aggressive'.  This defaults
+       to 10.
+
 gc.packrefs::
        `git gc` does not run `git pack-refs` in a bare repository by
        default so that older dumb-transport clients can still fetch
@@ -427,6 +459,11 @@ gc.rerereunresolved::
        kept for this many days when `git rerere gc` is run.
        The default is 15 days.  See gitlink:git-rerere[1].
 
+rerere.enabled::
+       Activate recording of resolved conflicts, so that identical
+       conflict hunks can be resolved automatically, should they
+       be encountered again.  See gitlink:git-rerere[1].
+
 gitcvs.enabled::
        Whether the cvs server interface is enabled for this repository.
        See gitlink:git-cvsserver[1].
@@ -533,7 +570,7 @@ merge.summary::
 merge.tool::
        Controls which merge resolution program is used by
        gitlink:git-mergetool[l].  Valid values are: "kdiff3", "tkdiff",
-       "meld", "xxdiff", "emerge", "vimdiff", and "opendiff"
+       "meld", "xxdiff", "emerge", "vimdiff", "gvimdiff", and "opendiff".
 
 merge.verbosity::
        Controls the amount of output shown by the recursive merge
@@ -541,6 +578,7 @@ merge.verbosity::
        message if conflicts were detected. Level 1 outputs only
        conflicts, 2 outputs conflicts and file changes.  Level 5 and
        above outputs debugging information.  The default is level 2.
+       Can be overriden by 'GIT_MERGE_VERBOSITY' environment variable.
 
 merge.<driver>.name::
        Defines a human readable name for a custom low-level
@@ -563,6 +601,28 @@ pack.depth::
        The maximum delta depth used by gitlink:git-pack-objects[1] when no
        maximum depth is given on the command line. Defaults to 50.
 
+pack.windowMemory::
+       The window memory size limit used by gitlink:git-pack-objects[1]
+       when no limit is given on the command line.  The value can be
+       suffixed with "k", "m", or "g".  Defaults to 0, meaning no
+       limit.
+
+pack.compression::
+       An integer -1..9, indicating the compression level for objects
+       in a pack file. -1 is the zlib default. 0 means no
+       compression, and 1..9 are various speed/size tradeoffs, 9 being
+       slowest.  If not set,  defaults to core.compression.  If that is
+       not set,  defaults to -1.
+
+pack.deltaCacheSize::
+       The maxium memory in bytes used for caching deltas in
+       gitlink:git-pack-objects[1].
+       A value of 0 means no limit. Defaults to 0.
+
+pack.deltaCacheLimit::
+       The maxium size of a delta, that is cached in
+       gitlink:git-pack-objects[1]. Defaults to 1000.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
@@ -668,5 +728,3 @@ receive.denyNonFastForwards::
 transfer.unpackLimit::
        When `fetch.unpackLimit` or `receive.unpackLimit` are
        not set, the value of this variable is used instead.
-
-
index eea44d9d5613f448b8c2b8f0aae236f917efad39..f3cc2238c72d11e611a756e366d797c9d8056111 100644 (file)
@@ -528,7 +528,7 @@ paths that have been trivially merged.
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Sadly, many merges aren't trivial. If there are files that have
-been added.moved or removed, or if both branches have modified the
+been addedmoved or removed, or if both branches have modified the
 same file, you will be left with an index tree that contains "merge
 entries" in it. Such an index tree can 'NOT' be written out to a tree
 object, and you will have to resolve any such merge clashes using
index 6b9b9ad7d1083ee09955ddaf46db4fad1a37834b..4fb6f4143cd849fc81f3ee5d2b8e4ee77e21137d 100644 (file)
@@ -9,11 +9,11 @@ repository, mainly because being hands-on and using explicit examples is
 often the best way of explaining what is going on.
 
 In normal life, most people wouldn't use the "core" git programs
-directly, but rather script around them to make them more palatable. 
+directly, but rather script around them to make them more palatable.
 Understanding the core git stuff may help some people get those scripts
 done, though, and it may also be instructive in helping people
 understand what it is that the higher-level helper scripts are actually
-doing. 
+doing.
 
 The core git is often called "plumbing", with the prettier user
 interfaces on top of it called "porcelain". You may not want to use the
@@ -41,7 +41,7 @@ Creating a new git repository couldn't be easier: all git repositories start
 out empty, and the only thing you need to do is find yourself a
 subdirectory that you want to use as a working tree - either an empty
 one for a totally new project, or an existing working tree that you want
-to import into git. 
+to import into git.
 
 For our first example, we're going to start a totally new repository from
 scratch, with no pre-existing files, and we'll call it `git-tutorial`.
@@ -169,7 +169,7 @@ $ ls .git/objects/??/*
 and see two files:
 
 ----------------
-.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238 
+.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238
 .git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962
 ----------------
 
@@ -220,7 +220,7 @@ you have not actually really "checked in" your files into git so far,
 you've only *told* git about them.
 
 However, since git knows about them, you can now start using some of the
-most basic git commands to manipulate the files or look at their status. 
+most basic git commands to manipulate the files or look at their status.
 
 In particular, let's not even check in the two files into git yet, we'll
 start off by adding another line to `hello` first:
@@ -350,7 +350,7 @@ Making a change
 
 Remember how we did the `git-update-index` on file `hello` and then we
 changed `hello` afterward, and could compare the new state of `hello` with the
-state we saved in the index file? 
+state we saved in the index file?
 
 Further, remember how I said that `git-write-tree` writes the contents
 of the *index* file to the tree, and thus what we just committed was in
@@ -370,7 +370,7 @@ file and the working tree, `git-diff-index` shows the differences
 between a committed *tree* and either the index file or the working
 tree. In other words, `git-diff-index` wants a tree to be diffed
 against, and before we did the commit, we couldn't do that, because we
-didn't have anything to diff against. 
+didn't have anything to diff against.
 
 But now we can do
 
@@ -379,7 +379,7 @@ $ git-diff-index -p HEAD
 ----------------
 
 (where `-p` has the same meaning as it did in `git-diff-files`), and it
-will show us the same difference, but for a totally different reason. 
+will show us the same difference, but for a totally different reason.
 Now we're comparing the working tree not against the index file,
 but against the tree we just wrote. It just so happens that those two
 are obviously the same, so we get the same result.
@@ -398,7 +398,7 @@ working tree, but when given the `\--cached` flag, it is told to
 instead compare against just the index cache contents, and ignore the
 current working tree state entirely. Since we just wrote the index
 file to HEAD, doing `git-diff-index \--cached -p HEAD` should thus return
-an empty set of differences, and that's exactly what it does. 
+an empty set of differences, and that's exactly what it does.
 
 [NOTE]
 ================
@@ -549,7 +549,7 @@ $ git-whatchanged -p --root
 ----------------
 
 and you will see exactly what has changed in the repository over its
-short history. 
+short history.
 
 [NOTE]
 The `\--root` flag is a flag to `git-diff-tree` to tell it to
@@ -637,7 +637,7 @@ So the mental model of "the git information is always tied directly to
 the working tree that it describes" may not be technically 100%
 accurate, but it's a good model for all normal use.
 
-This has two implications: 
+This has two implications:
 
  - if you grow bored with the tutorial repository you created (or you've
    made a mistake and want to start all over), you can just do simple
@@ -705,7 +705,7 @@ Many (most?) public remote repositories will not contain any of
 the checked out files or even an index file, and will *only* contain the
 actual core git files. Such a repository usually doesn't even have the
 `.git` subdirectory, but has all the git files directly in the
-repository. 
+repository.
 
 To create your own local live copy of such a "raw" git repository, you'd
 first create your own subdirectory for the project, and then copy the
@@ -718,7 +718,7 @@ $ cd my-git
 $ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git
 ----------------
 
-followed by 
+followed by
 
 ----------------
 $ git-read-tree HEAD
@@ -738,7 +738,7 @@ up-to-date (so that you don't have to refresh it afterward), and the
 `-a` flag means "check out all files" (if you have a stale copy or an
 older version of a checked out tree you may also need to add the `-f`
 flag first, to tell git-checkout-index to *force* overwriting of any old
-files). 
+files).
 
 Again, this can all be simplified with
 
@@ -751,7 +751,7 @@ $ git checkout
 which will end up doing all of the above for you.
 
 You have now successfully copied somebody else's (mine) remote
-repository, and checked it out. 
+repository, and checked it out.
 
 
 Creating a new branch
@@ -760,14 +760,14 @@ Creating a new branch
 Branches in git are really nothing more than pointers into the git
 object database from within the `.git/refs/` subdirectory, and as we
 already discussed, the `HEAD` branch is nothing but a symlink to one of
-these object pointers. 
+these object pointers.
 
 You can at any time create a new branch by just picking an arbitrary
 point in the project history, and just writing the SHA1 name of that
 object into a file under `.git/refs/heads/`. You can use any filename you
 want (and indeed, subdirectories), but the convention is that the
 "normal" branch is called `master`. That's just a convention, though,
-and nothing enforces it. 
+and nothing enforces it.
 
 To show that as an example, let's go back to the git-tutorial repository we
 used earlier, and create a branch in it. You do that by simply just
@@ -778,7 +778,7 @@ $ git checkout -b mybranch
 ------------
 
 will create a new branch based at the current `HEAD` position, and switch
-to it. 
+to it.
 
 [NOTE]
 ================================================
@@ -825,7 +825,7 @@ checking it out and switching to it. If so, just use the command
 $ git branch <branchname> [startingpoint]
 ------------
 
-which will simply _create_ the branch, but will not do anything further. 
+which will simply _create_ the branch, but will not do anything further.
 You can then later -- once you decide that you want to actually develop
 on that branch -- switch to that branch with a regular `git checkout`
 with the branchname as the argument.
@@ -884,7 +884,7 @@ $ gitk --all
 will show you graphically both of your branches (that's what the `\--all`
 means: normally it will just show you your current `HEAD`) and their
 histories. You can also see exactly how they came to be from a common
-source. 
+source.
 
 Anyway, let's exit `gitk` (`^Q` or the File menu), and decide that we want
 to merge the work we did on the `mybranch` branch into the `master`
@@ -905,8 +905,8 @@ of it as it can automatically (which in this case is just merge the `example`
 file, which had no differences in the `mybranch` branch), and say:
 
 ----------------
-       Auto-merging hello 
-       CONFLICT (content): Merge conflict in hello 
+       Auto-merging hello
+       CONFLICT (content): Merge conflict in hello
        Automatic merge failed; fix up by hand
 ----------------
 
@@ -1387,7 +1387,7 @@ repository. Kernel.org mirror network takes care of the
 propagation to other publicly visible machines:
 
 ------------
-$ git push master.kernel.org:/pub/scm/git/git.git/ 
+$ git push master.kernel.org:/pub/scm/git/git.git/
 ------------
 
 
index e38a1f14056b2e3cfe3c281eb7df7e5b44d520db..001503205b24d5c20ec10792c4ab6c4c7221bcb7 100644 (file)
@@ -1,7 +1,7 @@
 The output format from "git-diff-index", "git-diff-tree" and
 "git-diff-files" are very similar.
 
-These commands all compare two sets of things; what is 
+These commands all compare two sets of things; what is
 compared differs:
 
 git-diff-index <tree-ish>::
@@ -126,6 +126,13 @@ the file that rename/copy produces, respectively.
     If there is need for such substitution then the whole
     pathname is put in double quotes.
 
+The similarity index is the percentage of unchanged lines, and
+the dissimilarity index is the percentage of changed lines.  It
+is a rounded down integer, followed by a percent sign.  The
+similarity index value of 100% is thus reserved for two equal
+files, while 100% dissimilarity means that no line from the old
+file made it into the new one.
+
 
 combined diff format
 --------------------
@@ -139,28 +146,28 @@ index fabadb8,cc95eb0..4866510
 --- a/describe.c
 +++ b/describe.c
 @@@ -98,20 -98,12 +98,20 @@@
-       return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
+       return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
   }
-  
+
 - static void describe(char *arg)
  -static void describe(struct commit *cmit, int last_one)
 ++static void describe(char *arg, int last_one)
   {
  +     unsigned char sha1[20];
  +     struct commit *cmit;
-       struct commit_list *list;
-       static int initialized = 0;
-       struct commit_name *n;
-  
+       struct commit_list *list;
+       static int initialized = 0;
+       struct commit_name *n;
+
  +     if (get_sha1(arg, sha1) < 0)
  +             usage(describe_usage);
  +     cmit = lookup_commit_reference(sha1);
  +     if (!cmit)
  +             usage(describe_usage);
  +
-       if (!initialized) {
-               initialized = 1;
-               for_each_ref(get_name);
+       if (!initialized) {
+               initialized = 1;
+               for_each_ref(get_name);
 ------------
 
 1.   It is preceded with a "git diff" header, that looks like
@@ -233,4 +240,3 @@ parents).  When shown by `git diff-files -c`, it compares the
 two unresolved merge parents with the working tree file
 (i.e. file1 is stage 2 aka "our version", file2 is stage 3 aka
 "their version").
-
index 1689c748171a8ff48395cf432aabe0130fc6b8d2..228ccaf10ab6bf7e4a3909d31ac31afbd2361d8c 100644 (file)
@@ -4,6 +4,13 @@
 -u::
        Synonym for "-p".
 
+-U<n>::
+       Shorthand for "--unified=<n>".
+
+--unified=<n>::
+       Generate diffs with <n> lines of context instead of
+       the usual three. Implies "-p".
+
 --raw::
        Generate the raw format.
 
@@ -36,7 +43,9 @@
        Synonym for "-p --stat".
 
 -z::
-       \0 line termination on output
+       NUL-line termination on output.  This affects the --raw
+       output field terminator.  Also output from commands such
+       as "git-log" will be delimited with NUL between commits.
 
 --name-only::
        Show only names of changed files.
@@ -86,7 +95,7 @@
        Detect renames.
 
 -C::
-       Detect copies as well as renames.
+       Detect copies as well as renames.  See also `--find-copies-harder`.
 
 --diff-filter=[ACDMRTUXB*]::
        Select only files that are Added (`A`), Copied (`C`),
        that matches other criteria, nothing is selected.
 
 --find-copies-harder::
-       For performance reasons, by default, -C option finds copies only 
-       if the original file of the copy was modified in the same 
+       For performance reasons, by default, `-C` option finds copies only
+       if the original file of the copy was modified in the same
        changeset.  This flag makes the command
        inspect unmodified files as candidates for the source of
        copy.  This is a very expensive operation for large
-       projects, so use it with caution.
+       projects, so use it with caution.  Giving more than one
+       `-C` option has the same effect.
 
 -l<num>::
        -M and -C options require O(n^2) processing time where n
 --quiet::
        Disable all output of the program. Implies --exit-code.
 
+--ext-diff::
+       Allow an external diff helper to be executed. If you set an
+       external diff driver with gitlink:gitattributes(5), you need
+       to use this option with gitlink:git-log(1) and friends.
+
+--no-ext-diff::
+       Disallow external diff drivers.
+
 For more detailed explanation on these common options, see also
 link:diffcore.html[diffcore documentation].
index 34cd306bb1ee6ac73986cf3371114381b4165a0d..c6a983a5d54abe2b18d155b6cba520c257da6f59 100644 (file)
@@ -71,7 +71,7 @@ The first transformation in the chain is diffcore-pathspec, and
 is controlled by giving the pathname parameters to the
 git-diff-* commands on the command line.  The pathspec is used
 to limit the world diff operates in.  It removes the filepairs
-outside the specified set of pathnames.  E.g. If the input set 
+outside the specified set of pathnames.  E.g. If the input set
 of filepairs included:
 
 ------------------------------------------------
@@ -269,4 +269,3 @@ Documentation
 *.c
 t
 ------------------------------------------------
-
index 8821e305dd6d5df919e376d2d9c3d0d5716cc074..b878b385c6967f4c64ba30bdfe8f9bd24bef91e3 100644 (file)
-/*\r
-  CSS stylesheet for XHTML produced by DocBook XSL stylesheets.\r
-  Tested with XSL stylesheets 1.61.2, 1.67.2\r
-*/\r
-\r
-span.strong {\r
-  font-weight: bold;\r
-}\r
-\r
-body blockquote {\r
-  margin-top: .75em;\r
-  line-height: 1.5;\r
-  margin-bottom: .75em;\r
-}\r
-\r
-html body {\r
-  margin: 1em 5% 1em 5%;\r
-  line-height: 1.2;\r
-}\r
-\r
-body div {\r
-  margin: 0;\r
-}\r
-\r
-h1, h2, h3, h4, h5, h6,\r
-div.toc p b,\r
-div.list-of-figures p b,\r
-div.list-of-tables p b,\r
-div.abstract p.title\r
-{\r
-  color: #527bbd;\r
-  font-family: tahoma, verdana, sans-serif;\r
-}\r
-\r
-div.toc p:first-child,\r
-div.list-of-figures p:first-child,\r
-div.list-of-tables p:first-child,\r
-div.example p.title\r
-{\r
-  margin-bottom: 0.2em;\r
-}\r
-\r
-body h1 {\r
-  margin: .0em 0 0 -4%;\r
-  line-height: 1.3;\r
-  border-bottom: 2px solid silver;\r
-}\r
-\r
-body h2 {\r
-  margin: 0.5em 0 0 -4%;\r
-  line-height: 1.3;\r
-  border-bottom: 2px solid silver;\r
-}\r
-\r
-body h3 {\r
-  margin: .8em 0 0 -3%;\r
-  line-height: 1.3;\r
-}\r
-\r
-body h4 {\r
-  margin: .8em 0 0 -3%;\r
-  line-height: 1.3;\r
-}\r
-\r
-body h5 {\r
-  margin: .8em 0 0 -2%;\r
-  line-height: 1.3;\r
-}\r
-\r
-body h6 {\r
-  margin: .8em 0 0 -1%;\r
-  line-height: 1.3;\r
-}\r
-\r
-body hr {\r
-  border: none; /* Broken on IE6 */\r
-}\r
-div.footnotes hr {\r
-  border: 1px solid silver;\r
-}\r
-\r
-div.navheader th, div.navheader td, div.navfooter td {\r
-  font-family: sans-serif;\r
-  font-size: 0.9em;\r
-  font-weight: bold;\r
-  color: #527bbd;\r
-}\r
-div.navheader img, div.navfooter img {\r
-  border-style: none;\r
-}\r
-div.navheader a, div.navfooter a {\r
-  font-weight: normal;\r
-}\r
-div.navfooter hr {\r
-  border: 1px solid silver;\r
-}\r
-\r
-body td {\r
-  line-height: 1.2\r
-}\r
-\r
-body th {\r
-  line-height: 1.2;\r
-}\r
-\r
-ol {\r
-  line-height: 1.2;\r
-}\r
-\r
-ul, body dir, body menu {\r
-  line-height: 1.2;\r
-}\r
-\r
-html {\r
-  margin: 0; \r
-  padding: 0;\r
-}\r
-\r
-body h1, body h2, body h3, body h4, body h5, body h6 {\r
-  margin-left: 0\r
-} \r
-\r
-body pre {\r
-  margin: 0.5em 10% 0.5em 1em;\r
-  line-height: 1.0;\r
-  color: navy;\r
-}\r
-\r
-tt.literal, code.literal {\r
-  color: navy;\r
-}\r
-\r
-div.literallayout p {\r
-  padding: 0em;\r
-  margin: 0em;\r
-}\r
-\r
-div.literallayout {\r
-  font-family: monospace;\r
-#  margin: 0.5em 10% 0.5em 1em;\r
-  margin: 0em;\r
-  color: navy;\r
-  border: 1px solid silver;\r
-  background: #f4f4f4;\r
-  padding: 0.5em;\r
-}\r
-\r
-.programlisting, .screen {\r
-  border: 1px solid silver;\r
-  background: #f4f4f4;\r
-  margin: 0.5em 10% 0.5em 0;\r
-  padding: 0.5em 1em;\r
-}\r
-\r
-div.sidebar {\r
-  background: #ffffee;\r
-  margin: 1.0em 10% 0.5em 0;\r
-  padding: 0.5em 1em;\r
-  border: 1px solid silver;\r
-}\r
-div.sidebar * { padding: 0; }\r
-div.sidebar div { margin: 0; }\r
-div.sidebar p.title {\r
-  font-family: sans-serif;\r
-  margin-top: 0.5em;\r
-  margin-bottom: 0.2em;\r
-}\r
-\r
-div.bibliomixed {\r
-  margin: 0.5em 5% 0.5em 1em;\r
-}\r
-\r
-div.glossary dt {\r
-  font-weight: bold;\r
-}\r
-div.glossary dd p {\r
-  margin-top: 0.2em;\r
-}\r
-\r
-dl {\r
-  margin: .8em 0;\r
-  line-height: 1.2;\r
-}\r
-\r
-dt {\r
-  margin-top: 0.5em;\r
-}\r
-\r
-dt span.term {\r
-  font-style: italic;\r
-}\r
-\r
-div.variablelist dd p {\r
-  margin-top: 0;\r
-}\r
-\r
-div.itemizedlist li, div.orderedlist li {\r
-  margin-left: -0.8em;\r
-  margin-top: 0.5em;\r
-}\r
-\r
-ul, ol {\r
-    list-style-position: outside;\r
-}\r
-\r
-div.sidebar ul, div.sidebar ol {\r
-    margin-left: 2.8em;\r
-}\r
-\r
-div.itemizedlist p.title,\r
-div.orderedlist p.title,\r
-div.variablelist p.title\r
-{\r
-  margin-bottom: -0.8em;\r
-}\r
-\r
-div.revhistory table {\r
-  border-collapse: collapse;\r
-  border: none;\r
-}\r
-div.revhistory th {\r
-  border: none;\r
-  color: #527bbd;\r
-  font-family: tahoma, verdana, sans-serif;\r
-}\r
-div.revhistory td {\r
-  border: 1px solid silver;\r
-}\r
-\r
-/* Keep TOC and index lines close together. */\r
-div.toc dl, div.toc dt,\r
-div.list-of-figures dl, div.list-of-figures dt,\r
-div.list-of-tables dl, div.list-of-tables dt,\r
-div.indexdiv dl, div.indexdiv dt\r
-{\r
-  line-height: normal;\r
-  margin-top: 0;\r
-  margin-bottom: 0;\r
-}\r
-\r
-/*\r
-  Table styling does not work because of overriding attributes in\r
-  generated HTML.\r
-*/\r
-div.table table,\r
-div.informaltable table\r
-{\r
-    margin-left: 0;\r
-    margin-right: 5%;\r
-    margin-bottom: 0.8em;\r
-}\r
-div.informaltable table\r
-{\r
-    margin-top: 0.4em\r
-}\r
-div.table thead,\r
-div.table tfoot,\r
-div.table tbody,\r
-div.informaltable thead,\r
-div.informaltable tfoot,\r
-div.informaltable tbody\r
-{\r
-    /* No effect in IE6. */\r
-    border-top: 2px solid #527bbd;\r
-    border-bottom: 2px solid #527bbd;\r
-}\r
-div.table thead, div.table tfoot,\r
-div.informaltable thead, div.informaltable tfoot\r
-{\r
-    font-weight: bold;\r
-}\r
-\r
-div.mediaobject img {\r
-    border: 1px solid silver;\r
-    margin-bottom: 0.8em;\r
-}\r
-div.figure p.title,\r
-div.table p.title\r
-{\r
-  margin-top: 1em;\r
-  margin-bottom: 0.4em;\r
-}\r
-\r
-@media print {\r
-  div.navheader, div.navfooter { display: none; }\r
-}\r
+/*
+  CSS stylesheet for XHTML produced by DocBook XSL stylesheets.
+  Tested with XSL stylesheets 1.61.2, 1.67.2
+*/
+
+span.strong {
+  font-weight: bold;
+}
+
+body blockquote {
+  margin-top: .75em;
+  line-height: 1.5;
+  margin-bottom: .75em;
+}
+
+html body {
+  margin: 1em 5% 1em 5%;
+  line-height: 1.2;
+}
+
+body div {
+  margin: 0;
+}
+
+h1, h2, h3, h4, h5, h6,
+div.toc p b,
+div.list-of-figures p b,
+div.list-of-tables p b,
+div.abstract p.title
+{
+  color: #527bbd;
+  font-family: tahoma, verdana, sans-serif;
+}
+
+div.toc p:first-child,
+div.list-of-figures p:first-child,
+div.list-of-tables p:first-child,
+div.example p.title
+{
+  margin-bottom: 0.2em;
+}
+
+body h1 {
+  margin: .0em 0 0 -4%;
+  line-height: 1.3;
+  border-bottom: 2px solid silver;
+}
+
+body h2 {
+  margin: 0.5em 0 0 -4%;
+  line-height: 1.3;
+  border-bottom: 2px solid silver;
+}
+
+body h3 {
+  margin: .8em 0 0 -3%;
+  line-height: 1.3;
+}
+
+body h4 {
+  margin: .8em 0 0 -3%;
+  line-height: 1.3;
+}
+
+body h5 {
+  margin: .8em 0 0 -2%;
+  line-height: 1.3;
+}
+
+body h6 {
+  margin: .8em 0 0 -1%;
+  line-height: 1.3;
+}
+
+body hr {
+  border: none; /* Broken on IE6 */
+}
+div.footnotes hr {
+  border: 1px solid silver;
+}
+
+div.navheader th, div.navheader td, div.navfooter td {
+  font-family: sans-serif;
+  font-size: 0.9em;
+  font-weight: bold;
+  color: #527bbd;
+}
+div.navheader img, div.navfooter img {
+  border-style: none;
+}
+div.navheader a, div.navfooter a {
+  font-weight: normal;
+}
+div.navfooter hr {
+  border: 1px solid silver;
+}
+
+body td {
+  line-height: 1.2
+}
+
+body th {
+  line-height: 1.2;
+}
+
+ol {
+  line-height: 1.2;
+}
+
+ul, body dir, body menu {
+  line-height: 1.2;
+}
+
+html {
+  margin: 0;
+  padding: 0;
+}
+
+body h1, body h2, body h3, body h4, body h5, body h6 {
+  margin-left: 0
+}
+
+body pre {
+  margin: 0.5em 10% 0.5em 1em;
+  line-height: 1.0;
+  color: navy;
+}
+
+tt.literal, code.literal {
+  color: navy;
+}
+
+div.literallayout p {
+  padding: 0em;
+  margin: 0em;
+}
+
+div.literallayout {
+  font-family: monospace;
+#  margin: 0.5em 10% 0.5em 1em;
+  margin: 0em;
+  color: navy;
+  border: 1px solid silver;
+  background: #f4f4f4;
+  padding: 0.5em;
+}
+
+.programlisting, .screen {
+  border: 1px solid silver;
+  background: #f4f4f4;
+  margin: 0.5em 10% 0.5em 0;
+  padding: 0.5em 1em;
+}
+
+div.sidebar {
+  background: #ffffee;
+  margin: 1.0em 10% 0.5em 0;
+  padding: 0.5em 1em;
+  border: 1px solid silver;
+}
+div.sidebar * { padding: 0; }
+div.sidebar div { margin: 0; }
+div.sidebar p.title {
+  font-family: sans-serif;
+  margin-top: 0.5em;
+  margin-bottom: 0.2em;
+}
+
+div.bibliomixed {
+  margin: 0.5em 5% 0.5em 1em;
+}
+
+div.glossary dt {
+  font-weight: bold;
+}
+div.glossary dd p {
+  margin-top: 0.2em;
+}
+
+dl {
+  margin: .8em 0;
+  line-height: 1.2;
+}
+
+dt {
+  margin-top: 0.5em;
+}
+
+dt span.term {
+  font-style: italic;
+}
+
+div.variablelist dd p {
+  margin-top: 0;
+}
+
+div.itemizedlist li, div.orderedlist li {
+  margin-left: -0.8em;
+  margin-top: 0.5em;
+}
+
+ul, ol {
+    list-style-position: outside;
+}
+
+div.sidebar ul, div.sidebar ol {
+    margin-left: 2.8em;
+}
+
+div.itemizedlist p.title,
+div.orderedlist p.title,
+div.variablelist p.title
+{
+  margin-bottom: -0.8em;
+}
+
+div.revhistory table {
+  border-collapse: collapse;
+  border: none;
+}
+div.revhistory th {
+  border: none;
+  color: #527bbd;
+  font-family: tahoma, verdana, sans-serif;
+}
+div.revhistory td {
+  border: 1px solid silver;
+}
+
+/* Keep TOC and index lines close together. */
+div.toc dl, div.toc dt,
+div.list-of-figures dl, div.list-of-figures dt,
+div.list-of-tables dl, div.list-of-tables dt,
+div.indexdiv dl, div.indexdiv dt
+{
+  line-height: normal;
+  margin-top: 0;
+  margin-bottom: 0;
+}
+
+/*
+  Table styling does not work because of overriding attributes in
+  generated HTML.
+*/
+div.table table,
+div.informaltable table
+{
+    margin-left: 0;
+    margin-right: 5%;
+    margin-bottom: 0.8em;
+}
+div.informaltable table
+{
+    margin-top: 0.4em
+}
+div.table thead,
+div.table tfoot,
+div.table tbody,
+div.informaltable thead,
+div.informaltable tfoot,
+div.informaltable tbody
+{
+    /* No effect in IE6. */
+    border-top: 2px solid #527bbd;
+    border-bottom: 2px solid #527bbd;
+}
+div.table thead, div.table tfoot,
+div.informaltable thead, div.informaltable tfoot
+{
+    font-weight: bold;
+}
+
+div.mediaobject img {
+    border: 1px solid silver;
+    margin-bottom: 0.8em;
+}
+div.figure p.title,
+div.table p.title
+{
+  margin-top: 1em;
+  margin-bottom: 0.4em;
+}
+
+@media print {
+  div.navheader, div.navfooter { display: none; }
+}
diff --git a/Documentation/docbook.xsl b/Documentation/docbook.xsl
new file mode 100644 (file)
index 0000000..9a6912c
--- /dev/null
@@ -0,0 +1,5 @@
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+               version='1.0'>
+ <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/>
+ <xsl:output method="html" encoding="UTF-8" indent="no" />
+</xsl:stylesheet>
index bdc7332c7b11c25957ee170b0582f9fe182755eb..da034223f3a0c07bc3451814ae8d087423f1e2f5 100644 (file)
@@ -52,4 +52,3 @@
        Deepen the history of a 'shallow' repository created by
        `git clone` with `--depth=<depth>` option (see gitlink:git-clone[1])
        by the specified number of commits.
-
diff --git a/Documentation/fix-texi.perl b/Documentation/fix-texi.perl
new file mode 100755 (executable)
index 0000000..ff7d78f
--- /dev/null
@@ -0,0 +1,15 @@
+#!/usr/bin/perl -w
+
+while (<>) {
+       if (/^\@setfilename/) {
+               $_ = "\@setfilename git.info\n";
+       } elsif (/^\@direntry/) {
+               print '@dircategory Development
+@direntry
+* Git: (git).           A fast distributed revision control system
+@end direntry
+';     }
+       unless (/^\@direntry/../^\@end direntry/) {
+               print;
+       }
+}
index a0c9f68580594f785aaec0472ca8404701d4d86d..dee38f8250f2a2757155853b246b75029dd303bc 100644 (file)
@@ -3,32 +3,35 @@ git-add(1)
 
 NAME
 ----
-git-add - Add file contents to the changeset to be committed next
+git-add - Add file contents to the index
 
 SYNOPSIS
 --------
-'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--] <file>...
+'git-add' [-n] [-v] [-f] [--interactive | -i] [-u] [--refresh] [--] <file>...
 
 DESCRIPTION
 -----------
-All the changed file contents to be committed together in a single set
-of changes must be "added" with the 'add' command before using the
-'commit' command.  This is not only for adding new files.  Even modified
-files must be added to the set of changes about to be committed.
+This command adds the current content of new or modified files to the
+index, thus staging that content for inclusion in the next commit.
 
-This command can be performed multiple times before a commit. The added
-content corresponds to the state of specified file(s) at the time the
-'add' command is used. This means the 'commit' command will not consider
-subsequent changes to already added content if it is not added again before
-the commit.
+The "index" holds a snapshot of the content of the working tree, and it
+is this snapshot that is taken as the contents of the next commit.  Thus
+after making any changes to the working directory, and before running
+the commit command, you must use the 'add' command to add any new or
+modified files to the index.
 
-The 'git status' command can be used to obtain a summary of what is included
-for the next commit.
+This command can be performed multiple times before a commit.  It only
+adds the content of the specified file(s) at the time the add command is
+run; if you want subsequent changes included in the next commit, then
+you must run 'git add' again to add the new content to the index.
 
-This command can be used to add ignored files with `-f` (force)
-option, but they have to be
-explicitly and exactly specified from the command line.  File globbing
-and recursive behaviour do not add ignored files.
+The 'git status' command can be used to obtain a summary of which
+files have changes that are staged for the next commit.
+
+The 'add' command can be used to add ignored files with `-f` (force)
+option, but they have to be explicitly and exactly specified from the
+command line.  File globbing and recursive behaviour do not add ignored
+files.
 
 Please see gitlink:git-commit[1] for alternative ways to add content to a
 commit.
@@ -63,6 +66,10 @@ OPTIONS
        command line. If no paths are specified, all tracked files are
        updated.
 
+\--refresh::
+       Don't add the file(s), but only refresh their stat()
+       information in the index.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
@@ -228,4 +235,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index ba79773f79fc8823f1c611e51b163866ada26306..e4a6b3a6f0fc258e8a2052d1d56a71fb57667d39 100644 (file)
@@ -12,7 +12,7 @@ SYNOPSIS
 'git-am' [--signoff] [--dotest=<dir>] [--keep] [--utf8 | --no-utf8]
          [--3way] [--interactive] [--binary]
          [--whitespace=<option>] [-C<n>] [-p<n>]
-         <mbox>...
+         <mbox>|<Maildir>...
 'git-am' [--skip | --resolved]
 
 DESCRIPTION
@@ -23,9 +23,10 @@ current branch.
 
 OPTIONS
 -------
-<mbox>...::
+<mbox>|<Maildir>...::
        The list of mailbox files to read patches from. If you do not
-       supply this argument, reads from the standard input.
+       supply this argument, reads from the standard input. If you supply
+       directories, they'll be treated as Maildirs.
 
 -s, --signoff::
        Add `Signed-off-by:` line to the commit message, using
@@ -126,8 +127,7 @@ 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, just like 'git-applymbox' does.  You can
-recover from this in one of two ways:
+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'
   option.
@@ -144,7 +144,7 @@ names.
 
 SEE ALSO
 --------
-gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1].
+gitlink:git-apply[1].
 
 
 Author
@@ -158,4 +158,3 @@ Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.o
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 3bd2c995da7d65977481837486dc15e5e96baf91..f03f661652814d0900f3564c1914b217dc465a01 100644 (file)
@@ -183,4 +183,3 @@ Documentation by Junio C Hamano
 GIT
 ---
 Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt
deleted file mode 100644 (file)
index ea919ba..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-git-applymbox(1)
-================
-
-NAME
-----
-git-applymbox - Apply a series of patches in a mailbox
-
-
-SYNOPSIS
---------
-'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
-
-DESCRIPTION
------------
-Splits mail messages in a mailbox into commit log message,
-authorship information and patches, and applies them to the
-current branch.
-
-
-OPTIONS
--------
--q::
-       Apply patches interactively.  The user will be given
-       opportunity to edit the log message and the patch before
-       attempting to apply it.
-
--k::
-       Usually the program 'cleans up' the Subject: header line
-       to extract the title line for the commit log message,
-       among which (1) remove 'Re:' or 're:', (2) leading
-       whitespaces, (3) '[' up to ']', typically '[PATCH]', and
-       then prepends "[PATCH] ".  This flag forbids this
-       munging, and is most useful when used to read back 'git
-       format-patch -k' output.
-
--m::
-       Patches are applied with `git-apply` command, and unless
-       it cleanly applies without fuzz, the processing fails.
-       With this flag, if a tree that the patch applies cleanly
-       is found in a repository, the patch is applied to the
-       tree and then a 3-way merge between the resulting tree
-       and the current tree.
-
--u::
-       Pass `-u` flag to `git-mailinfo` (see gitlink:git-mailinfo[1]).
-       The proposed commit log message taken from the e-mail
-       are re-coded into UTF-8 encoding (configuration variable
-       `i18n.commitencoding` can be used to specify project's
-       preferred encoding if it is not UTF-8).  This used to be
-       optional but now it is the default.
-+
-Note that the patch is always used as-is without charset
-conversion, even with this flag.
-
--n::
-       Pass `-n` flag to `git-mailinfo` (see
-       gitlink:git-mailinfo[1]).
-
--c .dotest/<num>::
-       When the patch contained in an e-mail does not cleanly
-       apply, the command exits with an error message. The
-       patch and extracted message are found in .dotest/, and
-       you could re-run 'git applymbox' with '-c .dotest/<num>'
-       flag to restart the process after inspecting and fixing
-       them.
-
-<mbox>::
-       The name of the file that contains the e-mail messages
-       with patches.  This file should be in the UNIX mailbox
-       format.  See 'SubmittingPatches' document to learn about
-       the formatting convention for e-mail submission.
-
-<signoff>::
-       The name of the file that contains your "Signed-off-by"
-       line.  See 'SubmittingPatches' document to learn what
-       "Signed-off-by" line means.  You can also just say
-       'yes', 'true', 'me', or 'please' to use an automatically
-       generated "Signed-off-by" line based on your committer
-       identity.
-
-
-SEE ALSO
---------
-gitlink:git-am[1], gitlink:git-applypatch[1].
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-applypatch.txt b/Documentation/git-applypatch.txt
deleted file mode 100644 (file)
index 451434a..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-git-applypatch(1)
-=================
-
-NAME
-----
-git-applypatch - Apply one patch extracted from an e-mail
-
-
-SYNOPSIS
---------
-'git-applypatch' <msg> <patch> <info> [<signoff>]
-
-DESCRIPTION
------------
-This is usually not what an end user wants to run directly.  See
-gitlink:git-am[1] instead.
-
-Takes three files <msg>, <patch>, and <info> prepared from an
-e-mail message by 'git-mailinfo', and creates a commit.  It is
-usually not necessary to use this command directly.
-
-This command can run `applypatch-msg`, `pre-applypatch`, and
-`post-applypatch` hooks.  See link:hooks.html[hooks] for more
-information.
-
-
-OPTIONS
--------
-<msg>::
-       Commit log message (sans the first line, which comes
-       from e-mail Subject stored in <info>).
-
-<patch>::
-       The patch to apply.
-
-<info>::
-       Author and subject information extracted from e-mail,
-       used on "author" line and as the first line of the
-       commit log message.
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
-
index 82cb41d2791abbd32bd8209660e9879b9f2b8f32..7091b8d61cf4871e2cecc7b18bc6997ea42b4ef9 100644 (file)
@@ -17,26 +17,26 @@ DESCRIPTION
 Imports a project from one or more Arch repositories. It will follow branches
 and repositories within the namespaces defined by the <archive/branch>
 parameters supplied. If it cannot find the remote branch a merge comes from
-it will just import it as a regular commit. If it can find it, it will mark it 
-as a merge whenever possible (see discussion below). 
+it will just import it as a regular commit. If it can find it, it will mark it
+as a merge whenever possible (see discussion below).
 
-The script expects you to provide the key roots where it can start the import 
-from an 'initial import' or 'tag' type of Arch commit. It will follow and 
-import new branches within the provided roots. 
+The script expects you to provide the key roots where it can start the import
+from an 'initial import' or 'tag' type of Arch commit. It will follow and
+import new branches within the provided roots.
 
-It expects to be dealing with one project only. If it sees 
-branches that have different roots, it will refuse to run. In that case, 
-edit your <archive/branch> parameters to define clearly the scope of the 
-import. 
+It expects to be dealing with one project only. If it sees
+branches that have different roots, it will refuse to run. In that case,
+edit your <archive/branch> parameters to define clearly the scope of the
+import.
 
-`git-archimport` uses `tla` extensively in the background to access the 
+`git-archimport` uses `tla` extensively in the background to access the
 Arch repository.
 Make sure you have a recent version of `tla` available in the path. `tla` must
-know about the repositories you pass to `git-archimport`. 
+know about the repositories you pass to `git-archimport`.
 
-For the initial import `git-archimport` expects to find itself in an empty 
-directory. To follow the development of a project that uses Arch, rerun 
-`git-archimport` with the same parameters as the initial import to perform 
+For the initial import `git-archimport` expects to find itself in an empty
+directory. To follow the development of a project that uses Arch, rerun
+`git-archimport` with the same parameters as the initial import to perform
 incremental imports.
 
 While git-archimport will try to create sensible branch names for the
@@ -54,15 +54,15 @@ convert Arch repositories that had been rotated periodically.
 
 MERGES
 ------
-Patch merge data from Arch is used to mark merges in git as well. git 
+Patch merge data from Arch is used to mark merges in git as well. git
 does not care much about tracking patches, and only considers a merge when a
 branch incorporates all the commits since the point they forked. The end result
-is that git will have a good idea of how far branches have diverged. So the 
+is that git will have a good idea of how far branches have diverged. So the
 import process does lose some patch-trading metadata.
 
-Fortunately, when you try and merge branches imported from Arch, 
-git will find a good merge base, and it has a good chance of identifying 
-patches that have been traded out-of-sequence between the branches. 
+Fortunately, when you try and merge branches imported from Arch,
+git will find a good merge base, and it has a good chance of identifying
+patches that have been traded out-of-sequence between the branches.
 
 OPTIONS
 -------
@@ -71,10 +71,10 @@ OPTIONS
        Display usage.
 
 -v::
-       Verbose output. 
+       Verbose output.
 
 -T::
-       Many tags. Will create a tag for every commit, reflecting the commit 
+       Many tags. Will create a tag for every commit, reflecting the commit
        name in the Arch repository.
 
 -f::
@@ -104,7 +104,7 @@ OPTIONS
 
 
 <archive/branch>::
-       Archive/branch identifier in a format that `tla log` understands. 
+       Archive/branch identifier in a format that `tla log` understands.
 
 
 Author
@@ -118,4 +118,3 @@ Documentation by Junio C Hamano, Martin Langhoff and the git-list <git@vger.kern
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 5f68ee1584b294a9395f2a3d4f29e0b1208f5913..1072fb87d1fe38a74dc38e2d6886acfb44d9262b 100644 (file)
@@ -8,7 +8,7 @@ git-bisect - Find the change that introduced a bug by binary search
 
 SYNOPSIS
 --------
-'git bisect' <subcommand> <options> 
+'git bisect' <subcommand> <options>
 
 DESCRIPTION
 -----------
@@ -200,4 +200,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 44678b0c3601512df024e3670aadeabd5317b0c9..66f1203701350f42cb028c6971c169ac129332ec 100644 (file)
@@ -8,7 +8,7 @@ git-blame - Show what revision and author last modified each line of a file
 SYNOPSIS
 --------
 [verse]
-'git-blame' [-c] [-b] [--root] [-s] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m]
+'git-blame' [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [--incremental] [-L n,m]
             [-S <revs-file>] [-M] [-C] [-C] [--since=<date>]
             [<rev> | --contents <file>] [--] <file>
 
@@ -63,6 +63,11 @@ include::blame-options.txt[]
 -s::
        Suppress 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.
+
+
 THE PORCELAIN FORMAT
 --------------------
 
index 8dc5171f5e685ab18d466751710f44e8aba104b7..33bc31b0d4491169cdbfb06f3dc6c42ec84b1365 100644 (file)
@@ -41,7 +41,7 @@ to happen.
 
 With a `-d` or `-D` option, `<branchname>` will be deleted.  You may
 specify more than one branch for deletion.  If the branch currently
-has a ref log then the ref log will also be deleted. Use -r together with -d
+has a reflog then the reflog will also be deleted. Use -r together with -d
 to delete remote-tracking branches.
 
 
@@ -54,9 +54,9 @@ OPTIONS
        Delete a branch irrespective of its index status.
 
 -l::
-       Create the branch's ref log.  This activates recording of
-       all changes to made the branch ref, enabling use of date
-       based sha1 expressions such as "<branchname>@{yesterday}".
+       Create the 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}".
 
 -f::
        Force the creation of a new branch even if it means deleting
@@ -130,12 +130,12 @@ Delete unneeded branch::
 ------------
 $ git clone git://git.kernel.org/.../git.git my.git
 $ cd my.git
-$ git branch -d -r todo html man   <1>
-$ git branch -D test               <2>
+$ git branch -d -r origin/todo origin/html origin/man   <1>
+$ git branch -D test                                    <2>
 ------------
 +
-<1> delete remote-tracking branches "todo", "html", "man"
-<2> delete "test" branch even if the "master" branch does not have all
+<1> Delete remote-tracking branches "todo", "html", "man"
+<2> Delete "test" branch even if the "master" branch does not have all
 commits from test branch.
 
 
@@ -158,4 +158,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 075c0d05efca85c2357e09aa640aa4b7f0305419..afa095c79529dc92d5db6c54effd435c57f5eb7a 100644 (file)
@@ -71,4 +71,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index ceb51959b1d28a7298ddd42b0e3e968c568195a7..856d2af2ed9c1ccfcd819191060447b62e49b8b6 100644 (file)
@@ -34,4 +34,3 @@ Documentation by James Bowes.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 6dd6db04bb91ae97472ce40d6b76ff8d409b99f7..b1a8ce110cd3caa641a53e7944479bb8f84bec44 100644 (file)
@@ -182,4 +182,3 @@ Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 918d8ee720a573f91ec6efa654da5c2cdc05d7b0..818b720b914fa33d3a4b5c7858573bd59f0b0945 100644 (file)
@@ -62,9 +62,9 @@ OPTIONS
        configuration variable.
 
 -l::
-       Create the new branch's ref log.  This activates recording of
-       all changes to made the branch ref, enabling use of date
-       based sha1 expressions such as "<branchname>@{yesterday}".
+       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}".
 
 -m::
        If you have local modifications to one or more files that
@@ -215,4 +215,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 68bba982606b5e17b730f0e5d4d201923231c235..47b1e8c2fcd567b7e9d673f2d3ff30c9c32a1b83 100644 (file)
@@ -68,4 +68,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 8c7d9670d3b31353cd0b5a9bc76e63f7a4a7d7a1..e6943822cd1c003d666178ea26f7b24e56ad210f 100644 (file)
@@ -67,4 +67,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-citool.txt b/Documentation/git-citool.txt
new file mode 100644 (file)
index 0000000..5217ab2
--- /dev/null
@@ -0,0 +1,32 @@
+git-citool(1)
+=============
+
+NAME
+----
+git-citool - Graphical alternative to git-commit
+
+SYNOPSIS
+--------
+'git citool'
+
+DESCRIPTION
+-----------
+A Tcl/Tk based graphical interface to review modified files, stage
+them into the index, enter a commit message and record the new
+commit onto the current branch.  This interface is an alternative
+to the less interactive gitlink:git-commit[1] program.
+
+git-citool is actually a standard alias for 'git gui citool'.
+See gitlink:git-gui[1] for more details.
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index ac938d39df7a4b5b5ea3e4ca8cb2cf505fc49752..227f092e268497aad9c16cff4d796dabf60401de 100644 (file)
@@ -9,7 +9,8 @@ git-clone - Clone a repository into a new directory
 SYNOPSIS
 --------
 [verse]
-'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
+'git-clone' [--template=<template_directory>]
+         [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare]
          [-o <name>] [-u <upload-pack>] [--reference <repository>]
          [--depth <depth>] <repository> [<directory>]
 
@@ -40,8 +41,19 @@ OPTIONS
        this flag bypasses normal "git aware" transport
        mechanism and clones the repository by making a copy of
        HEAD and everything under objects and refs directories.
-       The files under .git/objects/ directory are hardlinked
-       to save space when possible.
+       The files under `.git/objects/` directory are hardlinked
+       to save space when possible.  This is now the default when
+       the source repository is specified with `/path/to/repo`
+       syntax, so it essentially is a no-op option.  To force
+       copying instead of hardlinking (which may be desirable
+       if you are trying to make a back-up of your repository),
+       but still avoid the usual "git aware" transport
+       mechanism, `--no-hardlinks` can be used.
+
+--no-hardlinks::
+       Optimize the cloning process from a repository on a
+       local filesystem by copying files under `.git/objects`
+       directory.
 
 --shared::
 -s::
@@ -64,6 +76,7 @@ OPTIONS
        Operate quietly.  This flag is passed to "rsync" and
        "git-fetch-pack" commands when given.
 
+--no-checkout::
 -n::
        No checkout of HEAD is performed after the clone is complete.
 
@@ -106,8 +119,9 @@ OPTIONS
        as patches.
 
 <repository>::
-       The (possibly remote) repository to clone from.  It can
-       be any URL git-fetch supports.
+       The (possibly remote) repository to clone from.  See the
+       <<URLS,URLS>> section below for more information on specifying
+       repositories.
 
 <directory>::
        The name of a new directory to clone into.  The "humanish"
@@ -116,6 +130,8 @@ OPTIONS
        for "host.xz:foo/.git").  Cloning into an existing directory
        is not allowed.
 
+include::urls.txt[]
+
 Examples
 --------
 
@@ -175,4 +191,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 504a3aa1b460ffac9308bec4ff72cf657f8fec30..5870c2ce4759f56455cb420ce076dc95770513d4 100644 (file)
@@ -40,7 +40,7 @@ OPTIONS
 
 -p <parent commit>::
        Each '-p' indicates the id of a parent commit object.
-       
+
 
 Commit Information
 ------------------
@@ -72,7 +72,7 @@ GIT_AUTHOR_EMAIL:
                name = "Your Name"
                email = "your@email.address.xz"
 
-A commit comment is read from stdin (max 999 chars). If a changelog
+A commit comment is read from stdin. If a changelog
 entry is not provided via "<" redirection, "git-commit-tree" will just wait
 for one to be entered and terminated with ^D.
 
@@ -107,4 +107,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 53a7bb0895036e4d66086b8c656e74588c82c38c..e54fb12103d4e4955f41532566cb4558432a97bb 100644 (file)
@@ -8,33 +8,34 @@ git-commit - Record changes to the repository
 SYNOPSIS
 --------
 [verse]
-'git-commit' [-a | --interactive] [-s] [-v]
+'git-commit' [-a | --interactive] [-s] [-v] [-u]
           [(-c | -C) <commit> | -F <file> | -m <msg> | --amend]
           [--no-verify] [-e] [--author <author>]
           [--] [[-i | -o ]<file>...]
 
 DESCRIPTION
 -----------
-Use 'git commit' when you want to record your changes into the repository
-along with a log message describing what the commit is about. All changes
-to be committed must be explicitly identified using one of the following
-methods:
+Use 'git commit' to store the current contents of the index in a new
+commit along with a log message describing the changes you have made.
+
+The content to be added can be specified in several ways:
 
 1. by using gitlink:git-add[1] to incrementally "add" changes to the
-   next commit before using the 'commit' command (Note: even modified
+   index before using the 'commit' command (Note: even modified
    files must be "added");
 
-2. by using gitlink:git-rm[1] to identify content removal for the next
-   commit, again before using the 'commit' command;
+2. by using gitlink:git-rm[1] to remove files from the working tree
+   and the index, again before using the 'commit' command;
 
-3. by directly listing files containing changes to be committed as arguments
-   to the 'commit' command, in which cases only those files alone will be
-   considered for the commit;
+3. by listing files as arguments to the 'commit' command, in which
+   case the commit will ignore changes staged in the index, and instead
+   record the current content of the listed files;
 
-4. by using the -a switch with the 'commit' command to automatically "add"
-   changes from all known files i.e. files that have already been committed
-   before, and to automatically "rm" files that have been
-   removed from the working tree, and perform the actual commit.
+4. by using the -a switch with the 'commit' command to automatically
+   "add" changes from all known files (i.e. all files that are already
+   listed in the index) and to automatically "rm" files in the index
+   that have been removed from the working tree, and then perform the
+   actual commit;
 
 5. by using the --interactive switch with the 'commit' command to decide one
    by one which files should be part of the commit, before finalizing the
@@ -71,9 +72,16 @@ OPTIONS
        Override the author name used in the commit.  Use
        `A U Thor <author@example.com>` format.
 
--m <msg>::
+-m <msg>|--message=<msg>::
        Use the given <msg> as the commit message.
 
+-t <file>|--template=<file>::
+       Use the contents of the given file as the initial version
+       of the commit message. The editor is invoked and you can
+       make subsequent changes. If a message is specified using
+       the `-m` or `-F` options, this option has no effect. This
+       overrides the `commit.template` configuration variable.
+
 -s|--signoff::
        Add Signed-off-by line at the end of the commit message.
 
@@ -115,6 +123,19 @@ but can be used to amend a merge commit.
        as well.  This is usually not what you want unless you
        are concluding a conflicted merge.
 
+-u|--untracked-files::
+       Show all untracked files, also those in uninteresting
+       directories, in the "Untracked files:" section of commit
+       message template.  Without this option only its name and
+       a trailing slash are displayed for each untracked
+       directory.
+
+-v|--verbose::
+       Show unified diff between the HEAD commit and what
+       would be committed at the bottom of the commit message
+       template.  Note that this diff output doesn't have its
+       lines prefixed with '#'.
+
 -q|--quiet::
        Suppress commit summary message.
 
@@ -231,10 +252,12 @@ on the Subject: line and the rest of the commit in the body.
 
 include::i18n.txt[]
 
-ENVIRONMENT VARIABLES
----------------------
-The command specified by either the VISUAL or EDITOR environment
-variables is used to edit the commit log message.
+ENVIRONMENT AND CONFIGURATION VARIABLES
+---------------------------------------
+The editor used to edit the commit log message will be chosen from the
+GIT_EDITOR environment variable, the core.editor configuration variable, the
+VISUAL environment variable, or the EDITOR environment variable (in that
+order).
 
 HOOKS
 -----
index 387d7bc84123d7a4e694104b5e59fa7535fceda7..c3dffffe3209317fdd228c9a4a1ac89cb1c348f4 100644 (file)
@@ -9,17 +9,17 @@ git-config - Get and set repository or global options
 SYNOPSIS
 --------
 [verse]
-'git-config' [--system | --global] name [value [value_regex]]
-'git-config' [--system | --global] --add name value
-'git-config' [--system | --global] --replace-all name [value [value_regex]]
-'git-config' [--system | --global] [type] --get name [value_regex]
-'git-config' [--system | --global] [type] --get-all name [value_regex]
-'git-config' [--system | --global] [type] --get-regexp name_regex [value_regex]
-'git-config' [--system | --global] --unset name [value_regex]
-'git-config' [--system | --global] --unset-all name [value_regex]
-'git-config' [--system | --global] --rename-section old_name new_name
-'git-config' [--system | --global] --remove-section name
-'git-config' [--system | --global] -l | --list
+'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] [-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]
+'git-config' [<file-option>] --unset name [value_regex]
+'git-config' [<file-option>] --unset-all name [value_regex]
+'git-config' [<file-option>] --rename-section old_name new_name
+'git-config' [<file-option>] --remove-section name
+'git-config' [<file-option>] [-z|--null] -l | --list
 
 DESCRIPTION
 -----------
@@ -37,14 +37,19 @@ prepend a single exclamation mark in front (see also <<EXAMPLES>>).
 The type specifier can be either '--int' or '--bool', which will make
 'git-config' ensure that the variable(s) are of the given type and
 convert the value to the canonical form (simple decimal number for int,
-a "true" or "false" string for bool).  Type specifiers currently only
-take effect for reading operations.  If no type specifier is passed,
+a "true" or "false" string for bool).  If no type specifier is passed,
 no checks or transformations are performed on the value.
 
+The file-option can be one of '--system', '--global' or '--file'
+which specify where the values will be read from or written to.
+The default is to assume the config file of the current repository,
+.git/config unless defined otherwise with GIT_DIR and GIT_CONFIG
+(see <<FILES>>).
+
 This command will fail if:
 
-. The .git/config file is invalid,
-. Can not write to .git/config,
+. The config file is invalid,
+. Can not write to the config file,
 . no section was provided,
 . the section or key is invalid,
 . you try to unset an option which does not exist,
@@ -94,6 +99,9 @@ rather than from all available files.
 +
 See also <<FILES>>.
 
+-f config-file, --file config-file::
+       Use the given config file instead of the one specified by GIT_CONFIG.
+
 --remove-section::
        Remove the given section from the configuration file.
 
@@ -118,13 +126,21 @@ See also <<FILES>>.
        in the config file will cause the value to be multiplied
        by 1024, 1048576, or 1073741824 prior to output.
 
+-z, --null::
+       For all options that output values and/or keys, always
+       end values with with the null character (instead of a
+       newline). Use newline instead as a delimiter between
+       key and value. This allows for secure parsing of the
+       output without getting confused e.g. by values that
+       contain line breaks.
+
 
 [[FILES]]
 FILES
 -----
 
-There are three files where git-config will search for configuration
-options:
+If not set explicitely with '--file', there are three files where
+git-config will search for configuration options:
 
 .git/config::
        Repository specific configuration file. (The filename is
@@ -198,9 +214,7 @@ Given a .git/config like this:
 
        ; Proxy settings
        [core]
-               gitproxy="ssh" for "ssh://kernel.org/"
                gitproxy="proxy-command" for kernel.org
-               gitproxy="myprotocol-command" for "my://"
                gitproxy=default-proxy ; for all the rest
 
 you can set the filemode to true with
@@ -275,7 +289,7 @@ To actually match only values with an exclamation mark, you have to
 To add a new proxy, without altering any of the existing ones, use
 
 ------------
-% git config core.gitproxy '"proxy" for example.com'
+% git config core.gitproxy '"proxy-command" for example.com'
 ------------
 
 
@@ -293,4 +307,3 @@ Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.ker
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index b1220c06e193ddf3942da6e5e0faf91368d0886a..9718abf86d8cd36ddae1eae8cf2337e35b927959 100644 (file)
@@ -26,4 +26,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 91c8c92c76648ed5bfdaab6b991e7cb1ce519733..81614111a4bb3bf8ab62650876189ceee5bd6c9e 100644 (file)
@@ -35,4 +35,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index f3590dee04f801f79cf6400a30a0d9c6f4169154..6c423e3a2f1c69e523eeb9cc092126adc2691803 100644 (file)
@@ -8,25 +8,25 @@ git-cvsexportcommit - Export a single commit to a CVS checkout
 
 SYNOPSIS
 --------
-'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-u] [-v] [-c] [-P] [-p] [-a] [-d cvsroot] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
 
 
 DESCRIPTION
 -----------
 Exports a commit from GIT to a CVS checkout, making it easier
-to merge patches from a git repository into a CVS repository. 
+to merge patches from a git repository into a CVS repository.
 
-Execute it from the root of the CVS working copy. GIT_DIR must be defined. 
+Execute it from the root of the CVS working copy. GIT_DIR must be defined.
 See examples below.
 
-It does its best to do the safe thing, it will check that the files are 
-unchanged and up to date in the CVS checkout, and it will not autocommit 
+It does its best to do the safe thing, it will check that the files are
+unchanged and up to date in the CVS checkout, and it will not autocommit
 by default.
 
 Supports file additions, removals, and commits that affect binary files.
 
 If the commit is a merge commit, you must tell git-cvsexportcommit what parent
-should the changeset be done against. 
+should the changeset be done against.
 
 OPTIONS
 -------
@@ -55,9 +55,12 @@ OPTIONS
        Force the parent commit, even if it is not a direct parent.
 
 -m::
-       Prepend the commit message with the provided prefix. 
+       Prepend the commit message with the provided prefix.
        Useful for patch series and the like.
 
+-u::
+       Update affected files from cvs repository before attempting export.
+
 -v::
        Verbose.
 
@@ -70,7 +73,7 @@ Merge one patch into CVS::
 $ export GIT_DIR=~/project/.git
 $ cd ~/project_cvs_checkout
 $ git-cvsexportcommit -v <commit-sha1>
-$ cvs commit -F .mgs <files> 
+$ cvs commit -F .mgs <files>
 ------------
 
 Merge pending patches into CVS automatically -- only if you really know what you are doing::
@@ -92,4 +95,3 @@ Documentation by Martin Langhoff <martin@catalyst.net.nz>
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index e0be8565468c0b278147abc3e83eb7d19f481a40..fdd7ec7edd8d655a1f660db7eca90ee8cfa9ffc2 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
              [-A <author-conv-file>] [-p <options-for-cvsps>] [-P <file>]
              [-C <git_repository>] [-z <fuzz>] [-i] [-k] [-u] [-s <subst>]
              [-a] [-m] [-M <regex>] [-S <regex>] [-L <commitlimit>]
-             [<CVS_module>]
+             [-r <remote>] [<CVS_module>]
 
 
 DESCRIPTION
@@ -25,10 +25,12 @@ Splitting the CVS log into patch sets is done by 'cvsps'.
 At least version 2.1 is required.
 
 You should *never* do any work of your own on the branches that are
-created by git-cvsimport. The initial import will create and populate a
+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
 to work with; after that, you need to 'git merge' incremental imports, or
-any CVS branches, yourself.
+any CVS branches, yourself.  It is advisable to specify a named remote via
+-r to separate and protect the incoming branches.
+
 
 OPTIONS
 -------
@@ -37,7 +39,7 @@ OPTIONS
 
 -d <CVSROOT>::
        The root of the CVS archive. May be local (a simple path) or remote;
-       currently, only the :local:, :ext: and :pserver: access methods 
+       currently, only the :local:, :ext: and :pserver: access methods
        are supported. If not given, git-cvsimport will try to read it
        from `CVS/Root`. If no such file exists, it checks for the
        `CVSROOT` environment variable.
@@ -51,10 +53,19 @@ OPTIONS
         The git repository to import to.  If the directory doesn't
         exist, it will be created.  Default is the current directory.
 
+-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.
+
 -o <branch-for-HEAD>::
-       The 'HEAD' branch from CVS is imported to the 'origin' branch within
-       the git repository, as 'HEAD' already has a special meaning for git.
-       Use this option if you want to import into a different branch.
+       When no remote is specified (via -r) the 'HEAD' branch
+       from CVS is imported to the 'origin' branch within the git
+       repository, as 'HEAD' already has a special meaning for git.
+       When a remote is specified the 'HEAD' branch is named
+       remotes/<remote>/master mirroring git-clone behaviour.
+       Use this option if you want to import into a different
+       branch.
 +
 Use '-o master' for continuing an import that was initially done by
 the old cvs2git tool.
@@ -67,7 +78,7 @@ the old cvs2git tool.
 -k::
        Kill keywords: will extract files with '-kk' from the CVS archive
        to avoid noisy changesets. Highly recommended, but off by default
-       to preserve compatibility with early imported trees. 
+       to preserve compatibility with early imported trees.
 
 -u::
        Convert underscores in tag and branch names to dots.
@@ -89,15 +100,15 @@ If you need to pass multiple options, separate them with a comma.
        Instead of calling cvsps, read the provided cvsps output file. Useful
        for debugging or when cvsps is being handled outside cvsimport.
 
--m::    
+-m::
        Attempt to detect merges based on the commit message. This option
-       will enable default regexes that try to capture the name source 
-       branch name from the commit message. 
+       will enable default regexes that try to capture the name source
+       branch name from the commit message.
 
 -M <regex>::
        Attempt to detect merges based on the commit message with a custom
        regex. It can be used with '-m' to also see the default regexes.
-       You must escape forward slashes. 
+       You must escape forward slashes.
 
 -S <regex>::
        Skip paths matching the regex.
@@ -156,4 +167,3 @@ Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index e5005f02f997437da3251f6c52c6efbdf06557b4..60d0bcf0f31e6b4da6e56d7726ed942744704cbb 100644 (file)
@@ -7,10 +7,53 @@ git-cvsserver - A CVS server emulator for git
 
 SYNOPSIS
 --------
+
+SSH:
+
 [verse]
 export CVS_SERVER=git-cvsserver
 'cvs' -d :ext:user@server/path/repo.git co <HEAD_name>
 
+pserver (/etc/inetd.conf):
+
+[verse]
+cvspserver stream tcp nowait nobody /usr/bin/git-cvsserver git-cvsserver pserver
+
+Usage:
+
+[verse]
+'git-cvsserver' [options] [pserver|server] [<directory> ...]
+
+OPTIONS
+-------
+
+All these options obviously only make sense if enforced by the server side.
+They have been implemented to resemble the gitlink:git-daemon[1] options as
+closely as possible.
+
+--base-path <path>::
+Prepend 'path' to requested CVSROOT
+
+--strict-paths::
+Don't allow recursing into subdirectories
+
+--export-all::
+Don't check for `gitcvs.enabled` in config. You also have to specify a list
+of allowed directories (see below) if you want to use this option.
+
+--version, -V::
+Print version information and exit
+
+--help, -h, -H::
+Print usage information and exit
+
+<directory>::
+You can specify a list of allowed directories. If no directories
+are given, all are allowed. This is an additional restriction, gitcvs
+access still needs to be enabled by the `gitcvs.enabled` config option
+unless '--export-all' was given, too.
+
+
 DESCRIPTION
 -----------
 
index 9ddab712031cc2b71ccfc83dd5b6b475b5bfc809..f902161c087a34d4e244a516cc6dd2e259af2e2e 100644 (file)
@@ -54,6 +54,12 @@ OPTIONS
        'git://example.com/hello.git', `git-daemon` will interpret the path
        as '/srv/git/hello.git'.
 
+--base-path-relaxed::
+       If --base-path is enabled and repo lookup fails, with this option
+       `git-daemon` will attempt to lookup without prefixing the base path.
+       This is useful for switching to --base-path usage, while still
+       allowing the old paths.
+
 --interpolated-path=pathtemplate::
        To support virtual hosting, an interpolated path template can be
        used to dynamically construct alternate paths.  The template
@@ -235,4 +241,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 47a583d3a605d95fb860e6ea7142cebf374ec5aa..ac23e28f2759222bb67fcf2445c229e918d96211 100644 (file)
@@ -8,7 +8,7 @@ git-describe - Show the most recent tag that is reachable from a commit
 
 SYNOPSIS
 --------
-'git-describe' [--all] [--tags] [--abbrev=<n>] <committish>...
+'git-describe' [--all] [--tags] [--contains] [--abbrev=<n>] <committish>...
 
 DESCRIPTION
 -----------
@@ -31,6 +31,11 @@ OPTIONS
        Instead of using only the annotated tags, use any tag
        found in `.git/refs/tags`.
 
+--contains::
+       Instead of finding the tag that predates the commit, find
+       the tag that comes after the commit, and thus contains it.
+       Automatically implies --tags.
+
 --abbrev=<n>::
        Instead of using the default 8 hexadecimal digits as the
        abbreviated object name, use <n> digits.
@@ -119,4 +124,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 2e1e29ef5ae633a005e35f656f30ce66e343de7a..d8a0a86022b805eabe3516fe980d6c626abfa68e 100644 (file)
@@ -26,7 +26,7 @@ include::diff-options.txt[]
        branch" respectively.  With these options, diffs for
        merged entries are not shown.
 +
-The default is to diff against our branch (-2) and the 
+The default is to diff against our branch (-2) and the
 cleanly resolved paths.  The option -0 can be given to
 omit diff output for unmerged entries and just show "Unmerged".
 
@@ -58,4 +58,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 2df581c2c9e598c676fcb95c35c91209d15562ca..7bd262cefd68efe84ff0c405af5eabcdc6f01be8 100644 (file)
@@ -75,7 +75,7 @@ actually doing a "git-write-tree" and comparing that. Except this one is much
 nicer for the case where you just want to check where you are.
 
 So doing a "git-diff-index --cached" is basically very useful when you are
-asking yourself "what have I already marked for being committed, and 
+asking yourself "what have I already marked for being committed, and
 what's the difference to a previous tree".
 
 Non-cached Mode
@@ -130,4 +130,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 6e660e2d08c2d9d795b5ccfb91f755160cf58930..6b3f74efe7405b2b1c2d92a975aadb4363a67be0 100644 (file)
@@ -166,4 +166,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 044cee9b425bedc7addd9a4418b553557902711b..b36e705dd0cc97ca73cb041a3552c8441846f1b8 100644 (file)
@@ -76,10 +76,10 @@ $ git diff --cached   <2>
 $ git diff HEAD       <3>
 ------------
 +
-<1> changes in the working tree not yet staged for the next commit.
-<2> changes between the index and your last commit; what you
+<1> Changes in the working tree not yet staged for the next commit.
+<2> Changes between the index and your last commit; what you
 would be committing if you run "git commit" without "-a" option.
-<3> changes in the working tree since your last commit; what you
+<3> Changes in the working tree since your last commit; what you
 would be committing if you run "git commit -a"
 
 Comparing with arbitrary commits::
@@ -90,30 +90,27 @@ $ git diff HEAD -- ./test  <2>
 $ git diff HEAD^ HEAD      <3>
 ------------
 +
-<1> instead of using the tip of the current branch, compare with the
+<1> Instead of using the tip of the current branch, compare with the
 tip of "test" branch.
-<2> instead of comparing with the tip of "test" branch, compare with
+<2> Instead of comparing with the tip of "test" branch, compare with
 the tip of the current branch, but limit the comparison to the
 file "test".
-<3> compare the version before the last commit and the last commit.
+<3> Compare the version before the last commit and the last commit.
 
 
 Limiting the diff output::
 +
 ------------
 $ git diff --diff-filter=MRC            <1>
-$ git diff --name-status -r             <2>
+$ git diff --name-status                <2>
 $ git diff arch/i386 include/asm-i386   <3>
 ------------
 +
-<1> show only modification, rename and copy, but not addition
+<1> Show only modification, rename and copy, but not addition
 nor deletion.
-<2> show only names and the nature of change, but not actual
-diff output.  --name-status disables usual patch generation
-which in turn also disables recursive behavior, so without -r
-you would only see the directory name if there is a change in a
-file in a subdirectory.
-<3> limit diff output to named subtrees.
+<2> Show only names and the nature of change, but not actual
+diff output.
+<3> Limit diff output to named subtrees.
 
 Munging the diff output::
 +
@@ -122,9 +119,9 @@ $ git diff --find-copies-harder -B -C  <1>
 $ git diff -R                          <2>
 ------------
 +
-<1> spend extra cycles to find renames, copies and complete
+<1> Spend extra cycles to find renames, copies and complete
 rewrites (very expensive).
-<2> output diff in reverse.
+<2> Output diff in reverse.
 
 
 Author
@@ -138,4 +135,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 87e70fe12d09e8e4f2b1995967d9fc1180907190..30ee98d17f537e289c08d94d9b43728a4b147a34 100644 (file)
@@ -302,7 +302,7 @@ change to the project.
        data
        ('from' SP <committish> LF)?
        ('merge' SP <committish> LF)?
-       (filemodify | filedelete | filedeleteall)*
+       (filemodify | filedelete | filecopy | filerename | filedeleteall)*
        LF
 ....
 
@@ -325,11 +325,13 @@ commit message use a 0 length data.  Commit messages are free-form
 and are not interpreted by Git.  Currently they must be encoded in
 UTF-8, as fast-import does not permit other encodings to be specified.
 
-Zero or more `filemodify`, `filedelete` and `filedeleteall` commands
+Zero or more `filemodify`, `filedelete`, `filecopy`, `filerename`
+and `filedeleteall` commands
 may be included to update the contents of the branch prior to
 creating the commit.  These commands may be supplied in any order.
 However it is recommended that a `filedeleteall` command preceed
-all `filemodify` commands in the same commit, as `filedeleteall`
+all `filemodify`, `filecopy` and `filerename` commands in the same
+commit, as `filedeleteall`
 wipes the branch clean (see below).
 
 `author`
@@ -495,6 +497,56 @@ here `<path>` is the complete path of the file or subdirectory to
 be removed from the branch.
 See `filemodify` above for a detailed description of `<path>`.
 
+`filecopy`
+^^^^^^^^^^^^
+Recursively copies an existing file or subdirectory to a different
+location within the branch.  The existing file or directory must
+exist.  If the destination exists it will be completely replaced
+by the content copied from the source.
+
+....
+       'C' SP <path> SP <path> LF
+....
+
+here the first `<path>` is the source location and the second
+`<path>` is the destination.  See `filemodify` above for a detailed
+description of what `<path>` may look like.  To use a source path
+that contains SP the path must be quoted.
+
+A `filecopy` command takes effect immediately.  Once the source
+location has been copied to the destination any future commands
+applied to the source location will not impact the destination of
+the copy.
+
+`filerename`
+^^^^^^^^^^^^
+Renames an existing file or subdirectory to a different location
+within the branch.  The existing file or directory must exist. If
+the destination exists it will be replaced by the source directory.
+
+....
+       'R' SP <path> SP <path> LF
+....
+
+here the first `<path>` is the source location and the second
+`<path>` is the destination.  See `filemodify` above for a detailed
+description of what `<path>` may look like.  To use a source path
+that contains SP the path must be quoted.
+
+A `filerename` command takes effect immediately.  Once the source
+location has been renamed to the destination any future commands
+applied to the source location will create new files there and not
+impact the destination of the rename.
+
+Note that a `filerename` is the same as a `filecopy` followed by a
+`filedelete` of the source location.  There is a slight performance
+advantage to using `filerename`, but the advantage is so small
+that it is never worth trying to convert a delete/add pair in
+source material into a rename for fast-import.  This `filerename`
+command is provided just to simplify frontends that already have
+rename information and don't want bother with decomposing it into a
+`filecopy` followed by a `filedelete`.
+
 `filedeleteall`
 ^^^^^^^^^^^^^^^
 Included in a `commit` command to remove all files (and also all
@@ -910,4 +962,3 @@ Documentation by Shawn O. Pearce <spearce@spearce.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 5fbeab76b7214f871865eaefd5dbe744036676b0..900347359645846dd1b501828e77c5e55cb4bfaf 100644 (file)
@@ -35,7 +35,7 @@ include::fetch-options.txt[]
 
 include::pull-fetch-param.txt[]
 
-include::urls.txt[]
+include::urls-remotes.txt[]
 
 SEE ALSO
 --------
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
new file mode 100644 (file)
index 0000000..915258f
--- /dev/null
@@ -0,0 +1,278 @@
+git-filter-branch(1)
+====================
+
+NAME
+----
+git-filter-branch - Rewrite branches
+
+SYNOPSIS
+--------
+[verse]
+'git-filter-branch' [--env-filter <command>] [--tree-filter <command>]
+       [--index-filter <command>] [--parent-filter <command>]
+       [--msg-filter <command>] [--commit-filter <command>]
+       [--tag-name-filter <command>] [--subdirectory-filter <directory>]
+       [-d <directory>] [-f | --force] [<rev-list options>...]
+
+DESCRIPTION
+-----------
+Lets you rewrite git revision history by creating a new branch from
+your current branch, applying custom filters on each revision.
+Those filters can modify each tree (e.g. removing a file or running
+a perl rewrite on all files) or information about each commit.
+Otherwise, all information (including original commit times or merge
+information) will be preserved.
+
+The command takes the new branch name as a mandatory argument and
+the filters as optional arguments.  If you specify no filters, the
+commits will be recommitted without any changes, which would normally
+have no effect.  Nevertheless, this may be useful in the future for
+compensating for some git bugs or such, therefore such a usage is
+permitted.
+
+*WARNING*! The rewritten history will have different object names for all
+the objects and will not converge with the original branch.  You will not
+be able to easily push and distribute the rewritten branch on top of the
+original branch.  Please do not use this command if you do not know the
+full implications, and avoid using it anyway, if a simple single commit
+would suffice to fix your problem.
+
+Always verify that the rewritten version is correct: The original refs,
+if different from the rewritten ones, will be stored in the namespace
+'refs/original/'.
+
+Note that since this operation is extensively I/O expensive, it might
+be a good idea to redirect the temporary directory off-disk, e.g. on
+tmpfs.  Reportedly the speedup is very noticeable.
+
+
+Filters
+~~~~~~~
+
+The filters are applied in the order as listed below.  The <command>
+argument is always evaluated in shell using the 'eval' command (with the
+notable exception of the commit filter, for technical reasons).
+Prior to that, the $GIT_COMMIT environment variable will be set to contain
+the id of the commit being rewritten.  Also, GIT_AUTHOR_NAME,
+GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL,
+and GIT_COMMITTER_DATE are set according to the current commit.
+
+A 'map' function is available that takes an "original sha1 id" argument
+and outputs a "rewritten sha1 id" if the commit has been already
+rewritten, and "original sha1 id" otherwise; the 'map' function can
+return several ids on separate lines if your commit filter emitted
+multiple commits.
+
+
+OPTIONS
+-------
+
+--env-filter <command>::
+       This is the filter for modifying the environment in which
+       the commit will be performed.  Specifically, you might want
+       to rewrite the author/committer name/email/time environment
+       variables (see gitlink:git-commit[1] for details).  Do not forget
+       to re-export the variables.
+
+--tree-filter <command>::
+       This is the filter for rewriting the tree and its contents.
+       The argument is evaluated in shell with the working
+       directory set to the root of the checked out tree.  The new tree
+       is then used as-is (new files are auto-added, disappeared files
+       are auto-removed - neither .gitignore files nor any other ignore
+       rules *HAVE ANY EFFECT*!).
+
+--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 gitlink:git-update-index[1].
+
+--parent-filter <command>::
+       This is the filter for rewriting the commit's parent list.
+       It will receive the parent string on stdin and shall output
+       the new parent string on stdout.  The parent string is in
+       a format accepted by gitlink:git-commit-tree[1]: empty for
+       the initial commit, "-p parent" for a normal commit and
+       "-p parent1 -p parent2 -p parent3 ..." for a merge commit.
+
+--msg-filter <command>::
+       This is the filter for rewriting the commit messages.
+       The argument is evaluated in the shell with the original
+       commit message on standard input; its standard output is
+       used as the new commit message.
+
+--commit-filter <command>::
+       This is the filter for performing the commit.
+       If this filter is specified, it will be called instead of the
+       gitlink:git-commit-tree[1] command, with arguments of the form
+       "<TREE_ID> [-p <PARENT_COMMIT_ID>]..." and the log message on
+       stdin.  The commit id is expected on stdout.
++
+As a special extension, the commit filter may emit multiple
+commit ids; in that case, ancestors of the original commit will
+have all of them as parents.
+
+--tag-name-filter <command>::
+       This is the filter for rewriting tag names. When passed,
+       it will be called for every tag ref that points to a rewritten
+       object (or to a tag object which points to a rewritten object).
+       The original tag name is passed via standard input, and the new
+       tag name is expected on standard output.
++
+The original tags are not deleted, but can be overwritten;
+use "--tag-name-filter=cat" to simply update the tags.  In this
+case, be very careful and make sure you have the old tags
+backed up in case the conversion has run afoul.
++
+Note that there is currently no support for proper rewriting of
+tag objects; in layman terms, if the tag has a message or signature
+attached, the rewritten tag won't have it.  Sorry.  (It is by
+definition impossible to preserve signatures at any rate.)
+
+--subdirectory-filter <directory>::
+       Only look at the history which touches the given subdirectory.
+       The result will contain that directory (and only that) as its
+       project root.
+
+-d <directory>::
+       Use this option to set the path to the temporary directory used for
+       rewriting.  When applying a tree filter, the command needs to
+       temporary checkout the tree to some directory, which may consume
+       considerable space in case of large projects.  By default it
+       does this in the '.git-rewrite/' directory but you can override
+       that choice by this parameter.
+
+-f\|--force::
+       `git filter-branch` refuses to start with an existing temporary
+       directory or when there are already refs starting with
+       'refs/original/', unless forced.
+
+<rev-list-options>::
+       When options are given after the new branch name, they will
+       be passed to gitlink:git-rev-list[1].  Only commits in the resulting
+       output will be filtered, although the filtered commits can still
+       reference parents which are outside of that set.
+
+
+Examples
+--------
+
+Suppose you want to remove a file (containing confidential information
+or copyright violation) from all commits:
+
+-------------------------------------------------------
+git filter-branch --tree-filter 'rm filename' HEAD
+-------------------------------------------------------
+
+A significantly faster version:
+
+--------------------------------------------------------------------------
+git filter-branch --index-filter 'git update-index --remove filename' HEAD
+--------------------------------------------------------------------------
+
+Now, you will get the rewritten history saved in the branch 'newbranch'
+(your current branch is left untouched).
+
+To set a commit (which typically is at the tip of another
+history) to be the parent of the current initial commit, in
+order to paste the other history behind the current history:
+
+-------------------------------------------------------------------
+git filter-branch --parent-filter 'sed "s/^\$/-p <graft-id>/"' HEAD
+-------------------------------------------------------------------
+
+(if the parent string is empty - therefore we are dealing with the
+initial commit - add graftcommit as a parent).  Note that this assumes
+history with a single root (that is, no merge without common ancestors
+happened).  If this is not the case, use:
+
+--------------------------------------------------------------------------
+git filter-branch --parent-filter \
+       'cat; test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>"' HEAD
+--------------------------------------------------------------------------
+
+or even simpler:
+
+-----------------------------------------------
+echo "$commit-id $graft-id" >> .git/info/grafts
+git filter-branch $graft-id..HEAD
+-----------------------------------------------
+
+To remove commits authored by "Darl McBribe" from the history:
+
+------------------------------------------------------------------------------
+git filter-branch --commit-filter '
+       if [ "$GIT_AUTHOR_NAME" = "Darl McBribe" ];
+       then
+               shift;
+               while [ -n "$1" ];
+               do
+                       shift;
+                       echo "$1";
+                       shift;
+               done;
+       else
+               git commit-tree "$@";
+       fi' HEAD
+------------------------------------------------------------------------------
+
+The shift magic first throws away the tree id and then the -p
+parameters.  Note that this handles merges properly! In case Darl
+committed a merge between P1 and P2, it will be propagated properly
+and all children of the merge will become merge commits with P1,P2
+as their parents instead of the merge commit.
+
+To restrict rewriting to only part of the history, specify a revision
+range in addition to the new branch name.  The new branch name will
+point to the top-most revision that a 'git rev-list' of this range
+will print.
+
+Note that the changes introduced by the commits, and not reverted by
+subsequent commits, will still be in the rewritten branch. If you want
+to throw out _changes_ together with the commits, you should use the
+interactive mode of gitlink:git-rebase[1].
+
+Consider this history:
+
+------------------
+     D--E--F--G--H
+    /     /
+A--B-----C
+------------------
+
+To rewrite only commits D,E,F,G,H, but leave A, B and C alone, use:
+
+--------------------------------
+git filter-branch ... C..H
+--------------------------------
+
+To rewrite commits E,F,G,H, use one of these:
+
+----------------------------------------
+git filter-branch ... C..H --not D
+git filter-branch ... D..H --not C
+----------------------------------------
+
+To move the whole tree into a subdirectory, or remove it from there:
+
+---------------------------------------------------------------
+git filter-branch --index-filter \
+       'git ls-files -s | sed "s-\t-&newsubdir/-" |
+               GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
+                       git update-index --index-info &&
+        mv $GIT_INDEX_FILE.new $GIT_INDEX_FILE' HEAD
+---------------------------------------------------------------
+
+
+Author
+------
+Written by Petr "Pasky" Baudis <pasky@suse.cz>,
+and the git list <git@vger.kernel.org>
+
+Documentation
+--------------
+Documentation by Petr Baudis and the git list.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 4913c2552f9a092dbd982b55c7e7cb2ffc986570..6affc5bb4d1730805e1704af1045bb71ca896dbb 100644 (file)
@@ -60,4 +60,3 @@ Documentation by Petr Baudis, Junio C Hamano and the git-list <git@vger.kernel.o
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 0ca58a7eb36fb4b29ea485a098a12660360a2252..6cbcf937bcd3128b27fbce673d8500590a3f66ce 100644 (file)
@@ -11,7 +11,8 @@ SYNOPSIS
 [verse]
 'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--thread]
                    [--attach[=<boundary>] | --inline[=<boundary>]]
-                   [-s | --signoff] [<common diff options>] [--start-number <n>]
+                   [-s | --signoff] [<common diff options>]
+                   [--start-number <n>] [--numbered-files]
                    [--in-reply-to=Message-Id] [--suffix=.<sfx>]
                    [--ignore-if-in-upstream]
                    [--subject-prefix=Subject-Prefix]
@@ -30,9 +31,11 @@ gitlink:git-rev-parse[1].
 The output of this command is convenient for e-mail submission or
 for use with gitlink:git-am[1].
 
-Each output file is numbered sequentially from 1, and uses the
+By default, each output file is numbered sequentially from 1, and uses the
 first line of the commit message (massaged for pathname safety) as
-the filename. The names of the output files are printed to standard
+the filename. With the --numbered-files option, the output file names
+will only be numbers, without the first line of the commit appended.
+The names of the output files are printed to standard
 output, unless the --stdout option is specified.
 
 If -o is specified, output files are created in <dir>.  Otherwise
@@ -63,6 +66,11 @@ include::diff-options.txt[]
 --start-number <n>::
        Start numbering the patches at <n> instead of 1.
 
+--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::
        Do not strip/add '[PATCH]' from the first line of the
        commit log message.
@@ -121,12 +129,13 @@ not add any suffix.
 CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each
-message in the repository configuration.  Also you can specify
-the default suffix different from the built-in one:
+message in the repository configuration.  You can also specify
+new defaults for the subject prefix and file suffix.
 
 ------------
 [format]
         headers = "Organization: git-foo\n"
+        subjectprefix = CHANGE
         suffix = .txt
 ------------
 
@@ -173,4 +182,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 8c68cf037259b3abc7ea16952d232b2fb2f07a25..45c0bee50a15e21516a7b288b546b3a40c48028e 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-fsck' [--tags] [--root] [--unreachable] [--cache] [--no-reflogs]
-                [--full] [--strict] [<object>*]
+        [--full] [--strict] [--verbose] [--lost-found] [<object>*]
 
 DESCRIPTION
 -----------
@@ -61,6 +61,15 @@ index file and all SHA1 references in .git/refs/* as heads.
        objects that triggers this check, but it is recommended
        to check new projects with this flag.
 
+--verbose::
+       Be chatty.
+
+--lost-found::
+       Write dangling objects into .git/lost-found/commit/ or
+       .git/lost-found/other/, depending on type.  If the object is
+       a blob, the contents are written into the file, rather than
+       its object name.
+
 It tests SHA1 and general object sanity, and it does full tracking of
 the resulting reachability and everything else. It prints out any
 corruption it finds (missing or bad objects), and if you use the
@@ -142,4 +151,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index bc1658434a61bdba41699859f103a3c8e0802d95..c7742ca9630b13d1eeef16d175f8ca840ddff4b0 100644 (file)
@@ -8,7 +8,7 @@ git-gc - Cleanup unnecessary files and optimize the local repository
 
 SYNOPSIS
 --------
-'git-gc' [--prune]
+'git-gc' [--prune] [--aggressive]
 
 DESCRIPTION
 -----------
@@ -35,6 +35,13 @@ OPTIONS
        repository at the same time (e.g. never use this option
        in a cron script).
 
+--aggressive::
+       Usually 'git-gc' runs very quickly while providing good disk
+       space utilization and performance.  This option will cause
+       git-gc to more aggressively optimize the repository at the expense
+       of taking much more time.  The effects of this optimization are
+       persistent, so this option only needs to be used occasionally; every
+       few hundred changesets or so.
 
 Configuration
 -------------
@@ -67,6 +74,13 @@ The optional configuration variable 'gc.packrefs' determines if
 is not run in bare repositories by default, to allow older dumb-transport
 clients fetch from the repository,  but this will change in the future.
 
+The optional configuration variable 'gc.aggressiveWindow' controls how
+much time is spent optimizing the delta compression of the objects in
+the repository when the --aggressive option is specified.  The larger
+the value, the more time is spent optimizing the delta compression.  See
+the documentation for the --window' option in gitlink:git-repack[1] for
+more details.  This defaults to 10.
+
 See Also
 --------
 gitlink:git-prune[1]
index 48805b651ca8161a87448f307b45c9e9d4df3c0f..9b5f86fc30aecc5e1183e514d5d0f81d14a751c3 100644 (file)
@@ -34,4 +34,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index c5a5dad1ce27dde6c3846beaf7ab2f7aa8ed81c8..97faaa1d3a9d8c7025ab33c3126ba10e4c5ed708 100644 (file)
@@ -144,4 +144,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-gui.txt b/Documentation/git-gui.txt
new file mode 100644 (file)
index 0000000..bd613b2
--- /dev/null
@@ -0,0 +1,115 @@
+git-gui(1)
+==========
+
+NAME
+----
+git-gui - A portable graphical interface to Git
+
+SYNOPSIS
+--------
+'git gui' [<command>] [arguments]
+
+DESCRIPTION
+-----------
+A Tcl/Tk based graphical user interface to Git.  git-gui focuses
+on allowing users to make changes to their repository by making
+new commits, amending existing ones, creating branches, performing
+local merges, and fetching/pushing to remote repositories.
+
+Unlike gitlink:gitk[1], git-gui focuses on commit generation
+and single file annotation, and does not show project history.
+It does however supply menu actions to start a gitk session from
+within git-gui.
+
+git-gui is known to work on all popular UNIX systems, Mac OS X,
+and Windows (under both Cygwin and MSYS).  To the extent possible
+OS specific user interface guidelines are followed, making git-gui
+a fairly native interface for users.
+
+COMMANDS
+--------
+blame::
+       Start a blame viewer on the specified file on the given
+       version (or working directory if not specified).
+
+browser::
+       Start a tree browser showing all files in the specified
+       commit (or 'HEAD' by default).  Files selected through the
+       browser are opened in the blame viewer.
+
+citool::
+       Start git-gui and arrange to make exactly one commit before
+       exiting and returning to the shell.  The interface is limited
+       to only commit actions, slightly reducing the application's
+       startup time and simplifying the menubar.
+
+version::
+       Display the currently running version of git-gui.
+
+
+Examples
+--------
+git gui blame Makefile::
+
+       Show the contents of the file 'Makefile' in the current
+       working directory, and provide annotations for both the
+       original author of each line, and who moved the line to its
+       current location.  The uncommitted file is annotated, and
+       uncommitted changes (if any) are explicitly attributed to
+       'Not Yet Committed'.
+
+git gui blame v0.99.8 Makefile::
+
+       Show the contents of 'Makefile' in revision 'v0.99.8'
+       and provide annotations for each line.  Unlike the above
+       example the file is read from the object database and not
+       the working directory.
+
+git gui citool::
+
+       Make one commit and return to the shell when it is complete.
+
+git citool::
+
+       Same as 'git gui citool' (above).
+
+git gui browser maint::
+
+       Show a browser for the tree of the 'maint' branch.  Files
+       selected in the browser can be viewed with the internal
+       blame viewer.
+
+See Also
+--------
+'gitk(1)'::
+       The git repository browser.  Shows branches, commit history
+       and file differences.  gitk is the utility started by
+       git-gui's Repository Visualize actions.
+
+Other
+-----
+git-gui is actually maintained as an independent project, but stable
+versions are distributed as part of the Git suite for the convience
+of end users.
+
+A git-gui development repository can be obtained from:
+
+  git clone git://repo.or.cz/git-gui.git
+
+or
+
+  git clone http://repo.or.cz/r/git-gui.git
+
+or browsed online at http://repo.or.cz/w/git-gui.git/[].
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>.
+
+Documentation
+--------------
+Documentation by Shawn O. Pearce <spearce@spearce.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 5edc36f060f7cf6444dd016cbb7e073af923f5db..616f196d81ca54595dc67f4846bace5880b9b90d 100644 (file)
@@ -18,7 +18,7 @@ work tree), and optionally writes the resulting object into the
 object database.  Reports its object ID to its standard output.
 This is used by "git-cvsimport" to update the index
 without modifying files in the work tree.  When <type> is not
-specified, it defaults to "blob". 
+specified, it defaults to "blob".
 
 OPTIONS
 -------
@@ -43,4 +43,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 4deabc376c6698408bee032409434bd7e32740a4..45e48453a17d70c559cbc65477cad6610abc5928 100644 (file)
@@ -54,4 +54,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index a15cf5b2a30a6ed2c207622c767d131346936a06..9afb860381369767a0a3f5295f588b75f559ff22 100644 (file)
@@ -52,7 +52,7 @@ Specifying the Refs
 
 A '<ref>' specification can be either a single pattern, or a pair
 of such patterns separated by a colon ":" (this means that a ref name
-cannot have a colon in it).  A single pattern '<name>' is just a 
+cannot have a colon in it).  A single pattern '<name>' is just a
 shorthand for '<name>:<name>'.
 
 Each pattern pair consists of the source side (before the colon)
index 226926964e5f3e6620f20b23b74c0299a168d07f..a8a7f6f04bf5b95a5b325dc2df0adf9d94532bc5 100644 (file)
@@ -98,4 +98,3 @@ Documentation by Sergey Vlasov
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 5412135d763af3a8c68e18441b774654f07eadfd..d4e01cb325150b72a30bde67ba55461d8062fb83 100644 (file)
@@ -8,7 +8,7 @@ git-init-db - Creates an empty git repository
 
 SYNOPSIS
 --------
-'git-init-db' [--template=<template_directory>] [--shared[=<permissions>]]
+'git-init-db' [-q | --quiet] [--template=<template_directory>] [--shared[=<permissions>]]
 
 
 DESCRIPTION
@@ -16,4 +16,3 @@ DESCRIPTION
 
 This is a synonym for gitlink:git-init[1].  Please refer to the
 documentation of that command.
-
index 1b64d3ab03c2e13807e24833823193021559d947..07484a4fd065b639bbf33832cbf642eb8399757e 100644 (file)
@@ -8,7 +8,7 @@ git-init - Create an empty git repository or reinitialize an existing one
 
 SYNOPSIS
 --------
-'git-init' [--template=<template_directory>] [--shared[=<permissions>]]
+'git-init' [-q | --quiet] [--template=<template_directory>] [--shared[=<permissions>]]
 
 
 OPTIONS
@@ -16,6 +16,10 @@ OPTIONS
 
 --
 
+-q, \--quiet::
+
+Only print error and warning messages, all other output will be suppressed.
+
 --template=<template_directory>::
 
 Provide the directory from which templates will be used.  The default template
@@ -108,4 +112,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 9df0ab2d76f6f024a138a84a7fd3c442342c1b88..cec60ee78075aa4411cd637aece93fc38080b0c5 100644 (file)
@@ -82,4 +82,3 @@ Documentation by Eric Wong <normalperson@yhbt.net>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 51389ef37da443c981f3bb5d46f819e90c3d2980..141b76768c61c42e6d876ddb840897cb8805dc96 100644 (file)
@@ -14,6 +14,8 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+THIS COMMAND IS DEPRECATED.
+
 Duplicates another git repository on a local system.
 
 OPTIONS
@@ -62,4 +64,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 0f353f6558e5843b1534f4cb9acd75746c313510..5a90f65b5e9d9ce21bcd0c936a01ded5065d5868 100644 (file)
@@ -52,7 +52,24 @@ include::pretty-options.txt[]
        See also gitlink:git-reflog[1].
 
 --decorate::
-    Print out the ref names of any commits that are shown.
+       Print out the ref names of any commits that are shown.
+
+--full-diff::
+       Without this flag, "git log -p <paths>..." shows commits that
+       touch the specified paths, and diffs about the same specified
+       paths.  With this, the full diff is shown for commits that touch
+       the specified paths; this means that "<paths>..." limits only
+       commits, and doesn't limit diff for those commits.
+
+--follow::
+       Continue listing the history of a file beyond renames.
+
+--log-size::
+       Before the log message print out its size in bytes. Intended
+       mainly for porcelain tools consumption. If git is unable to
+       produce a valid value size is set to zero.
+       Note that only message is considered, if also a diff is shown
+       its size is not included.
 
 <paths>...::
        Show only commits that affect the specified paths.
@@ -84,6 +101,12 @@ git log -r --name-status release..test::
        in the "release" branch, along with the list of paths
        each commit modifies.
 
+git log --follow builtin-rev-list.c::
+
+       Shows the commits that changed builtin-rev-list.c, including
+       those commits that occurred before the file was given its
+       present name.
+
 Discussion
 ----------
 
@@ -101,4 +124,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index a78a9ff1b85d1c8cf003ddab63b8412cf3083657..997594549fbf5a1e50a7d187c3cebcbe93461455 100644 (file)
@@ -180,4 +180,3 @@ Documentation by David Greaves, Junio C Hamano, Josh Triplett, and the git-list
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index c254005ca39e6872b8ec7b729246d18757df31d1..93e9a60330797070fc50c0df6c0767b7b81f79c0 100644 (file)
@@ -70,4 +70,3 @@ Written by Junio C Hamano <junkio@cox.net>
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 78993940811c96752e87f7b9e7c1381066056950..7b7859967326acaff157eab01da16b83c1342fcc 100644 (file)
@@ -9,7 +9,7 @@ git-ls-tree - List the contents of a tree object
 SYNOPSIS
 --------
 [verse]
-'git-ls-tree' [-d] [-r] [-t] [-z]
+'git-ls-tree' [-d] [-r] [-t] [-l] [-z]
            [--name-only] [--name-status] [--full-name] [--abbrev=[<n>]]
            <tree-ish> [paths...]
 
@@ -36,6 +36,10 @@ OPTIONS
        Show tree entries even when going to recurse them. Has no effect
        if '-r' was not passed. '-d' implies '-t'.
 
+-l::
+--long::
+       Show object size of blob (file) entries.
+
 -z::
        \0 line termination on output.
 
@@ -65,6 +69,14 @@ Output Format
 When the `-z` option is not used, TAB, LF, and backslash characters
 in pathnames are represented as `\t`, `\n`, and `\\`, respectively.
 
+When the `-l` option is used, format changes to
+
+        <mode> SP <type> SP <object> SP <object size> TAB <file>
+
+Object size identified by <object> is given in bytes, and right-justified
+with minimum width of 7 characters.  Object size is given only for blobs
+(file) entries; for other entries `-` character is used in place of size.
+
 
 Author
 ------
@@ -80,4 +92,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 8eadcebfcf5c26c599cde0d939d1046de69f2335..64aa6a1ea6bca248e7078efcecb3d582cf8a0f5f 100644 (file)
@@ -16,7 +16,7 @@ DESCRIPTION
 Reading a single e-mail message from the standard input, and
 writes the commit log message in <msg> file, and the patches in
 <patch> file.  The author name, e-mail and e-mail subject are
-written out to the standard output to be used by git-applypatch
+written out to the standard output to be used by git-am
 to create a commit.  It is usually not necessary to use this
 command directly.  See gitlink:git-am[1] instead.
 
@@ -67,4 +67,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index c11d6a530f67857bb28a5b3cee48bc5b01f5cb92..c4f4cabbdcdaae18491c301d44fd68ae89b18352 100644 (file)
@@ -7,12 +7,15 @@ git-mailsplit - Simple UNIX mbox splitter program
 
 SYNOPSIS
 --------
-'git-mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>...]
+'git-mailsplit' [-b] [-f<nn>] [-d<prec>] -o<directory> [--] [<mbox>|<Maildir>...]
 
 DESCRIPTION
 -----------
-Splits a mbox file into a list of files: "0001" "0002" ..  in the specified
-directory so you can process them further from there.
+Splits a mbox file or a Maildir into a list of files: "0001" "0002" ..  in the
+specified directory so you can process them further from there.
+
+IMPORTANT: Maildir splitting relies upon filenames being sorted to output
+patches in the correct order.
 
 OPTIONS
 -------
@@ -20,6 +23,10 @@ OPTIONS
        Mbox file to split.  If not given, the mbox is read from
        the standard input.
 
+<Maildir>::
+       Root of the Maildir to split. This directory should contain the cur, tmp
+       and new subdirectories.
+
 <directory>::
        Directory in which to place the individual messages.
 
@@ -49,4 +56,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 3190aed10885b5f3471dfc30922c141835d6c4f1..6b71880ec45455b1e62c3e1582d4cf78f2ed4852 100644 (file)
@@ -40,4 +40,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index b8ee1ff2b0681ea9fbc83618b1d09d95f3a902ff..17e9f10c659844e55e56b0d3a005e5f250f43c20 100644 (file)
@@ -59,7 +59,7 @@ Examples:
   This is modified MM in the branch B.                 # merge2
   This is modified MM in the branch B.                 # current contents
 
-or 
+or
 
   torvalds@ppc970:~/merge-test> git-merge-index cat AA MM
   cat: : No such file or directory
@@ -85,4 +85,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index f80ab3b8c479d787e116298f5446d1ff57ff2774..f35d0e1b4560a4970e7423011b585e1ff7d538c1 100644 (file)
@@ -27,4 +27,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 35fb4fb713bac225c665b6d7701efc9f105b9c16..6892fdac3df054e87019318c524185109ea16200 100644 (file)
@@ -34,4 +34,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 9c08efa53abc2797a7b7ca0e589d83b9a4387987..144bc16ff26bbf1d311482e6be15fc034994ce49 100644 (file)
@@ -9,7 +9,7 @@ git-merge - Join two or more development histories together
 SYNOPSIS
 --------
 [verse]
-'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]...
+'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]...
        [-m <msg>] <remote> <remote>...
 
 DESCRIPTION
@@ -43,6 +43,21 @@ If you tried a merge which resulted in a complex conflicts and
 would want to start over, you can recover with
 gitlink:git-reset[1].
 
+CONFIGURATION
+-------------
+
+merge.summary::
+       Whether to include summaries of merged commits in newly
+       created merge commit. False by default.
+
+merge.verbosity::
+       Controls the amount of output shown by the recursive merge
+       strategy.  Level 0 outputs nothing except a final error
+       message if conflicts were detected. Level 1 outputs only
+       conflicts, 2 outputs conflicts and file changes.  Level 5 and
+       above outputs debugging information.  The default is level 2.
+       Can be overriden by 'GIT_MERGE_VERBOSITY' environment variable.
+
 
 HOW MERGE WORKS
 ---------------
@@ -92,11 +107,11 @@ pull after you are done and ready.
 
 When things cleanly merge, these things happen:
 
-1. the results are updated both in the index file and in your
-   working tree,
-2. index file is written out as a tree,
-3. the tree gets committed, and 
-4. the `HEAD` pointer gets advanced.
+1. The results are updated both in the index file and in your
+   working tree;
+2. Index file is written out as a tree;
+3. The tree gets committed; and
+4. The `HEAD` pointer gets advanced.
 
 Because of 2., we require that the original state of the index
 file to match exactly the current `HEAD` commit; otherwise we
index add01e855a09c34b6aa9c83af6efcd17f24f3c9d..6c32c6d18ead2047ce590e2853bbc1a5a2dd1e7c 100644 (file)
@@ -25,7 +25,7 @@ OPTIONS
 -t or --tool=<tool>::
        Use the merge resolution program specified by <tool>.
        Valid merge tools are:
-       kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, and opendiff
+       kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, and opendiff
 +
 If a merge resolution program is not specified, 'git mergetool'
 will use the configuration variable merge.tool.  If the
@@ -43,4 +43,3 @@ Documentation by Theodore Y Ts'o.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 2860a3d1ba54c712bb30febfa37693f3efbdb7bf..ea7a75234a91b0dade03b8e19d4ba7d0d1a82f63 100644 (file)
@@ -19,18 +19,18 @@ The output is the new tag's <object> identifier.
 
 Tag Format
 ----------
-A tag signature file has a very simple fixed format: three lines of
+A tag signature file has a very simple fixed format: four lines of
 
   object <sha1>
   type <typename>
   tag <tagname>
+  tagger <tagger>
 
-followed by some 'optional' free-form signature that git itself
-doesn't care about, but that can be verified with gpg or similar.
-
-The size of the full object is artificially limited to 8kB.  (Just
-because I'm a lazy bastard, and if you can't fit a signature in that
-size, you're doing something wrong)
+followed by some 'optional' free-form message (some tags created
+by older git may not have `tagger` line).  The message, when
+exists, is separated by a blank line from the header.  The
+message part may contain a signature that git itself doesn't
+care about, but that can be verified with gpg.
 
 
 Author
@@ -44,4 +44,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 5f9ee603b758ea251a438399a642fa60d364aca2..638abc7d0f7425749339b1519cab5c0586e96138 100644 (file)
@@ -32,4 +32,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 6756b76bb11bc17279c3f20c16d7b4ba131f5832..2c9cf743c7a097ab955938d023e347e866fbc13e 100644 (file)
@@ -51,4 +51,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index d6c8bf800f4456ea63ff7d9b0c01b2d4f0d9d179..91eede120e5e192ac8ce3a483dd6a88dce414333 100644 (file)
@@ -34,6 +34,13 @@ OPTIONS
        Read from stdin, append "(<rev_name>)" to all sha1's of nameable
        commits, and pass to stdout
 
+--name-only::
+       Instead of printing both the SHA-1 and the name, print only
+       the name.  If given with --tags the usual tag prefix of
+       "tags/" is also ommitted from the name, matching the output
+       of gitlink::git-describe[1] more closely.  This option
+       cannot be combined with --stdin.
+
 EXAMPLE
 -------
 
@@ -69,4 +76,3 @@ Documentation by Johannes Schindelin.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-p4import.txt b/Documentation/git-p4import.txt
deleted file mode 100644 (file)
index 714abbe..0000000
+++ /dev/null
@@ -1,168 +0,0 @@
-git-p4import(1)
-===============
-
-NAME
-----
-git-p4import - Import a Perforce repository into git
-
-
-SYNOPSIS
---------
-[verse]
-`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
-               <//p4repo/path> <branch>
-`git-p4import` --stitch <//p4repo/path>
-`git-p4import`
-
-
-DESCRIPTION
------------
-Import a Perforce repository into an existing git repository.  When
-a <//p4repo/path> and <branch> are specified a new branch with the
-given name will be created and the initial import will begin.
-
-Once the initial import is complete you can do an incremental import
-of new commits from the Perforce repository.  You do this by checking
-out the appropriate git branch and then running `git-p4import` without
-any options.
-
-The standard p4 client is used to communicate with the Perforce
-repository; it must be configured correctly in order for `git-p4import`
-to operate (see below).
-
-
-OPTIONS
--------
--q::
-       Do not display any progress information.
-
--v::
-        Give extra progress information.
-
-\--authors::
-       Specify an authors file containing a mapping of Perforce user
-       ids to full names and email addresses (see Notes below).
-
-\--notags::
-       Do not create a tag for each imported commit.
-
-\--stitch::
-       Import the contents of the given perforce branch into the
-       currently checked out git branch.
-
-\--log::
-       Store debugging information in the specified file.
-
--t::
-       Specify that the remote repository is in the specified timezone.
-       Timezone must be in the format "US/Pacific" or "Europe/London"
-       etc.  You only need to specify this once, it will be saved in
-       the git config file for the repository.
-
-<//p4repo/path>::
-       The Perforce path that will be imported into the specified branch.
-
-<branch>::
-       The new branch that will be created to hold the Perforce imports.
-
-
-P4 Client
----------
-You must make the `p4` client command available in your $PATH and
-configure it to communicate with the target Perforce repository.
-Typically this means you must set the "$P4PORT" and "$P4CLIENT"
-environment variables.
-
-You must also configure a `p4` client "view" which maps the Perforce
-branch into the top level of your git repository, for example:
-
-------------
-Client: myhost
-
-Root:   /home/sean/import
-
-Options:   noallwrite clobber nocompress unlocked modtime rmdir
-
-View:
-        //public/jam/... //myhost/jam/...
-------------
-
-With the above `p4` client setup, you could import the "jam"
-perforce branch into a branch named "jammy", like so:
-
-------------
-$ mkdir -p /home/sean/import/jam
-$ cd /home/sean/import/jam
-$ git init
-$ git p4import //public/jam jammy
-------------
-
-
-Multiple Branches
------------------
-Note that by creating multiple "views" you can use `git-p4import`
-to import additional branches into the same git repository.
-However, the `p4` client has a limitation in that it silently
-ignores all but the last "view" that maps into the same local
-directory.  So the following will *not* work:
-
-------------
-View:
-        //public/jam/... //myhost/jam/...
-        //public/other/... //myhost/jam/...
-        //public/guest/... //myhost/jam/...
-------------
-
-If you want more than one Perforce branch to be imported into the
-same directory you must employ a workaround.  A simple option is
-to adjust your `p4` client before each import to only include a
-single view.
-
-Another option is to create multiple symlinks locally which all
-point to the same directory in your git repository and then use
-one per "view" instead of listing the actual directory.
-
-
-Tags
-----
-A git tag of the form p4/xx is created for every change imported from
-the Perforce repository where xx is the Perforce changeset number.
-Therefore after the import you can use git to access any commit by its
-Perforce number, e.g. git show p4/327.
-
-The tag associated with the HEAD commit is also how `git-p4import`
-determines if there are new changes to incrementally import from the
-Perforce repository.
-
-If you import from a repository with many thousands of changes
-you will have an equal number of p4/xxxx git tags.  Git tags can
-be expensive in terms of disk space and repository operations.
-If you don't need to perform further incremental imports, you
-may delete the tags.
-
-
-Notes
------
-You can interrupt the import (e.g. ctrl-c) at any time and restart it
-without worry.
-
-Author information is automatically determined by querying the
-Perforce "users" table using the id associated with each change.
-However, if you want to manually supply these mappings you can do
-so with the "--authors" option.  It accepts a file containing a list
-of mappings with each line containing one mapping in the format:
-
-------------
-    perforce_id = Full Name <email@address.com>
-------------
-
-
-Author
-------
-Written by Sean Estabrooks <seanlkml@sympatico.ca>
-
-
-GIT
----
-Part of the gitlink:git[7] suite
-
index bd3ee456e336add5d880d6a1192646220e7c34d7..6f17cff24ab784ee68767724dff6892b67b3ad53 100644 (file)
@@ -85,6 +85,22 @@ base-name::
        times to get to the necessary object.
        The default value for --window is 10 and --depth is 50.
 
+--window-memory=[N]::
+       This option provides an additional limit on top of `--window`;
+       the window size will dynamically scale down so as to not take
+       up more than N bytes in memory.  This is useful in
+       repositories with a mix of large and small objects to not run
+       out of memory with a large window, but still be able to take
+       advantage of the large window for the smaller objects.  The
+       size can be suffixed with "k", "m", or "g".
+       `--window-memory=0` makes memory usage unlimited, which is the
+       default.
+
+--max-pack-size=<n>::
+       Maximum size of each output packfile, expressed in MiB.
+       If specified,  multiple packfiles may be created.
+       The default is unlimited.
+
 --incremental::
        This flag causes an object already in a pack ignored
        even if it appears in the standard input.
@@ -127,6 +143,25 @@ base-name::
        This flag tells the command not to reuse existing deltas
        but compute them from scratch.
 
+--no-reuse-object::
+       This flag tells the command not to reuse existing object data at all,
+       including non deltified object, forcing recompression of everything.
+       This implies --no-reuse-delta. Useful only in the obscure case where
+       wholesale enforcement of a different compression level on the
+       packed data is desired.
+
+--compression=[N]::
+       Specifies compression level for newly-compressed data in the
+       generated pack.  If not specified,  pack compression level is
+       determined first by pack.compression,  then by core.compression,
+       and defaults to -1,  the zlib default,  if neither is set.
+       Data copied from loose objects will be recompressed
+       if core.legacyheaders was true when they were created or if
+       the loose compression level (see core.loosecompression and
+       core.compression) is now a different value than the pack
+       compression level.  Add --no-reuse-object if you want to force
+       a uniform compression level on all data no matter the source.
+
 --delta-base-offset::
        A packed archive can express base object of a delta as
        either 20-byte object name or as an offset in the
@@ -161,4 +196,3 @@ gitlink:git-prune-packed[1]
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 94bbea0db2caf32635cfa8fc199e8150b1cd05f2..f2ceebac4ba57a07d2d9bf2ae1e9f54d003b2d7f 100644 (file)
@@ -17,7 +17,7 @@ are redundant. The output is suitable for piping to
 'xargs rm' if you are in the root of the repository.
 
 git-pack-redundant accepts a list of objects on standard input. Any objects
-given will be ignored when checking which packs are required. This makes the 
+given will be ignored when checking which packs are required. This makes the
 following command useful when wanting to remove packs which contain unreachable
 objects.
 
@@ -55,4 +55,3 @@ gitlink:git-prune-packed[1]
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index a7e9fd021a4cc1e467b2df7dd7e06d79a5182c7a..ad528a922487be06207bdcc38a03fe55fd0f2a5d 100644 (file)
@@ -40,4 +40,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 74f37bd9040200c0dbc82c67b45540e873d4649e..abc171266a35299159308d0653bb0c659b8bdc77 100644 (file)
@@ -52,4 +52,3 @@ Documentation by Junio C Hamano.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 310033e4605d20b99c21a769e79b25b64abbb618..3800edb7bb7d9062dddb3393eaf1a80e5a44596d 100644 (file)
@@ -50,4 +50,3 @@ gitlink:git-repack[1]
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index b8166a210fe5f0a6c5ade480640146684d2517cb..0ace233d18383f53ca4d31baaa8e3c230ddb874b 100644 (file)
@@ -58,4 +58,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 94478ed94d70c597c8a024afe79b456a66d40a35..e1eb2c1d0037f06dbf2b5cc0bde84918157ee2bf 100644 (file)
@@ -29,7 +29,7 @@ include::fetch-options.txt[]
 
 include::pull-fetch-param.txt[]
 
-include::urls.txt[]
+include::urls-remotes.txt[]
 
 include::merge-strategies.txt[]
 
@@ -165,4 +165,3 @@ Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index e9ad10672a7f045f25937ca85caec548fcb4327f..0dd9caf86799e1648fde8895af918f6f4806d202 100644 (file)
@@ -53,9 +53,8 @@ side are updated.
 +
 `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`.
 +
-A parameter <ref> without a colon is equivalent to
-<ref>`:`<ref>, hence updates <ref> in the destination from <ref>
-in the source.
+A parameter <ref> without a colon pushes the <ref> from the source
+repository to the destination repository under the same name.
 +
 Pushing an empty <src> allows you to delete the <dst> ref from
 the remote repository.
@@ -80,7 +79,7 @@ the remote repository.
 
 -f, \--force::
        Usually, the command refuses to update a remote ref that is
-       not a descendant of the local ref used to overwrite it.
+       not an ancestor of the local ref used to overwrite it.
        This flag disables the check.  This can cause the
        remote repository to lose commits; use it with care.
 
@@ -96,7 +95,27 @@ the remote repository.
 -v::
        Run verbosely.
 
-include::urls.txt[]
+include::urls-remotes.txt[]
+
+
+Examples
+--------
+
+git push origin master::
+       Find a ref that matches `master` in the source repository
+       (most likely, it would find `refs/heads/master`), and update
+       the same ref (e.g. `refs/heads/master`) in `origin` repository
+       with it.
+
+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 master:satellite/master::
+       Find a ref that matches `master` in the source repository
+       (most likely, it would find `refs/heads/master`), and update
+       the ref that matches `satellite/master` (most likely, it would
+       be `refs/remotes/satellite/master`) in `origin` repository with it.
 
 Author
 ------
@@ -110,4 +129,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 296937a4167dc2e4c93cfcafca9fea14090cb108..1c3ef4c59362a1ccc8e44a54738c788f710201be 100644 (file)
@@ -58,4 +58,3 @@ Documentation by Eric Biederman <ebiederm@lnxi.com>
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index acb57447a864a54b3b531b7ef7403563fd20f886..74c5478ba18763de1703f0c8c08c20f1aac02ac0 100644 (file)
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
 
 SYNOPSIS
 --------
-'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--trivial] [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -50,6 +50,12 @@ OPTIONS
        trees that are not directly related to the current
        working tree status into a temporary index file.
 
+--trivial::
+       Restrict three-way merge by `git-read-tree` to happen
+       only if there is no file-level merging required, instead
+       of resolving merge for trivial cases and leaving
+       conflicting files unresolved in the index.
+
 --aggressive::
        Usually a three-way merge by `git-read-tree` resolves
        the merge for really trivial cases and leaves other
@@ -356,4 +362,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 753b275a0f79d88ef439a4c235452bd17f81ce2e..96907d48632be36d0d8c66f6a0b4d937b7a38cb6 100644 (file)
@@ -8,7 +8,8 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git-rebase' [-v] [--merge] [-C<n>] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-i | --interactive] [-v | --verbose] [--merge] [-C<n>]
+       [-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]
 'git-rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -208,6 +209,14 @@ OPTIONS
        context exist they all must match.  By default no context is
        ever ignored.
 
+-i, \--interactive::
+       Make a list of the commits which are about to be rebased.  Let the
+       user edit that list before rebasing.
+
+-p, \--preserve-merges::
+       Instead of ignoring merges, try to recreate them.  This option
+       only works in interactive mode.
+
 include::merge-strategies.txt[]
 
 NOTES
@@ -226,9 +235,100 @@ pre-rebase hook script for an example.
 You must be in the top directory of your project to start (or continue)
 a rebase.  Upon completion, <branch> will be the current branch.
 
-Author
+INTERACTIVE MODE
+----------------
+
+Rebasing interactively means that you have a chance to edit the commits
+which are rebased.  You can reorder the commits, and you can
+remove them (weeding out bad or otherwise unwanted patches).
+
+The interactive mode is meant for this type of workflow:
+
+1. have a wonderful idea
+2. hack on the code
+3. prepare a series for submission
+4. submit
+
+where point 2. consists of several instances of
+
+a. regular use
+ 1. finish something worthy of a commit
+ 2. commit
+b. independent fixup
+ 1. realize that something does not work
+ 2. fix that
+ 3. commit it
+
+Sometimes the thing fixed in b.2. cannot be amended to the not-quite
+perfect commit it fixes, because that commit is buried deeply in a
+patch series.  That is exactly what interactive rebase is for: use it
+after plenty of "a"s and "b"s, by rearranging and editing
+commits, and squashing multiple commits into one.
+
+Start it with the last commit you want to retain as-is:
+
+       git rebase -i <after-this-commit>
+
+An editor will be fired up with all the commits in your current branch
+(ignoring merge commits), which come after the given commit.  You can
+reorder the commits in this list to your heart's content, and you can
+remove them.  The list looks more or less like this:
+
+-------------------------------------------
+pick deadbee The oneline of this commit
+pick fa1afe1 The oneline of the next commit
+...
+-------------------------------------------
+
+The oneline descriptions are purely for your pleasure; `git-rebase` will
+not look at them but at the commit names ("deadbee" and "fa1afe1" in this
+example), so do not delete or edit the names.
+
+By replacing the command "pick" with the command "edit", you can tell
+`git-rebase` to stop after applying that commit, so that you can edit
+the files and/or the commit message, amend the commit, and continue
+rebasing.
+
+If you want to fold two or more commits into one, replace the command
+"pick" with "squash" for the second and subsequent commit.  If the
+commits had different authors, it will attribute the squashed commit to
+the author of the last commit.
+
+In both cases, or when a "pick" does not succeed (because of merge
+errors), the loop will stop to let you fix things, and you can continue
+the loop with `git rebase --continue`.
+
+For example, if you want to reorder the last 5 commits, such that what
+was HEAD~4 becomes the new HEAD. To achieve that, you would call
+`git-rebase` like this:
+
+----------------------
+$ git rebase -i HEAD~5
+----------------------
+
+And move the first patch to the end of the list.
+
+You might want to preserve merges, if you have a history like this:
+
+------------------
+           X
+            \
+         A---M---B
+        /
+---o---O---P---Q
+------------------
+
+Suppose you want to rebase the side branch starting at "A" to "Q". Make
+sure that the current HEAD is "B", and call
+
+-----------------------------
+$ git rebase -i -p --onto Q O
+-----------------------------
+
+Authors
 ------
-Written by Junio C Hamano <junkio@cox.net>
+Written by Junio C Hamano <junkio@cox.net> and
+Johannes E. Schindelin <johannes.schindelin@gmx.de>
 
 Documentation
 --------------
@@ -237,4 +337,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 6914aa59c3ae6b8174e865e3c46b68bef4d375ca..4ef18404722e045bbc0b010545b3a34cac2d6586 100644 (file)
@@ -48,8 +48,8 @@ standard input of the hook will be one line per ref to be updated:
 The refname value is relative to $GIT_DIR; e.g. for the master
 head this is "refs/heads/master".  The two sha1 values before
 each refname are the object names for the refname before and after
-the update.  Refs to be created will have sha1-old equal to 0{40},
-while refs to be deleted will have sha1-new equal to 0{40}, otherwise
+the update.  Refs to be created will have sha1-old equal to 0\{40},
+while refs to be deleted will have sha1-new equal to 0\{40}, otherwise
 sha1-old and sha1-new should be valid objects in the repository.
 
 This hook is called before any refname is updated and before any
@@ -71,7 +71,7 @@ The refname parameter is relative to $GIT_DIR; e.g. for the master
 head this is "refs/heads/master".  The two sha1 arguments are
 the object names for the refname before and after the update.
 Note that the hook is called before the refname is updated,
-so either sha1-old is 0{40} (meaning there is no such ref yet),
+so either sha1-old is 0\{40} (meaning there is no such ref yet),
 or it should match what is recorded in refname.
 
 The hook should exit with non-zero status if it wants to disallow
@@ -96,8 +96,8 @@ The refname value is relative to $GIT_DIR; e.g. for the master
 head this is "refs/heads/master".  The two sha1 values before
 each refname are the object names for the refname before and after
 the update.  Refs that were created will have sha1-old equal to
-0{40}, while refs that were deleted will have sha1-new equal to
-0{40}, otherwise sha1-old and sha1-new should be valid objects in
+0\{40}, while refs that were deleted will have sha1-new equal to
+0\{40}, otherwise sha1-old and sha1-new should be valid objects in
 the repository.
 
 Using this hook, it is easy to generate mails describing the updates
index 1e343bcdcdd4c1998b9887866507639bb31d030c..89bc9c51ea13630f6556b12637ce8b1cc483d2fd 100644 (file)
@@ -39,6 +39,19 @@ the current branch. It is basically an alias for 'git log -g --abbrev-commit
 OPTIONS
 -------
 
+--stale-fix::
+       This revamps the logic -- the definition of "broken commit"
+       becomes: a commit that is not reachable from any of the refs and
+       there is a missing object among the commit, tree, or blob
+       objects reachable from it that is not reachable from any of the
+       refs.
++
+This computation involves traversing all the reachable objects, i.e. it
+has the same cost as 'git prune'.  Fortunately, once this is run, we
+should not have to ever worry about missing objects, because the current
+prune and pack-objects know about reflogs and protect objects referred by
+them.
+
 --expire=<time>::
        Entries older than this time are pruned.  Without the
        option it is taken from configuration `gc.reflogExpire`,
@@ -65,4 +78,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index aca60120c8a671465bb83127bde080b94c0bb782..fe631bb3dd9096e831087511202e2771ebb85df4 100644 (file)
@@ -34,4 +34,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index b35c65ba3b7307d6dff600d9d4b4907596ecbe5a..61a6022ce8a0fc7aac8b1e9bd08587817ef0d69c 100644 (file)
@@ -135,4 +135,3 @@ Documentation by J. Bruce Fields and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index c57013b9537f68fafdb1c1d90491ff5d556ed326..5283ef84a9f4083b1c79316286c3278679a41699 100644 (file)
@@ -68,6 +68,22 @@ OPTIONS
        to be applied that many times to get to the necessary object.
        The default value for --window is 10 and --depth is 50.
 
+--window-memory=[N]::
+       This option provides an additional limit on top of `--window`;
+       the window size will dynamically scale down so as to not take
+       up more than N bytes in memory.  This is useful in
+       repositories with a mix of large and small objects to not run
+       out of memory with a large window, but still be able to take
+       advantage of the large window for the smaller objects.  The
+       size can be suffixed with "k", "m", or "g".
+       `--window-memory=0` makes memory usage unlimited, which is the
+       default.
+
+--max-pack-size=<n>::
+       Maximum size of each output packfile, expressed in MiB.
+       If specified,  multiple packfiles may be created.
+       The default is unlimited.
+
 
 Configuration
 -------------
@@ -99,4 +115,3 @@ gitlink:git-prune-packed[1]
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 478a5fd6b74ddbcbf279339a5c3ae5eefb039cb4..087eeb7cc22552b8903e54fc52f08eeb56fb1a0d 100644 (file)
@@ -37,4 +37,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 7ff9b05e680cabc6513f9d8a6aa80eb7eccda82d..c4d4263238e5b7c6dff705de5d8552a577fc35d6 100644 (file)
@@ -23,7 +23,7 @@ initial manual merge, and later by noticing the same automerge
 results and applying the previously recorded hand resolution.
 
 [NOTE]
-You need to create `$GIT_DIR/rr-cache` directory to enable this
+You need to set the config variable rerere.enabled to enable this
 command.
 
 
@@ -171,7 +171,7 @@ records it if it is a new conflict, or reuses the earlier hand
 resolve when it is not.  `git-commit` also invokes `git-rerere`
 when recording a merge result.  What this means is that you do
 not have to do anything special yourself (Note: you still have
-to create `$GIT_DIR/rr-cache` directory to enable this command).
+to set the config variable rerere.enabled to enable this command).
 
 In our example, when you did the test merge, the manual
 resolution is recorded, and it will be reused when you do the
index 19c5b9bbda60744723e453dceaa209c47a49cb60..15e3aca9a17cbcc651079968c76b571a9d144696 100644 (file)
@@ -63,7 +63,7 @@ $ git commit -a -c ORIG_HEAD  <3>
 <1> This is most often done when you remembered what you
 just committed is incomplete, or you misspelled your commit
 message, or both.  Leaves working tree as it was before "reset".
-<2> make corrections to working tree files.
+<2> Make corrections to working tree files.
 <3> "reset" copies the old head to .git/ORIG_HEAD; redo the
 commit by starting with its log message.  If you do not need to
 edit the message further, you can give -C option instead.
@@ -106,17 +106,17 @@ $ git reset                                <3>
 $ git pull git://info.example.com/ nitfol  <4>
 ------------
 +
-<1> you are happily working on something, and find the changes
+<1> You are happily working on something, and find the changes
 in these files are in good order.  You do not want to see them
 when you run "git diff", because you plan to work on other files
 and changes with these files are distracting.
-<2> somebody asks you to pull, and the changes sounds worthy of merging.
-<3> however, you already dirtied the index (i.e. your index does
+<2> Somebody asks you to pull, and the changes sounds worthy of merging.
+<3> However, you already dirtied the index (i.e. your index does
 not match the HEAD commit).  But you know the pull you are going
 to make does not affect frotz.c nor filfre.c, so you revert the
 index changes for these two files.  Your changes in working tree
 remain there.
-<4> then you can pull and merge, leaving frotz.c and filfre.c
+<4> Then you can pull and merge, leaving frotz.c and filfre.c
 changes still in the working tree.
 
 Undo a merge or pull::
@@ -133,15 +133,15 @@ Fast forward
 $ git reset --hard ORIG_HEAD       <4>
 ------------
 +
-<1> try to update from the upstream resulted in a lot of
+<1> Try to update from the upstream resulted in a lot of
 conflicts; you were not ready to spend a lot of time merging
 right now, so you decide to do that later.
 <2> "pull" has not made merge commit, so "git reset --hard"
 which is a synonym for "git reset --hard HEAD" clears the mess
 from the index file and the working tree.
-<3> merge a topic branch into the current branch, which resulted
+<3> Merge a topic branch into the current branch, which resulted
 in a fast forward.
-<4> but you decided that the topic branch is not ready for public
+<4> But you decided that the topic branch is not ready for public
 consumption yet.  "pull" or "merge" always leaves the original
 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,
index c3c2043d183e0e6c7292f5af7c28af57958c4dcd..1c1978140f68a5e21dec6ea8d6715b58666f8b7c 100644 (file)
@@ -16,16 +16,20 @@ SYNOPSIS
             [ \--sparse ]
             [ \--no-merges ]
             [ \--remove-empty ]
+            [ \--full-history ]
             [ \--not ]
             [ \--all ]
             [ \--stdin ]
             [ \--topo-order ]
             [ \--parents ]
+            [ \--timestamp ]
             [ \--left-right ]
             [ \--cherry-pick ]
             [ \--encoding[=<encoding>] ]
             [ \--(author|committer|grep)=<pattern> ]
-            [ \--date={local|relative|default} ]
+            [ \--regexp-ignore-case | \-i ]
+            [ \--extended-regexp | \-E ]
+            [ \--date={local|relative|default|iso|rfc|short} ]
             [ [\--objects | \--objects-edge] [ \--unpacked ] ]
             [ \--pretty | \--header ]
             [ \--bisect ]
@@ -33,6 +37,7 @@ SYNOPSIS
             [ \--merge ]
             [ \--reverse ]
             [ \--walk-reflogs ]
+            [ \--no-walk ] [ \--do-walk ]
             <commit>... [ \-- <paths>... ]
 
 DESCRIPTION
@@ -93,7 +98,7 @@ include::pretty-options.txt[]
 
        Synonym for `--date=relative`.
 
---date={relative,local,default}::
+--date={relative,local,default,iso,rfc}::
 
        Only takes effect for dates shown in human-readable format, such
        as when using "--pretty".
@@ -103,6 +108,13 @@ e.g. "2 hours ago".
 +
 `--date=local` shows timestamps in user's local timezone.
 +
+`--date=iso` (or `--date=iso8601`) shows timestamps in ISO 8601 format.
++
+`--date=rfc` (or `--date=rfc2822`) shows timestamps in RFC 2822
+format, often found in E-mail messages.
++
+`--date=short` shows only date but not time, in `YYYY-MM-DD` fomat.
++
 `--date=default` shows timestamps in the original timezone
 (either committer's or author's).
 
@@ -115,6 +127,9 @@ e.g. "2 hours ago".
 
        Print the parents of the commit.
 
+--timestamp::
+       Print the raw commit timestamp.
+
 --left-right::
 
        Mark which side of a symmetric diff a commit is reachable from.
@@ -214,10 +229,27 @@ limiting may be applied.
        Limit the commits output to ones with log message that
        matches the specified pattern (regular expression).
 
+-i, --regexp-ignore-case::
+
+       Match the regexp limiting patterns without regard to letters case.
+
+-E, --extended-regexp::
+
+       Consider the limiting patterns to be extended regular expressions
+       instead of the default basic regular expressions.
+
 --remove-empty::
 
        Stop when a given path disappears from the tree.
 
+--full-history::
+
+       Show also parts of history irrelevant to current state of a given
+       path. This turns off history simplification, which removed merges
+       which didn't change anything at all at some child. It will still actually
+       simplify away merges that didn't change anything at all into either
+       child.
+
 --no-merges::
 
        Do not print commits with more than one parent.
@@ -261,9 +293,9 @@ excluded from the output.
 +
 With '\--pretty' format other than oneline (for obvious reasons),
 this causes the output to have two extra lines of information
-taken from the reflog.  By default, 'commit@{Nth}' notation is
+taken from the reflog.  By default, 'commit@\{Nth}' notation is
 used in the output.  When the starting commit is specified as
-'commit@{now}', output also uses 'commit@{timestamp}' notation
+'commit@{now}', output also uses 'commit@\{timestamp}' notation
 instead.  Under '\--pretty=oneline', the commit message is
 prefixed with this information on the same line.
 
@@ -367,6 +399,14 @@ These options are mostly targeted for packing of git repositories.
        Only useful with '--objects'; print the object IDs that are not
        in packs.
 
+--no-walk::
+
+       Only show the given revs, but do not traverse their ancestors.
+
+--do-walk::
+
+       Overrides a previous --no-walk.
+
 
 include::pretty-formats.txt[]
 
index 7757abe62190a066b6ad8b57214b70ff3fa0f12a..4b4d229e60150a4f4e5c734a06e220ac263aa8f2 100644 (file)
@@ -89,6 +89,17 @@ OPTIONS
 --git-dir::
        Show `$GIT_DIR` if defined else show the path to the .git directory.
 
+--is-inside-git-dir::
+       When the current working directory is below the repository
+       directory print "true", otherwise "false".
+
+--is-inside-work-tree::
+       When the current working directory is inside the work tree of the
+       repository print "true", otherwise "false".
+
+--is-bare-repository::
+       When the repository is bare print "true", otherwise "false".
+
 --short, --short=number::
        Instead of outputting the full SHA1 values of object names try to
        abbreviate them to a shorter unique name. When no length is specified
@@ -213,7 +224,7 @@ left-to-right.
     G   H   I   J
      \ /     \ /
       D   E   F
-       \  |  / \
+       \  |  / \ 
         \ | /   |
          \|/    |
           B     C
@@ -286,4 +297,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 8081bbaffa8d3710b644c744b9f81d459938a20e..69db4984473fae7928797bcfb13baf1f0e39d851 100644 (file)
@@ -56,4 +56,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index a65f24a0f698eb801ac43bba5aa7dc746e96edc0..be61a821642ada0a0e8e3303aa81463c716fd970 100644 (file)
@@ -14,7 +14,8 @@ DESCRIPTION
 Remove files from the working tree and from the index.  The
 files have to be identical to the tip of the branch, and no
 updates to its contents must have been placed in the staging
-area (aka index).
+area (aka index).  When --cached is given, the staged content has to
+match either the tip of the branch *or* the file on disk.
 
 
 OPTIONS
@@ -95,4 +96,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 8bb52f46872194783148851ca2fe81dfb9d13bf7..dee5d0da9db0e8e88f7f2f237fcfe3ca7f5c28ec 100644 (file)
@@ -66,4 +66,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 7ae39fd5a20a869db8c2be101d7001c1c48ff198..d243ed1e3a3c4f3e94d01b076e7715d37a1cd204 100644 (file)
@@ -44,8 +44,8 @@ The --cc option must be repeated for each user you want on the cc list.
        value; if that is unspecified, default to --chain-reply-to.
 
 --compose::
-       Use $EDITOR to edit an introductory message for the
-       patch series.
+       Use $GIT_EDITOR, core.editor, $VISUAL, or $EDITOR to edit an
+       introductory message for the patch series.
 
 --from::
        Specify the sender of the emails.  This will default to
@@ -59,9 +59,11 @@ The --cc option must be repeated for each user you want on the cc list.
        Only necessary if --compose is also set.  If --compose
        is not set, this will be prompted for.
 
---no-signed-off-by-cc::
-       Do not add emails found in Signed-off-by: or Cc: lines to the
-       cc list.
+--signed-off-by-cc, --no-signed-off-by-cc::
+        If this is set, add emails found in Signed-off-by: or Cc: lines to the
+        cc list.
+        Default is the value of 'sendemail.signedoffbycc' configuration value;
+        if that is unspecified, default to --signed-off-by-cc.
 
 --quiet::
        Make git-send-email less verbose.  One line per email should be
@@ -78,13 +80,22 @@ The --cc option must be repeated for each user you want on the cc list.
        `localhost` otherwise.
 
 --subject::
-       Specify the initial subject of the email thread.
+       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.
 
---suppress-from::
-       Do not add the From: address to the cc: list, if it shows up in a From:
-       line.
+--suppress-from, --no-suppress-from::
+        If this is set, do not add the From: address to the cc: list, if it
+        shows up in a From: line.
+        Default is the value of 'sendemail.suppressfrom' configuration value;
+        if that is unspecified, default to --no-supress-from.
+
+--thread, --no-thread::
+       If this is set, the In-Reply-To header will be set on each email sent.
+       If disabled with "--no-thread", no emails will have the In-Reply-To
+       header set.
+       Default is the value of the 'sendemail.thread' configuration value;
+       if that is unspecified, default to --thread.
 
 --dry-run::
        Do everything except actually send the emails.
@@ -137,4 +148,3 @@ Documentation by Ryan Anderson
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 2b2abebd608be337db35a3de4b83d37d43061acb..1ea1faa1b57adecea4269fa6d0c2c4afdee9b3da 100644 (file)
@@ -69,4 +69,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 228b9f14f3530b9ec28e603a3ef2327c7039c440..48f2d57b7b48fa9c8fb0cbc5fcb547d6aed32cbb 100644 (file)
@@ -32,4 +32,3 @@ Documentation by Petr Baudis and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 15cc6f77c1e9b8a77c8396fab99cf9d9fda98ef3..2220ef6ea8d3bfa58f3c1279dfcf4f735c2dae0a 100644 (file)
@@ -56,4 +56,3 @@ Documentation by Junio C Hamano.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index be09b62bebb2b98fa8b0f39e47d29267126fa2a6..764d99356bcefb40a16d3c27bc41e881f87b396a 100644 (file)
@@ -32,4 +32,3 @@ Documentation by Junio C Hamano
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 34c5caf2d08324cc1595013cbdcd40d89d7a1968..a42e1211500a6a6ecdf1d9e1ddbf6712869ceb79 100644 (file)
@@ -84,4 +84,3 @@ This manual page is a stub. You can help the git documentation by expanding it.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 192b1f15a9ca75351623d32e4373d65c6501b465..8d3e2ffb2c907376a485888a3497c938427c27a1 100644 (file)
@@ -13,6 +13,8 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+THIS COMMAND IS DEPRECATED.
+
 Pulls from a remote repository over ssh connection, invoking
 git-ssh-upload on the other end. It functions identically to
 git-ssh-upload, aside from which end you run it on.
@@ -48,4 +50,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index a9b7e9f974c13252456309c7053537853dc7ea9b..5e2ca8dccfb291bc4c50df3b0a8dfe2f28d07b59 100644 (file)
@@ -12,6 +12,8 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+THIS COMMAND IS DEPRECATED.
+
 Pushes from a remote repository over ssh connection, invoking
 git-ssh-fetch on the other end. It functions identically to
 git-ssh-fetch, aside from which end you run it on.
@@ -44,4 +46,3 @@ Documentation by Daniel Barkalow
 GIT
 ---
 Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-stash.txt b/Documentation/git-stash.txt
new file mode 100644 (file)
index 0000000..05f40cf
--- /dev/null
@@ -0,0 +1,165 @@
+git-stash(1)
+============
+
+NAME
+----
+git-stash - Stash the changes in a dirty working directory away
+
+SYNOPSIS
+--------
+[verse]
+'git-stash' (list | show [<stash>] | apply [<stash>] | clear)
+'git-stash' [save] [message...]
+
+DESCRIPTION
+-----------
+
+Use 'git-stash' when you want to record the current state of the
+working directory and the index, but want to go back to a clean
+working directory.  The command saves your local modifications away
+and reverts the working directory to match the `HEAD` commit.
+
+The modifications stashed away by this command can be listed with
+`git-stash list`, inspected with `git-stash show`, and restored
+(potentially on top of a different commit) with `git-stash apply`.
+Calling git-stash without any arguments is equivalent to `git-stash
+save`.  A stash is by default listed as "WIP on 'branchname' ...", but
+you can give a more descriptive message on the command line when
+you create one.
+
+The latest stash you created is stored in `$GIT_DIR/refs/stash`; older
+stashes are found in the reflog of this reference and can be named using
+the usual reflog syntax (e.g. `stash@\{0}` is the most recently
+created stash, `stash@\{1}` is the one before it, `stash@\{2.hours.ago}`
+is also possible).
+
+OPTIONS
+-------
+
+save::
+
+       Save your local modifications to a new 'stash', and run `git-reset
+       --hard` to revert them.  This is the default action when no
+       subcommand is given.
+
+list::
+
+       List the stashes that you currently have.  Each 'stash' is listed
+       with its name (e.g. `stash@\{0}` is the latest stash, `stash@\{1}` is
+       the one before, etc.), the name of the branch that was current when the
+       stash was made, and a short description of the commit the stash was
+       based on.
++
+----------------------------------------------------------------
+stash@{0}: WIP on submit: 6ebd0e2... Update git-stash documentation
+stash@{1}: On master: 9cc0589... Add git-stash
+----------------------------------------------------------------
+
+show [<stash>]::
+
+       Show the changes recorded in the stash as a diff between the the
+       stashed state and its original parent. When no `<stash>` is given,
+       shows the latest one. By default, the command shows the diffstat, but
+       it will accept any format known to `git-diff` (e.g., `git-stash show
+       -p stash@\{1}` to view the second most recent stash in patch form).
+
+apply [<stash>]::
+
+       Restore the changes recorded in the stash on top of the current
+       working tree state.  When no `<stash>` is given, applies the latest
+       one.  The working directory must match the index.
++
+This operation can fail with conflicts; you need to resolve them
+by hand in the working tree.
+
+clear::
+       Remove all the stashed states. Note that those states will then
+       be subject to pruning, and may be difficult or impossible to recover.
+
+
+DISCUSSION
+----------
+
+A stash is represented as a commit whose tree records the state of the
+working directory, and its first parent is the commit at `HEAD` when
+the stash was created.  The tree of the second parent records the
+state of the index when the stash is made, and it is made a child of
+the `HEAD` commit.  The ancestry graph looks like this:
+
+            .----W
+           /    /
+     -----H----I
+
+where `H` is the `HEAD` commit, `I` is a commit that records the state
+of the index, and `W` is a commit that records the state of the working
+tree.
+
+
+EXAMPLES
+--------
+
+Pulling into a dirty tree::
+
+When you are in the middle of something, you learn that there are
+upstream changes that are possibly relevant to what you are
+doing.  When your local changes do not conflict with the changes in
+the upstream, a simple `git pull` will let you move forward.
++
+However, there are cases in which your local changes do conflict with
+the upstream changes, and `git pull` refuses to overwrite your
+changes.  In such a case, you can stash your changes away,
+perform a pull, and then unstash, like this:
++
+----------------------------------------------------------------
+$ git pull
+...
+file foobar not up to date, cannot merge.
+$ git stash
+$ git pull
+$ git stash apply
+----------------------------------------------------------------
+
+Interrupted workflow::
+
+When you are in the middle of something, your boss comes in and
+demands that you fix something immediately.  Traditionally, you would
+make a commit to a temporary branch to store your changes away, and
+return to your original branch to make the emergency fix, like this:
++
+----------------------------------------------------------------
+... hack hack hack ...
+$ git checkout -b my_wip
+$ git commit -a -m "WIP"
+$ git checkout master
+$ edit emergency fix
+$ git commit -a -m "Fix in a hurry"
+$ git checkout my_wip
+$ git reset --soft HEAD^
+... continue hacking ...
+----------------------------------------------------------------
++
+You can use `git-stash` to simplify the above, like this:
++
+----------------------------------------------------------------
+... hack hack hack ...
+$ git stash
+$ edit emergency fix
+$ git commit -a -m "Fix in a hurry"
+$ git stash apply
+... continue hacking ...
+----------------------------------------------------------------
+
+SEE ALSO
+--------
+gitlink:git-checkout[1],
+gitlink:git-commit[1],
+gitlink:git-reflog[1],
+gitlink:git-reset[1]
+
+AUTHOR
+------
+Written by Nanako Shiraishi <nanako3@bluebottle.com>
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 1fd1af102ae948ca008bbd35675d859340e46dbd..8fd0fc62368611bee25b4bbc3becef853937ff2c 100644 (file)
@@ -27,6 +27,13 @@ The command takes the same set of options as `git-commit`; it
 shows what would be committed if the same options are given to
 `git-commit`.
 
+If any paths have been touched in the working tree (that is,
+their modification times have changed) but their contents and
+permissions are identical to those in the index file, the command
+updates the index file. Running `git-status` can thus speed up
+subsequent operations such as `git-diff` if the working tree
+contains many paths that have been touched but not modified.
+
 
 OUTPUT
 ------
@@ -58,4 +65,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 3a03dd04106d45cc3c233230d1773874f3ca1cef..5212358306a684c006e8ca30c3f7e21fb3c97afb 100644 (file)
@@ -8,7 +8,7 @@ git-stripspace - Filter out empty lines
 
 SYNOPSIS
 --------
-'git-stripspace' < <stream>
+'git-stripspace' [-s | --strip-comments] < <stream>
 
 DESCRIPTION
 -----------
@@ -16,6 +16,9 @@ Remove multiple empty lines, and empty lines at beginning and end.
 
 OPTIONS
 -------
+-s\|--strip-comments::
+       In addition to empty lines, also strip lines starting with '#'.
+
 <stream>::
        Byte stream to act on.
 
@@ -30,4 +33,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt
new file mode 100644 (file)
index 0000000..2c48936
--- /dev/null
@@ -0,0 +1,77 @@
+git-submodule(1)
+================
+
+NAME
+----
+git-submodule - Initialize, update or inspect submodules
+
+
+SYNOPSIS
+--------
+[verse]
+'git-submodule' [--quiet] [-b branch] add <repository> [<path>]
+'git-submodule' [--quiet] [--cached] [status|init|update] [--] [<path>...]
+
+
+COMMANDS
+--------
+add::
+       Add the given repository as a submodule at the given path
+       to the changeset to be committed next.  In particular, the
+       repository is cloned at the specified path, added to the
+       changeset and registered in .gitmodules.   If no path is
+       specified, the path is deduced from the repository specification.
+
+status::
+       Show the status of the submodules. This will print the SHA-1 of the
+       currently checked out commit for each submodule, along with the
+       submodule path and the output of gitlink:git-describe[1] for the
+       SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not
+       initialized and `+` if the currently checked out submodule commit
+       does not match the SHA-1 found in the index of the containing
+       repository. This command is the default command for git-submodule.
+
+init::
+       Initialize the submodules, i.e. register in .git/config each submodule
+       name and url found in .gitmodules. The key used in .git/config is
+       `submodule.$name.url`. This command does not alter existing information
+       in .git/config.
+
+update::
+       Update the registered submodules, i.e. clone missing submodules and
+       checkout the commit specified in the index of the containing repository.
+       This will make the submodules HEAD be detached.
+
+
+OPTIONS
+-------
+-q, --quiet::
+       Only print error messages.
+
+-b, --branch::
+       Branch of repository to add as submodule.
+
+--cached::
+       Display the SHA-1 stored in the index, not the SHA-1 of the currently
+       checked out submodule commit. This option is only valid for the
+       status command.
+
+<path>::
+       Path to submodule(s). When specified this will restrict the command
+       to only operate on the submodules found at the specified paths.
+
+FILES
+-----
+When initializing submodules, a .gitmodules file in the top-level directory
+of the containing repository is used to find the url of each submodule.
+This file should be formatted in the same way as $GIR_DIR/config. The key
+to each submodule url is "submodule.$name.url".
+
+
+AUTHOR
+------
+Written by Lars Hjemli <hjemli@gmail.com>
+
+GIT
+---
+Part of the gitlink:git[7] suite
index c0d7d9597b8f569a1e996072bef667f517e74bd2..816340b9440cb5acfbfb02a346f25461ea1498aa 100644 (file)
@@ -66,9 +66,10 @@ COMMANDS
        to the names of remotes if trunk/branches/tags are
        specified.  The prefix does not automatically include a
        trailing slash, so be sure you include one in the
-       argument if that is what you want.  This is useful if
-       you wish to track multiple projects that share a common
-       repository.
+       argument if that is what you want.  If --branches/-b is
+       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.
 
 'fetch'::
        Fetch unfetched revisions from the Subversion remote we are
@@ -434,6 +435,26 @@ Tracking and contributing to an entire Subversion-managed project
 # of dcommit/rebase/show-ignore should be the same as above.
 ------------------------------------------------------------------------
 
+The initial 'git-svn clone' can be quite time-consuming
+(especially for large Subversion repositories). If multiple
+people (or one person with multiple machines) want to use
+git-svn to interact with the same Subversion repository, you can
+do the initial 'git-svn clone' to a repository on a server and
+have each person clone that repository with 'git clone':
+
+------------------------------------------------------------------------
+# Do the initial import on a server
+       ssh server "cd /pub && git-svn clone http://svn.foo.org/project
+# Clone locally
+       git clone server:/pub/project
+# Tell git-svn which branch contains the Subversion commits
+       git update-ref refs/remotes/git-svn origin/master
+# Initialize git-svn locally (be sure to use the same URL and -T/-b/-t options as were used on server)
+       git-svn init http://svn.foo.org/project
+# Pull the latest changes from Subversion
+       git-svn rebase
+------------------------------------------------------------------------
+
 REBASE VS. PULL/MERGE
 ---------------------
 
index bdae7d87dce5d07d0d31b45f127fa43a239a6632..e97d15e8f237e969b76cdda9a90f6740aab77f13 100644 (file)
@@ -174,4 +174,3 @@ Documentation by Matthias Urlichs <smurf@smurf.noris.de>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 4e3e02756c8863dbc6fe1d745e1a8c9a97c18919..119117f0bdc689a4da1c1ba4b8ae9a41fd8f05fd 100644 (file)
@@ -11,8 +11,8 @@ SYNOPSIS
 [verse]
 'git-tag' [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]  <name> [<head>]
 'git-tag' -d <name>...
-'git-tag' -l [<pattern>]
-'git-tag' -v <name>
+'git-tag' [-n [<num>]] -l [<pattern>]
+'git-tag' -v <name>...
 
 DESCRIPTION
 -----------
@@ -23,7 +23,7 @@ Unless `-f` is given, the tag must not yet exist in
 
 If one of `-a`, `-s`, or `-u <key-id>` is passed, the command
 creates a 'tag' object, and requires the tag message.  Unless
-`-m <msg>` is given, an editor is started for the user to type
+`-m <msg>` or `-F <file>` is given, an editor is started for the user to type
 in the tag message.
 
 Otherwise just the SHA1 object name of the commit object is
@@ -38,8 +38,8 @@ GnuPG key for signing.
 
 `-v <tag>` verifies the gpg signature of the tag.
 
-`-l <pattern>` lists tags that match the given pattern (or all
-if no pattern is given).
+`-l <pattern>` lists tags with names that match the given pattern
+(or all if no pattern is given).
 
 OPTIONS
 -------
@@ -59,10 +59,17 @@ OPTIONS
        Delete existing tags with the given names.
 
 -v::
-       Verify the gpg signature of given the tag
+       Verify the gpg signature of the given tag names.
+
+-n <num>::
+       <num> specifies how many lines from the annotation, if any,
+       are printed when using -l.
+       The default is not to print any annotation lines.
+       If no number is given to `-n`, only the first line is printed.
 
 -l <pattern>::
-       List tags that match the given pattern (or all if no pattern is given).
+       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.
 
 -m <msg>::
        Use the given tag message (instead of prompting)
index 7bde73b1b85df03893fa15b99b17c5aff09ae574..2d01d9666fd2ab5f53ab2265ca701d56ea370f8b 100644 (file)
@@ -90,4 +90,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 213dc8196b90170cc14e35f7c637815b39a7ff8d..20bb6a7800c43c6613aaeda2a86cd3311213bfbe 100644 (file)
@@ -33,4 +33,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index b1b3ec9772b7e689a4a3d42e78be68df34de40ac..d529a43f55ed6356f88fc6410f3fe00dfc01359d 100644 (file)
@@ -52,4 +52,3 @@ Documentation by Junio C Hamano
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 6cfbd9a842f0601cf81f8780ecf6b7939b25dccb..0a1953803e48947f6eeae385550ac2cd4e838411 100644 (file)
@@ -56,7 +56,7 @@ OPTIONS
 
 --unmerged::
         If --refresh finds unmerged changes in the index, the default
-        behavior is to error out.  This option makes git-update-index 
+        behavior is to error out.  This option makes git-update-index
         continue anyway.
 
 --ignore-missing::
@@ -64,12 +64,12 @@ OPTIONS
 
 --cacheinfo <mode> <object> <path>::
        Directly insert the specified info into the index.
-       
+
 --index-info::
         Read index information from stdin.
 
 --chmod=(+|-)x::
-        Set the execute permissions on the updated files.        
+        Set the execute permissions on the updated files.
 
 --assume-unchanged, --no-assume-unchanged::
        When these flags are specified, the object name recorded
@@ -126,7 +126,7 @@ OPTIONS
 <file>::
        Files to act on.
        Note that files beginning with '.' are discarded. This includes
-       `./file` and `dir/./file`. If you don't want this, then use     
+       `./file` and `dir/./file`. If you don't want this, then use
        cleaner names.
        The same applies to directories ending '/' and paths with '//'
 
@@ -324,4 +324,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 9424feab320afcaa5a44bd2c2c6c86728bd8d004..f222616591f157beada3c5355ee0e56d68171761 100644 (file)
@@ -7,7 +7,7 @@ git-update-ref - Update the object name stored in a ref safely
 
 SYNOPSIS
 --------
-'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | <ref> <newvalue> [<oldvalue>])
+'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | [--no-deref] <ref> <newvalue> [<oldvalue>])
 
 DESCRIPTION
 -----------
@@ -36,6 +36,9 @@ them and update them as a regular file (i.e. it will allow the
 filesystem to follow them, but will overwrite such a symlink to
 somewhere else with a regular filename).
 
+If --no-deref is given, <ref> itself is overwritten, rather than
+the result of following the symbolic pointers.
+
 In general, using
 
        git-update-ref HEAD "$head"
index 88a03c7c5ed4935fc10764d0f56326f0436a6f26..e7e82a31ea58a0515ed3a294140fd361577c07ce 100644 (file)
@@ -55,4 +55,3 @@ Documentation by Junio C Hamano.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 9b0de1c111d7b58cb8d431d90814d1f1530f5e0d..813942368b31a9eccb1c06b67e1a4303d3b62ede 100644 (file)
@@ -62,4 +62,3 @@ Documentation by Eric Biederman and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 7a6132b016903801fd1070e062381a41ec52536a..f4c540f39bffe5bda536445dc1556ff4c6bfa60a 100644 (file)
@@ -51,4 +51,3 @@ Documentation by Junio C Hamano
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 0f9bdb58dcc3cf7ee549f483164a708f536d2bd5..ac7fb19154bf7744cce6ab189ae2a424fd06f74b 100644 (file)
@@ -3,11 +3,11 @@ git-verify-tag(1)
 
 NAME
 ----
-git-verify-tag - Check the GPG signature of tag
+git-verify-tag - Check the GPG signature of tags
 
 SYNOPSIS
 --------
-'git-verify-tag' <tag>
+'git-verify-tag' <tag>...
 
 DESCRIPTION
 -----------
@@ -29,4 +29,3 @@ Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 399bff3bbcab89be96039c373cf1120dcab0efe1..607df48f09254f2c93ad41e75ed976b092711dff 100644 (file)
@@ -78,4 +78,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 96d5e07b112aa2a87eb6433f7cbeb8ce5f10205a..cb8d6aadeb3fc3b55c58bd40b96461a07a124260 100644 (file)
@@ -47,4 +47,3 @@ Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index 98860af0455ebb5290e7225e6ac677dc7ab3268c..18f8b6a0a10c2f724e623ba95d0aa17bf9b6d686 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate]
-    [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]
+    [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
+    [--help] COMMAND [ARGS]
 
 DESCRIPTION
 -----------
@@ -41,9 +42,13 @@ unreleased) version of git, that is available from 'master'
 branch of the `git.git` repository.
 Documentation for older releases are available here:
 
-* link:v1.5.2/git.html[documentation for release 1.5.2]
+* link:v1.5.2.4/git.html[documentation for release 1.5.2.4]
 
 * release notes for
+  link:RelNotes-1.5.2.4.txt[1.5.2.4],
+  link:RelNotes-1.5.2.3.txt[1.5.2.3],
+  link:RelNotes-1.5.2.2.txt[1.5.2.2],
+  link:RelNotes-1.5.2.1.txt[1.5.2.1],
   link:RelNotes-1.5.2.txt[1.5.2].
 
 * link:v1.5.1.6/git.html[documentation for release 1.5.1.6]
@@ -101,6 +106,14 @@ OPTIONS
        Set the path to the repository. This can also be controlled by
        setting the GIT_DIR environment variable.
 
+--work-tree=<path>::
+       Set the path to the working tree.  The value will not be
+       used in combination with repositories found automatically in
+       a .git directory (i.e. $GIT_DIR is not set).
+       This can also be controlled by setting the GIT_WORK_TREE
+       environment variable and the core.worktree configuration
+       variable.
+
 --bare::
        Same as --git-dir=`pwd`.
 
@@ -345,6 +358,13 @@ git so take care if using Cogito etc.
        specifies a path to use instead of the default `.git`
        for the base of the repository.
 
+'GIT_WORK_TREE'::
+       Set the path to the working tree.  The value will not be
+       used in combination with repositories found automatically in
+       a .git directory (i.e. $GIT_DIR is not set).
+       This can also be controlled by the '--work-tree' command line
+       option and the core.worktree configuration variable.
+
 git Commits
 ~~~~~~~~~~~
 'GIT_AUTHOR_NAME'::
@@ -391,8 +411,41 @@ parameter, <path>.
 
 other
 ~~~~~
+'GIT_MERGE_VERBOSITY'::
+       A number controlling the amount of output shown by
+       the recursive merge strategy.  Overrides merge.verbosity.
+       See gitlink:git-merge[1]
+
 'GIT_PAGER'::
-       This environment variable overrides `$PAGER`.
+       This environment variable overrides `$PAGER`. If it is set
+       to an empty string or to the value "cat", git will not launch
+       a pager.
+
+'GIT_SSH'::
+       If this environment variable is set then gitlink:git-fetch[1]
+       and gitlink:git-push[1] will use this command instead
+       of `ssh` when they need to connect to a remote system.
+       The 'GIT_SSH' command will be given exactly two arguments:
+       the 'username@host' (or just 'host') from the URL and the
+       shell command to execute on that remote system.
++
+To pass options to the program that you want to list in GIT_SSH
+you will need to wrap the program and options into a shell script,
+then set GIT_SSH to refer to the shell script.
++
+Usually it is easier to configure any desired options through your
+personal `.ssh/config` file.  Please consult your ssh documentation
+for further details.
+
+'GIT_FLUSH'::
+       If this environment variable is set to "1", then commands such
+       as git-blame (in incremental mode), git-rev-list, git-log,
+       git-whatchanged, etc., will force a flush of the output stream
+       after each commit-oriented record have been flushed.   If this
+       variable is set to "0", the output of these commands will be done
+       using completely buffered I/O.   If this environment variable is
+       not set, git will choose buffered or record-oriented flushing
+       based on whether stdout appears to be redirected to a file or not.
 
 'GIT_TRACE'::
        If this variable is set to "1", "2" or "true" (comparison
@@ -428,4 +481,3 @@ contributors on the git-list <git@vger.kernel.org>.
 GIT
 ---
 Part of the gitlink:git[7] suite
-
index d3ac9c718147042a89db90a87d75fee34e778e5a..8b90a5b98074e5aab851b7689d83b25a4e38e373 100644 (file)
@@ -72,8 +72,8 @@ EFFECTS
 -------
 
 Certain operations by git can be influenced by assigning
-particular attributes to a path.  Currently, three operations
-are attributes-aware.
+particular attributes to a path.  Currently, the following
+operations are attributes-aware.
 
 Checking-out and checking-in
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -199,7 +199,9 @@ Generating diff text
 ~~~~~~~~~~~~~~~~~~~~
 
 The attribute `diff` affects if `git diff` generates textual
-patch for the path or just says `Binary files differ`.
+patch for the path or just says `Binary files differ`.  It also
+can affect what line is shown on the hunk header `@@ -k,l +n,m @@`
+line.
 
 Set::
 
@@ -224,7 +226,8 @@ String::
        Diff is shown using the specified custom diff driver.
        The driver program is given its input using the same
        calling convention as used for GIT_EXTERNAL_DIFF
-       program.
+       program.  This name is also used for custom hunk header
+       selection.
 
 
 Defining a custom diff driver
@@ -249,6 +252,50 @@ parameters, just like `GIT_EXTERNAL_DIFF` program is called.
 See gitlink:git[7] for details.
 
 
+Defining a custom hunk-header
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Each group of changes (called "hunk") in the textual diff output
+is prefixed with a line of the form:
+
+       @@ -k,l +n,m @@ TEXT
+
+The text is called 'hunk header', and by default a line that
+begins with an alphabet, an underscore or a dollar sign is used,
+which matches what GNU `diff -p` output uses.  This default
+selection however is not suited for some contents, and you can
+use customized pattern to make a selection.
+
+First in .gitattributes, you would assign the `diff` attribute
+for paths.
+
+------------------------
+*.tex  diff=tex
+------------------------
+
+Then, you would define "diff.tex.funcname" configuration to
+specify a regular expression that matches a line that you would
+want to appear as the hunk header, like this:
+
+------------------------
+[diff "tex"]
+       funcname = "^\\(\\\\\\(sub\\)*section{.*\\)$"
+------------------------
+
+Note.  A single level of backslashes are eaten by the
+configuration file parser, so you would need to double the
+backslashes; the pattern above picks a line that begins with a
+backslash, and zero or more occurences of `sub` followed by
+`section` followed by open brace, to the end of line.
+
+There are a few built-in patterns to make this easier, and `tex`
+is one of them, so you do not have to write the above in your
+configuration file (you still need to enable this with the
+attribute mechanism, via `.gitattributes`).  Another built-in
+pattern is defined for `java` that defines a pattern suitable
+for program text in Java language.
+
+
 Performing a three-way merge
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
@@ -358,7 +405,7 @@ the attributes given to path `t/abc` are computed as follows:
    and `bar` attributes should be given to this path, so it
    leaves `foo` and `bar` unset.  Attribute `baz` is set.
 
-3. Finally it examines `$GIT_DIR/info/gitattributes`.  This file
+3. Finally it examines `$GIT_DIR/info/attributes`.  This file
    is used to override the in-tree settings.  The first line is
    a match, and `foo` is set, `bar` is reverted to unspecified
    state, and `baz` is unset.
index ea79d74b8896a9f6d9681b37a5d3ca373d5bc6cf..9c83095693447a46f36299f417d71262890fb098 100644 (file)
@@ -18,21 +18,26 @@ pattern.
 
 When deciding whether to ignore a path, git normally checks
 `gitignore` patterns from multiple sources, with the following
-order of precedence:
+order of precedence, from highest to lowest (within one level of
+precedence, the last matching pattern decides the outcome):
 
- * Patterns read from the file specified by the configuration
-   variable 'core.excludesfile'.
-
- * Patterns read from `$GIT_DIR/info/exclude`.
+ * Patterns read from the command line for those commands that support
+   them.
 
  * Patterns read from a `.gitignore` file in the same directory
-   as the path, or in any parent directory, ordered from the
-   deepest such file to a file in the root of the repository.
+   as the path, or in any parent directory, with patterns in the
+   higher level files (up to the root) being overriden by those in
+   lower level files down to the directory containing the file.
    These patterns match relative to the location of the
    `.gitignore` file.  A project normally includes such
    `.gitignore` files in its repository, containing patterns for
    files generated as part of the project build.
 
+ * Patterns read from `$GIT_DIR/info/exclude`.
+
+ * Patterns read from the file specified by the configuration
+   variable 'core.excludesfile'.
+
 The underlying git plumbing tools, such as
 gitlink:git-ls-files[1] and gitlink:git-read-tree[1], read
 `gitignore` patterns specified by command-line options, or from
@@ -49,7 +54,8 @@ Patterns have the following format:
 
  - An optional prefix '!' which negates the pattern; any
    matching file excluded by a previous pattern will become
-   included again.
+   included again.  If a negated pattern matches, this will
+   override lower precedence patterns sources.
 
  - If the pattern does not contain a slash '/', git treats it as
    a shell glob pattern and checks for a match against the
index 48c5894736ac3065e80a36e1a5230d86004bee2e..e9f82b97b91b92785b76a224e176b695593cdbe7 100644 (file)
@@ -99,4 +99,3 @@ Documentation by Junio C Hamano, Jonas Fonseca, and the git-list
 GIT
 ---
 Part of the gitlink:git[7] suite
-
diff --git a/Documentation/gitmodules.txt b/Documentation/gitmodules.txt
new file mode 100644 (file)
index 0000000..035294e
--- /dev/null
@@ -0,0 +1,62 @@
+gitmodules(5)
+=============
+
+NAME
+----
+gitmodules - defining submodule properties
+
+SYNOPSIS
+--------
+gitmodules
+
+
+DESCRIPTION
+-----------
+
+The `.gitmodules` file, located in the top-level directory of a git
+working tree, is a text file with a syntax matching the requirements
+of gitlink:git-config[1].
+
+The file contains one subsection per submodule, and the subsection value
+is the name of the submodule. Each submodule section also contains the
+following required keys:
+
+submodule.<name>.path::
+       Defines the path, relative to the top-level directory of the git
+       working tree, where the submodule is expected to be checked out.
+       The path name must not end with a `/`. All submodule paths must
+       be unique within the .gitmodules file.
+
+submodule.<name>.url::
+       Defines an url from where the submodule repository can be cloned.
+
+
+EXAMPLES
+--------
+
+Consider the following .gitmodules file:
+
+       [submodule "libfoo"]
+               path = include/foo
+               url = git://foo.com/git/lib.git
+
+       [submodule "libbar"]
+               path = include/bar
+               url = git://bar.com/git/lib.git
+
+
+This defines two submodules, `libfoo` and `libbar`. These are expected to
+be checked out in the paths 'include/foo' and 'include/bar', and for both
+submodules an url is specified which can be used for cloning the submodules.
+
+SEE ALSO
+--------
+gitlink:git-submodule[1] gitlink:git-config[1]
+
+DOCUMENTATION
+-------------
+Documentation by Lars Hjemli <hjemli@gmail.com>
+
+GIT
+---
+Part of the gitlink:git[7] suite
index aabb9750fddbc28091c46780ccf7b835b62b04c7..6836477ca816a6caa860960c3456cfbbf5cd32f1 100644 (file)
@@ -12,11 +12,10 @@ This document describes the currently defined hooks.
 applypatch-msg
 --------------
 
-This hook is invoked by `git-applypatch` script, which is
-typically invoked by `git-applymbox`.  It takes a single
+This hook is invoked by `git-am` script.  It takes a single
 parameter, the name of the file that holds the proposed commit
 log message.  Exiting with non-zero status causes
-`git-applypatch` to abort before applying the patch.
+`git-am` to abort before applying the patch.
 
 The hook is allowed to edit the message file in place, and can
 be used to normalize the message into some project standard
@@ -29,8 +28,7 @@ The default 'applypatch-msg' hook, when enabled, runs the
 pre-applypatch
 --------------
 
-This hook is invoked by `git-applypatch` script, which is
-typically invoked by `git-applymbox`.  It takes no parameter,
+This hook is invoked by `git-am`.  It takes no parameter,
 and is invoked after the patch is applied, but before a commit
 is made.  Exiting with non-zero status causes the working tree
 after application of the patch not committed.
@@ -44,12 +42,11 @@ The default 'pre-applypatch' hook, when enabled, runs the
 post-applypatch
 ---------------
 
-This hook is invoked by `git-applypatch` script, which is
-typically invoked by `git-applymbox`.  It takes no parameter,
+This hook is invoked by `git-am`.  It takes no parameter,
 and is invoked after the patch is applied and a commit is made.
 
 This hook is meant primarily for notification, and cannot affect
-the outcome of `git-applypatch`.
+the outcome of `git-am`.
 
 pre-commit
 ----------
index 646c55cc6954fbf36a11fbe22cb2c1acb64dfd43..554909fe08de380aa02f2bf37f0f3b43b0233f4b 100644 (file)
@@ -9,16 +9,16 @@ Abstract: In this article, Linus demonstrates how a broken commit
 
 On Sat, 13 Aug 2005, Linus Torvalds wrote:
 
-> That's correct. Same things apply: you can move a patch over, and create a 
-> new one with a modified comment, but basically the _old_ commit will be 
+> That's correct. Same things apply: you can move a patch over, and create a
+> new one with a modified comment, but basically the _old_ commit will be
 > immutable.
 
 Let me clarify.
 
 You can entirely _drop_ old branches, so commits may be immutable, but
-nothing forces you to keep them. Of course, when you drop a commit, you'll 
-always end up dropping all the commits that depended on it, and if you 
-actually got somebody else to pull that commit you can't drop it from 
+nothing forces you to keep them. Of course, when you drop a commit, you'll
+always end up dropping all the commits that depended on it, and if you
+actually got somebody else to pull that commit you can't drop it from
 _their_ repository, but undoing things is not impossible.
 
 For example, let's say that you've made a mess of things: you've committed
@@ -29,7 +29,7 @@ want to save "b" and "c". What you can do is
        # for reference
        git branch broken
 
-       # Reset the main branch to three parents back: this 
+       # Reset the main branch to three parents back: this
        # effectively undoes the three top commits
        git reset HEAD^^^
        git checkout -f
@@ -59,7 +59,7 @@ Finally, check out the end result again:
 
 to see that everything looks sensible.
 
-And then, you can just remove the broken branch if you decide you really 
+And then, you can just remove the broken branch if you decide you really
 don't want it:
 
        # remove 'broken' branch
@@ -68,8 +68,8 @@ don't want it:
        # Prune old objects if you're really really sure
        git prune
 
-And yeah, I'm sure there are other ways of doing this. And as usual, the 
-above is totally untested, and I just wrote it down in this email, so if 
+And yeah, I'm sure there are other ways of doing this. And as usual, the
+above is totally untested, and I just wrote it down in this email, so if
 I've done something wrong, you'll have to figure it out on your own ;)
 
                        Linus
@@ -77,5 +77,3 @@ I've done something wrong, you'll have to figure it out on your own ;)
 To unsubscribe from this list: send the line "unsubscribe git" in
 the body of a message to majordomo@vger.kernel.org
 More majordomo info at  http://vger.kernel.org/majordomo-info.html
-
-
index 3b3a5c2e69e59d4708171ba745d7d4009788102b..7a76045eb742b38e726e15491db2bf4315cb8f6a 100644 (file)
@@ -14,10 +14,10 @@ Petr Baudis <pasky@suse.cz> writes:
 > Dear diary, on Sun, Aug 14, 2005 at 09:57:13AM CEST, I got a letter
 > where Junio C Hamano <junkio@cox.net> told me that...
 >> Linus Torvalds <torvalds@osdl.org> writes:
->> 
->> > Junio, maybe you want to talk about how you move patches from your "pu" 
+>>
+>> > Junio, maybe you want to talk about how you move patches from your "pu"
 >> > branch to the real branches.
->> 
+>>
 > Actually, wouldn't this be also precisely for what StGIT is intended to?
 
 Exactly my feeling.  I was sort of waiting for Catalin to speak
@@ -118,7 +118,7 @@ up your changes, along with other changes.
 
    where                      *your "master" head
   upstream --> #1 --> #2 --> #3
-    used   \ 
+    used   \
    to be     \--> #A --> #2' --> #3' --> #B --> #C
                                                 *upstream head
 
@@ -133,7 +133,7 @@ You fetch from upstream, but not merge.
     $ git fetch upstream
 
 This leaves the updated upstream head in .git/FETCH_HEAD but
-does not touch your .git/HEAD nor .git/refs/heads/master.  
+does not touch your .git/HEAD nor .git/refs/heads/master.
 You run "git rebase" now.
 
     $ git rebase FETCH_HEAD master
@@ -161,5 +161,3 @@ the #1' commit.
 To unsubscribe from this list: send the line "unsubscribe git" in
 the body of a message to majordomo@vger.kernel.org
 More majordomo info at  http://vger.kernel.org/majordomo-info.html
-
-
index 02621b54a03ce5771eab1c49d121a07ba31cbf16..8d55dfbfaef7ef6bb30b65ea52c7825b099b50d8 100644 (file)
@@ -84,4 +84,3 @@ There are four things worth mentioning:
  - This is still crude and does not protect against simultaneous
    make invocations stomping on each other.  I would need to add
    some locking mechanism for this.
-
index d88ec23a97bc59f29a5f9734fc5ba0ef5a9ae6cf..865a6663240f744d448b0a125ebc123afb72ede2 100644 (file)
@@ -146,7 +146,7 @@ Everything is in the good order.  I do not need the temporary branch
 nor tag anymore, so remove them:
 
 ------------------------------------------------
-$ rm -f .git/refs/tags/pu-anchor 
+$ rm -f .git/refs/tags/pu-anchor
 $ git branch -d revert-c99
 ------------------------------------------------
 
index 090e2c9b018c7ea16b98094746d4e005abd27eec..0d73b31224c881eb83f30ec3c5421a81288f6502 100644 (file)
@@ -12,7 +12,7 @@ up with a history like this:
 
             "master"
         o---o
-             \                    "topic" 
+             \                    "topic"
               o---o---o---o---o---o
 
 At this point, "topic" contains something I know I want, but it
@@ -29,11 +29,11 @@ start building on top of "master":
         $ git checkout -b topicA master
         ... pick and apply pieces from P.diff to build
         ... commits on topicA branch.
-                      
+
               o---o---o
              /        "topicA"
         o---o"master"
-             \                    "topic" 
+             \                    "topic"
               o---o---o---o---o---o
 
 Before doing each commit on "topicA" HEAD, I run "diff HEAD"
@@ -59,7 +59,7 @@ other topic:
              /o---o---o
             |/        "topicA"
         o---o"master"
-             \                    "topic" 
+             \                    "topic"
               o---o---o---o---o---o
 
 After I am done, I'd try a pretend-merge between "topicA" and
@@ -73,7 +73,7 @@ After I am done, I'd try a pretend-merge between "topicA" and
              /o---o---o----------'
             |/        "topicA"
         o---o"master"
-             \                    "topic" 
+             \                    "topic"
               o---o---o---o---o---o
 
 The last diff better not to show anything other than cleanups
@@ -84,8 +84,7 @@ for crufts.  Then I can finally clean things up:
 
                                 "topicB"
                o---o---o---o---o
-              / 
+              /
              /o---o---o
             |/        "topicA"
         o---o"master"
-
index 1a1eb246bf5b5ff5f67edea306371604159762ae..4e2f75cb6167633c97ec1981d2b6659368cc0170 100644 (file)
@@ -49,4 +49,3 @@ Now, test your daemon with
        $ git ls-remote git://127.0.0.1/rule-the-world.git
 
 If this does not work, find out why, and submit a patch to this document.
-
index a64054948aec41e820b8864a71229bbd568aa99a..5433cf8cedc466d2da56386ec4b5f4f9f462ef5b 100755 (executable)
@@ -7,10 +7,10 @@ mandir="$2"
 SUBDIRECTORY_OK=t
 USAGE='<refname> <target directory>'
 . git-sh-setup
-export GIT_DIR
+cd_to_toplevel
 
 test -z "$mandir" && usage
-if ! git-rev-parse --verify "$head^0" >/dev/null; then
+if ! git rev-parse --verify "$head^0" >/dev/null; then
        echo >&2 "head: $head does not exist in the current repository"
        usage
 fi
@@ -18,14 +18,14 @@ fi
 GIT_INDEX_FILE=`pwd`/.quick-doc.index
 export GIT_INDEX_FILE
 rm -f "$GIT_INDEX_FILE"
-git-read-tree $head
-git-checkout-index -a -f --prefix="$mandir"/
+trap 'rm -f "$GIT_INDEX_FILE"' 0
+
+git read-tree $head
+git checkout-index -a -f --prefix="$mandir"/
 
 if test -n "$GZ"; then
-       cd "$mandir"
-       for i in `git-ls-tree -r --name-only $head`
-       do
-               gzip < $i > $i.gz && rm $i
-       done
+       git ls-tree -r --name-only $head |
+       xargs printf "$mandir/%s\n" |
+       xargs gzip -f
 fi
 rm -f "$GIT_INDEX_FILE"
index 182cef54be5963ff0f32a24329ca195607413e29..d64c259bb35d3140b371e8717a2553146d3f92f5 100644 (file)
@@ -1,3 +1,7 @@
+--summary::
+       Show a diffstat at the end of the merge. The diffstat is also
+       controlled by the configuration option merge.diffstat.
+
 -n, \--no-summary::
        Do not show diffstat at the end of the merge.
 
@@ -21,4 +25,3 @@
        If there is no `-s` option, a built-in list of strategies
        is used instead (`git-merge-recursive` when merging a single
        head, `git-merge-octopus` otherwise).
-
index d922e8e86c173491fc492d3060bf92f4ee7b6c2d..0193c3ce58de4f51a164d43e68023fdf5639a920 100644 (file)
@@ -106,12 +106,14 @@ The placeholders are:
 - '%aD': author date, RFC2822 style
 - '%ar': author date, relative
 - '%at': author date, UNIX timestamp
+- '%ai': author date, ISO 8601 format
 - '%cn': committer name
 - '%ce': committer email
 - '%cd': committer date
 - '%cD': committer date, RFC2822 style
 - '%cr': committer date, relative
 - '%ct': committer date, UNIX timestamp
+- '%ci': committer date, ISO 8601 format
 - '%e': encoding
 - '%s': subject
 - '%b': body
@@ -121,4 +123,3 @@ The placeholders are:
 - '%Creset': reset color
 - '%m': left, right or boundary mark
 - '%n': newline
-
index 7d515be0fd3b89d602ac2919fd2ea3dd4bc84131..973d8dd733f954abf93aac7c68efef1154e9f315 100644 (file)
@@ -1,9 +1,18 @@
 --pretty[='<format>']::
 
-       Pretty print the contents of the commit logs in a given format,
+       Pretty-print the contents of the commit logs in a given format,
        where '<format>' can be one of 'oneline', 'short', 'medium',
        'full', 'fuller', 'email', 'raw' and 'format:<string>'.
-       When left out the format default to 'medium'.
+       When omitted, the format defaults to 'medium'.
+
+--abbrev-commit::
+       Instead of showing the full 40-byte hexadecimal commit object
+       name, show only handful hexdigits 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.
 
 --encoding[=<encoding>]::
        The commit objects record the encoding used for the log message
@@ -11,4 +20,3 @@
        command to re-code the commit log message in the encoding
        preferred by the user.  For non plumbing commands this
        defaults to UTF-8.
-
index 8d4e950abc65c233268d785dcbfee67ab0522f4c..b6eb7fc6189daece1a200293dc767b6bc064620a 100644 (file)
@@ -58,7 +58,7 @@ is often useful.
 +
 Some short-cut notations are also supported.
 +
-* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`; 
+* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`;
   it requests fetching everything up to the given tag.
 * A parameter <ref> without a colon is equivalent to
   <ref>: when pulling/fetching, so it merges <ref> into the current
index 15221b53206eb65072f147ac72f556d0a1ee55e4..4c92e375fed4592f7190086dd40ef6dd1241d238 100644 (file)
@@ -177,4 +177,3 @@ shallow::
        This is similar to `info/grafts` but is internally used
        and maintained by shallow clone mechanism.  See `--depth`
        option to gitlink:git-clone[1] and gitlink:git-fetch[1].
-
index 9ce3c473ae1a9b5fd0b3a909fb264d8e0fff27a5..e5b31c81fa3b5268c6d1bc0afcaec967f401ac28 100644 (file)
@@ -80,7 +80,7 @@ Pack Idx file:
            +--------------------------------+ |
 main       | offset                         | |
 index      | object name 00XXXXXXXXXXXXXXXX | |
-table      +--------------------------------+ | 
+table      +--------------------------------+ |
            | offset                         | |
            | object name 00XXXXXXXXXXXXXXXX | |
            +--------------------------------+ |
@@ -97,14 +97,14 @@ trailer       | | packfile checksum              |
          | +--------------------------------+
          | | idxfile checksum               |
          | +--------------------------------+
-          .-------.      
+          .-------.
                   |
 Pack file entry: <+
 
      packed object header:
        1-byte size extension bit (MSB)
               type (next 3 bit)
-              size0 (lower 4-bit) 
+              size0 (lower 4-bit)
         n-byte sizeN (as long as MSB is set, each 7-bit)
                size0..sizeN form 4+7+7+..+7 bit integer, size0
                is the least significant part, and sizeN is the
@@ -114,5 +114,5 @@ Pack file entry: <+
                is the size before compression).
        If it is DELTA, then
          20-byte base object name SHA1 (the size above is the
-               size of the delta data that follows).
+               size of the delta data that follows).
           delta data, deflated.
index 965ec00a6b403d1a4d23c7db2d08198480dc4f5b..bd9fbee99aeb67c5362523d6b4cb43d4889cf8a5 100644 (file)
@@ -354,7 +354,7 @@ used for pulls:
 
 -------------------------------------
 $ git config --get remote.origin.url
-/home/bob/myrepo
+/home/alice/project
 -------------------------------------
 
 (The complete configuration created by git-clone is visible using
diff --git a/Documentation/urls-remotes.txt b/Documentation/urls-remotes.txt
new file mode 100644 (file)
index 0000000..5dd1f83
--- /dev/null
@@ -0,0 +1,55 @@
+include::urls.txt[]
+
+REMOTES
+-------
+
+In addition to the above, as a short-hand, the name of a
+file in `$GIT_DIR/remotes` directory can be given; the
+named file should be in the following format:
+
+------------
+       URL: one of the above URL format
+       Push: <refspec>
+       Pull: <refspec>
+
+------------
+
+Then such a short-hand is specified in place of
+<repository> without <refspec> parameters on the command
+line, <refspec> specified on `Push:` lines or `Pull:`
+lines are used for `git-push` and `git-fetch`/`git-pull`,
+respectively.  Multiple `Push:` and `Pull:` lines may
+be specified for additional branch mappings.
+
+Or, equivalently, in the `$GIT_DIR/config` (note the use
+of `fetch` instead of `Pull:`):
+
+------------
+       [remote "<remote>"]
+               url = <url>
+               push = <refspec>
+               fetch = <refspec>
+
+------------
+
+The name of a file in `$GIT_DIR/branches` directory can be
+specified as an older notation short-hand; the named
+file should contain a single line, a URL in one of the
+above formats, optionally followed by a hash `#` and the
+name of remote head (URL fragment notation).
+`$GIT_DIR/branches/<remote>` file that stores a <url>
+without the fragment is equivalent to have this in the
+corresponding file in the `$GIT_DIR/remotes/` directory.
+
+------------
+       URL: <url>
+       Pull: refs/heads/master:<remote>
+
+------------
+
+while having `<url>#<head>` is equivalent to
+
+------------
+       URL: <url>
+       Pull: refs/heads/<head>:<remote>
+------------
index 745f9677d005b522f52496339abd5afc4267a815..b38145faff384344fde354152db186275ebeb5ca 100644 (file)
@@ -15,11 +15,11 @@ to name the remote repository:
 - ssh://{startsb}user@{endsb}host.xz/~/path/to/repo.git
 ===============================================================
 
-SSH is the default transport protocol.  You can optionally specify
-which user to log-in as, and an alternate, scp-like syntax is also
-supported.  Both syntaxes support username expansion,
-as does the native git protocol. The following three are
-identical to the last three above, respectively:
+SSH is the default transport protocol over the network.  You can
+optionally specify which user to log-in as, and an alternate,
+scp-like syntax is also supported.  Both syntaxes support
+username expansion, as does the native git protocol. The following
+three are identical to the last three above, respectively:
 
 ===============================================================
 - {startsb}user@{endsb}host.xz:/path/to/repo.git/
@@ -27,62 +27,12 @@ identical to the last three above, respectively:
 - {startsb}user@{endsb}host.xz:path/to/repo.git
 ===============================================================
 
-To sync with a local directory, use:
+To sync with a local directory, you can use:
 
 ===============================================================
 - /path/to/repo.git/
+- file:///path/to/repo.git/
 ===============================================================
 
-REMOTES
--------
-
-In addition to the above, as a short-hand, the name of a
-file in `$GIT_DIR/remotes` directory can be given; the
-named file should be in the following format:
-
-------------
-       URL: one of the above URL format
-       Push: <refspec>
-       Pull: <refspec>
-
-------------
-
-Then such a short-hand is specified in place of
-<repository> without <refspec> parameters on the command
-line, <refspec> specified on `Push:` lines or `Pull:`
-lines are used for `git-push` and `git-fetch`/`git-pull`,
-respectively.  Multiple `Push:` and `Pull:` lines may
-be specified for additional branch mappings.
-
-Or, equivalently, in the `$GIT_DIR/config` (note the use
-of `fetch` instead of `Pull:`):
-
-------------
-       [remote "<remote>"]
-               url = <url>
-               push = <refspec>
-               fetch = <refspec>
-
-------------
-
-The name of a file in `$GIT_DIR/branches` directory can be
-specified as an older notation short-hand; the named
-file should contain a single line, a URL in one of the
-above formats, optionally followed by a hash `#` and the
-name of remote head (URL fragment notation).
-`$GIT_DIR/branches/<remote>` file that stores a <url>
-without the fragment is equivalent to have this in the
-corresponding file in the `$GIT_DIR/remotes/` directory.
-
-------------
-       URL: <url>
-       Pull: refs/heads/master:<remote>
-
-------------
-
-while having `<url>#<head>` is equivalent to
-
-------------
-       URL: <url>
-       Pull: refs/heads/<head>:<remote>
-------------
+They are mostly equivalent, except when cloning.  See
+gitlink:git-clone[1] for details.
index d6caff42486bff0a78f66ce0ab1dbca0c1c944e2..f89952ad844d962e23ec7a40733926ddf4a9fa15 100644 (file)
@@ -1,4 +1,4 @@
-Git User's Manual (for version 1.5.1 or newer)
+Git User's Manual (for version 1.5.3 or newer)
 ______________________________________________
 
 
@@ -154,11 +154,11 @@ Author: Jamal Hadi Salim <hadi@cyberus.ca>
 Date:   Sat Dec 2 22:22:25 2006 -0800
 
     [XFRM]: Fix aevent structuring to be more complete.
-    
+
     aevents can not uniquely identify an SA. We break the ABI with this
     patch, but consensus is that since it is not yet utilized by any
     (known) application then it is fine (better do it now than later).
-    
+
     Signed-off-by: Jamal Hadi Salim <hadi@cyberus.ca>
     Signed-off-by: David S. Miller <davem@davemloft.net>
 
@@ -167,7 +167,7 @@ index 8be626f..d7aac9d 100644
 --- a/Documentation/networking/xfrm_sync.txt
 +++ b/Documentation/networking/xfrm_sync.txt
 @@ -47,10 +47,13 @@ aevent_id structure looks like:
+
     struct xfrm_aevent_id {
               struct xfrm_usersa_id           sa_id;
 +             xfrm_address_t                  saddr;
@@ -449,7 +449,7 @@ Exploring git history
 
 Git is best thought of as a tool for storing the history of a
 collection of files.  It does this by storing compressed snapshots of
-the contents of a file heirarchy, together with "commits" which show
+the contents of a file hierarchy, together with "commits" which show
 the relationships between these snapshots.
 
 Git provides extremely flexible and fast tools for exploring the
@@ -1057,7 +1057,7 @@ $ git show
 -------------------------------------------------
 
 As a special shortcut,
-               
+
 -------------------------------------------------
 $ git commit -a
 -------------------------------------------------
@@ -1070,7 +1070,7 @@ about to commit:
 
 -------------------------------------------------
 $ git diff --cached # difference between HEAD and the index; what
-                   # would be commited if you ran "commit" now.
+                   # would be committed if you ran "commit" now.
 $ git diff         # difference between the index file and your
                    # working directory; changes that would not
                    # be included if you ran "commit" now.
@@ -1079,6 +1079,11 @@ $ git diff HEAD      # difference between HEAD and working tree; what
 $ git status       # a brief per-file summary of the above.
 -------------------------------------------------
 
+You can also use gitlink:git-gui[1] to create commits, view changes in
+the index and the working tree files, and individually select diff hunks
+for inclusion in the index (by right-clicking on the diff hunk and
+choosing "Stage Hunk For Commit").
+
 [[creating-good-commit-messages]]
 Creating good commit messages
 -----------------------------
@@ -1257,7 +1262,7 @@ index 802992c,2b60207..0000000
 ++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
 -------------------------------------------------
 
-Recall that the commit which will be commited after we resolve this
+Recall that the commit which will be committed after we resolve this
 conflict will have two parents instead of the usual one: one parent
 will be HEAD, the tip of the current branch; the other will be the
 tip of the other branch, which is stored temporarily in MERGE_HEAD.
@@ -1351,7 +1356,7 @@ away, you can always return to the pre-merge state with
 $ git reset --hard HEAD
 -------------------------------------------------
 
-Or, if you've already commited the merge that you want to throw away,
+Or, if you've already committed the merge that you want to throw away,
 
 -------------------------------------------------
 $ git reset --hard ORIG_HEAD
@@ -1484,6 +1489,38 @@ $ git show HEAD^:path/to/file
 
 which will display the given version of the file.
 
+[[interrupted-work]]
+Temporarily setting aside work in progress
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+While you are in the middle of working on something complicated, you
+find an unrelated but obvious and trivial bug.  You would like to fix it
+before continuing.  You can use gitlink:git-stash[1] to save the current
+state of your work, and after fixing the bug (or, optionally after doing
+so on a different branch and then coming back), unstash the
+work-in-progress changes.
+
+------------------------------------------------
+$ git stash "work in progress for foo feature"
+------------------------------------------------
+
+This command will save your changes away to the `stash`, and
+reset your working tree and the index to match the tip of your
+current branch.  Then you can make your fix as usual.
+
+------------------------------------------------
+... edit and test ...
+$ git commit -a -m "blorpl: typofix"
+------------------------------------------------
+
+After that, you can go back to what you were working on with
+`git stash apply`:
+
+------------------------------------------------
+$ git stash apply
+------------------------------------------------
+
+
 [[ensuring-good-performance]]
 Ensuring good performance
 -------------------------
@@ -1555,7 +1592,7 @@ history.
 
 Fortunately, git also keeps a log, called a "reflog", of all the
 previous values of each branch.  So in this case you can still find the
-old history using, for example, 
+old history using, for example,
 
 -------------------------------------------------
 $ git log master@{1}
@@ -1631,7 +1668,7 @@ If you decide you want the history back, you can always create a new
 reference pointing to it, for example, a new branch:
 
 ------------------------------------------------
-$ git branch recovered-branch 7281251ddd 
+$ git branch recovered-branch 7281251ddd
 ------------------------------------------------
 
 Other types of dangling objects (blobs and trees) are also possible, and
@@ -1667,24 +1704,19 @@ one step:
 $ git pull origin master
 -------------------------------------------------
 
-In fact, "origin" is normally the default repository to pull from,
-and the default branch is normally the HEAD of the remote repository,
-so often you can accomplish the above with just
+In fact, if you have "master" checked out, then by default "git pull"
+merges from the HEAD branch of the origin repository.  So often you can
+accomplish the above with just a simple
 
 -------------------------------------------------
 $ git pull
 -------------------------------------------------
 
-See the descriptions of the branch.<name>.remote and branch.<name>.merge
-options in gitlink:git-config[1] to learn how to control these defaults
-depending on the current branch.  Also note that the --track option to
-gitlink:git-branch[1] and gitlink:git-checkout[1] can be used to
-automatically set the default remote branch to pull from at the time
-that a branch is created:
-
--------------------------------------------------
-$ git checkout --track -b maint origin/maint
--------------------------------------------------
+More generally, a branch that is created from a remote branch will pull
+by default from that branch.  See the descriptions of the
+branch.<name>.remote and branch.<name>.merge options in
+gitlink:git-config[1], and the discussion of the --track option in
+gitlink:git-checkout[1], to learn how to control these defaults.
 
 In addition to saving you keystrokes, "git pull" also helps you by
 producing a default commit message documenting the branch and
@@ -1803,7 +1835,7 @@ like this:
 
                         you push
   your personal repo ------------------> your public repo
-       ^                                     |
+       ^                                     |
        |                                     |
        | you pull                            | they pull
        |                                     |
@@ -2377,7 +2409,7 @@ the result would create a new merge commit, like this:
         \        \
          a--b--c--m <-- mywork
 ................................................
+
 However, if you prefer to keep the history in mywork a simple series of
 commits without any merges, you may instead choose to use
 gitlink:git-rebase[1]:
@@ -2479,8 +2511,10 @@ $ 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 commit --amend.
+The 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
 patches, then reset the state to before the patches:
@@ -2753,7 +2787,7 @@ must have at least one root, and while you can tie several different
 root objects together into one project by creating a commit object which
 has two or more separate roots as its ultimate parents, that's probably
 just going to confuse people.  So aim for the notion of "one root object
-per project", even if git itself does not enforce that. 
+per project", even if git itself does not enforce that.
 
 A <<def_tag_object,"tag" object>> symbolically identifies and can be
 used to sign other objects. It contains the identifier and type of
@@ -2972,15 +3006,15 @@ cache, and the normal operation is to re-generate it completely from a
 known tree object, or update/compare it with a live tree that is being
 developed.  If you blow the directory cache away entirely, you generally
 haven't lost any information as long as you have the name of the tree
-that it described. 
+that it described.
 
-At the same time, the index is at the same time also the
-staging area for creating new trees, and creating a new tree always
-involves a controlled modification of the index file.  In particular,
-the index file can have the representation of an intermediate tree that
-has not yet been instantiated.  So the index can be thought of as a
-write-back cache, which can contain dirty information that has not yet
-been written back to the backing store.
+At the same time, the index is also the staging area for creating
+new trees, and creating a new tree always involves a controlled
+modification of the index file.  In particular, the index file can
+have the representation of an intermediate tree that has not yet been
+instantiated.  So the index can be thought of as a write-back cache,
+which can contain dirty information that has not yet been written back
+to the backing store.
 
 
 
@@ -2992,7 +3026,7 @@ Generally, all "git" operations work on the index file. Some operations
 work *purely* on the index file (showing the current state of the
 index), but most operations move data to and from the index file. Either
 from the database or from the working directory. Thus there are four
-main combinations: 
+main combinations:
 
 [[working-directory-to-index]]
 working directory -> index
@@ -3455,7 +3489,7 @@ 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.
 
-Anyway, once you are sure that you're not interested in any dangling 
+Anyway, once you are sure that you're not interested in any dangling
 state, you can just prune all unreachable objects:
 
 ------------------------------------------------
@@ -3466,12 +3500,12 @@ 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. 
-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 
+(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
 repository is a *BAD* idea).
 
 [[birdview-on-the-source-code]]
index a62248deb16f2db03fb12207b6729966fa76e457..3c0032cec592a765692234f1cba47dfdcc3a9200 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.2.5.GIT
+DEF_VER=v1.5.3.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index 361c65bacc50b4038cb9a32bab07c275a1ecdc76..289b046a443c0647624607d471289b2c7dcd470b 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -5,8 +5,8 @@ Normally you can just do "make" followed by "make install", and that
 will install the git programs in your own ~/bin/ directory.  If you want
 to do a global install, you can do
 
-       $ make prefix=/usr all doc ;# as yourself
-       # make prefix=/usr install install-doc ;# as root
+       $ make prefix=/usr all doc info ;# as yourself
+       # make prefix=/usr install install-doc install-info ;# as root
 
 (or prefix=/usr/local, of course).  Just like any program suite
 that uses $prefix, the built results have some paths encoded,
@@ -31,7 +31,7 @@ Issues of note:
    interactive tools.  None of the core git stuff needs the wrapper,
    it's just a convenient shorthand and while it is documented in some
    places, you can always replace "git commit" with "git-commit"
-   instead. 
+   instead.
 
    But let's face it, most of us don't have GNU interactive tools, and
    even if we had it, we wouldn't know what it does.  I don't think it
@@ -56,9 +56,8 @@ Issues of note:
 
        - "zlib", the compression library. Git won't build without it.
 
-       - "openssl".  The git-rev-list program uses bignum support from
-         openssl, and unless you specify otherwise, you'll also get the
-         SHA1 library from here.
+       - "openssl".  Unless you specify otherwise, you'll get the SHA1
+         library from here.
 
          If you don't have openssl, you can use one of the SHA1 libraries
          that come with git (git includes the one from Mozilla, and has
@@ -73,7 +72,7 @@ Issues of note:
          management over DAV.  Similar to "curl" above, this is optional.
 
         - "wish", the Tcl/Tk windowing shell is used in gitk to show the
-          history graphically
+          history graphically, and in git-gui.
 
        - "ssh" is used to push and pull over the net
 
@@ -89,10 +88,20 @@ Issues of note:
    will include them.  Note that config.mak is not distributed;
    the name is reserved for local settings.
 
- - To build and install documentation suite, you need to have the
-   asciidoc/xmlto toolchain.  Alternatively, pre-formatted
-   documentation are available in "html" and "man" branches of the git
-   repository itself.  For example, you could:
+ - To build and install documentation suite, you need to have
+   the asciidoc/xmlto toolchain.  Because not many people are
+   inclined to install the tools, the default build target
+   ("make all") does _not_ build them.
+
+   Building and installing the info file additionally requires
+   makeinfo and docbook2X.  Version 0.8.3 is known to work.
+
+   The documentation is written for AsciiDoc 7, but "make
+   ASCIIDOC8=YesPlease doc" will let you format with AsciiDoc 8.
+
+   Alternatively, pre-formatted documentation are available in
+   "html" and "man" branches of the git repository itself.  For
+   example, you could:
 
        $ mkdir manual && cd manual
        $ git init
@@ -112,3 +121,6 @@ Issues of note:
 
        http://www.kernel.org/pub/software/scm/git/docs/
 
+   It has been reported that docbook-xsl version 1.72 and 1.73 are
+   buggy; 1.72 misformats manual pages for callouts, and 1.73 needs
+   the patch in contrib/patches/docbook-xsl-manpages-charmap.patch
index eb443bddb9a164c6b6b8c23ec8be71f50cb615a8..4eb463797adc693dc168b926b6932ff53f17d0b1 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -112,8 +112,6 @@ all::
 # Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
 # MakeMaker (e.g. using ActiveState under Cygwin).
 #
-# Define WITH_P4IMPORT to build and install Python git-p4import script.
-#
 # Define NO_TCLTK if you do not want Tcl/Tk GUI.
 #
 # The TCL_PATH variable governs the location of the Tcl interpreter
@@ -146,13 +144,14 @@ STRIP ?= strip
 prefix = $(HOME)
 bindir = $(prefix)/bin
 gitexecdir = $(bindir)
-sharedir = $(prefix)/share/
-template_dir = $(sharedir)/git-core/templates/
+sharedir = $(prefix)/share
+template_dir = $(sharedir)/git-core/templates
 ifeq ($(prefix),/usr)
 sysconfdir = /etc
 else
 sysconfdir = $(prefix)/etc
 endif
+lib = lib
 ETC_GITCONFIG = $(sysconfdir)/gitconfig
 # DESTDIR=
 
@@ -176,7 +175,9 @@ export prefix bindir gitexecdir sharedir template_dir sysconfdir
 
 CC = gcc
 AR = ar
+RM = rm -f
 TAR = tar
+FIND = find
 INSTALL = install
 RPMBUILD = rpmbuild
 TCL_PATH = tclsh
@@ -204,14 +205,15 @@ SCRIPT_SH = \
        git-fetch.sh \
        git-ls-remote.sh \
        git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
-       git-pull.sh git-rebase.sh \
+       git-pull.sh git-rebase.sh git-rebase--interactive.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
        git-sh-setup.sh \
-       git-tag.sh git-verify-tag.sh \
-       git-applymbox.sh git-applypatch.sh git-am.sh \
+       git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
        git-merge-resolve.sh git-merge-ours.sh \
-       git-lost-found.sh git-quiltimport.sh
+       git-lost-found.sh git-quiltimport.sh git-submodule.sh \
+       git-filter-branch.sh \
+       git-stash.sh
 
 SCRIPT_PERL = \
        git-add--interactive.perl \
@@ -220,27 +222,15 @@ SCRIPT_PERL = \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
 
-SCRIPT_PYTHON = \
-       git-p4import.py
-
-ifdef WITH_P4IMPORT
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-status git-instaweb
-else
-SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
-         $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-         git-status git-instaweb
-endif
-
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
        git-convert-objects$X git-fetch-pack$X \
        git-hash-object$X git-index-pack$X git-local-fetch$X \
        git-fast-import$X \
-       git-merge-base$X \
        git-daemon$X \
        git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \
        git-peek-remote$X git-receive-pack$X \
@@ -284,9 +274,6 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
-ifndef PYTHON_PATH
-       PYTHON_PATH = /usr/local/bin/python
-endif
 
 export PERL_PATH
 
@@ -298,7 +285,8 @@ LIB_H = \
        diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \
        run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \
        tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
-       utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h mailmap.h
+       utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \
+       mailmap.h remote.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -320,7 +308,7 @@ LIB_OBJS = \
        write_or_die.o trace.o list-objects.o grep.o match-trees.o \
        alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
        color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
-       convert.o attr.o decorate.o progress.o mailmap.o symlinks.o
+       convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -374,18 +362,20 @@ BUILTIN_OBJS = \
        builtin-show-branch.o \
        builtin-stripspace.o \
        builtin-symbolic-ref.o \
+       builtin-tag.o \
        builtin-tar-tree.o \
        builtin-unpack-objects.o \
        builtin-update-index.o \
        builtin-update-ref.o \
        builtin-upload-archive.o \
        builtin-verify-pack.o \
+       builtin-verify-tag.o \
        builtin-write-tree.o \
        builtin-show-ref.o \
        builtin-pack-refs.o
 
 GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
-EXTLIBS = -lz
+EXTLIBS =
 
 #
 # Platform specific tweaks
@@ -469,6 +459,10 @@ ifeq ($(uname_S),AIX)
        NO_STRLCPY = YesPlease
        NEEDS_LIBICONV=YesPlease
 endif
+ifeq ($(uname_S),GNU)
+       # GNU/Hurd
+       NO_STRLCPY=YesPlease
+endif
 ifeq ($(uname_S),IRIX64)
        NO_IPV6=YesPlease
        NO_SETENV=YesPlease
@@ -512,9 +506,9 @@ endif
 
 ifndef NO_CURL
        ifdef CURLDIR
-               # Try "-Wl,-rpath=$(CURLDIR)/lib" in such a case.
+               # Try "-Wl,-rpath=$(CURLDIR)/$(lib)" in such a case.
                BASIC_CFLAGS += -I$(CURLDIR)/include
-               CURL_LIBCURL = -L$(CURLDIR)/lib $(CC_LD_DYNPATH)$(CURLDIR)/lib -lcurl
+               CURL_LIBCURL = -L$(CURLDIR)/$(lib) $(CC_LD_DYNPATH)$(CURLDIR)/$(lib) -lcurl
        else
                CURL_LIBCURL = -lcurl
        endif
@@ -530,11 +524,17 @@ ifndef NO_CURL
        endif
 endif
 
+ifdef ZLIB_PATH
+       BASIC_CFLAGS += -I$(ZLIB_PATH)/include
+       EXTLIBS += -L$(ZLIB_PATH)/$(lib) $(CC_LD_DYNPATH)$(ZLIB_PATH)/$(lib)
+endif
+EXTLIBS += -lz
+
 ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
                BASIC_CFLAGS += -I$(OPENSSLDIR)/include
-               OPENSSL_LINK = -L$(OPENSSLDIR)/lib $(CC_LD_DYNPATH)$(OPENSSLDIR)/lib
+               OPENSSL_LINK = -L$(OPENSSLDIR)/$(lib) $(CC_LD_DYNPATH)$(OPENSSLDIR)/$(lib)
        else
                OPENSSL_LINK =
        endif
@@ -551,7 +551,7 @@ endif
 ifdef NEEDS_LIBICONV
        ifdef ICONVDIR
                BASIC_CFLAGS += -I$(ICONVDIR)/include
-               ICONV_LINK = -L$(ICONVDIR)/lib $(CC_LD_DYNPATH)$(ICONVDIR)/lib
+               ICONV_LINK = -L$(ICONVDIR)/$(lib) $(CC_LD_DYNPATH)$(ICONVDIR)/$(lib)
        else
                ICONV_LINK =
        endif
@@ -708,7 +708,6 @@ prefix_SQ = $(subst ','\'',$(prefix))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
-PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
 TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
@@ -727,7 +726,7 @@ export TAR INSTALL DESTDIR SHELL_PATH
 
 all:: $(ALL_PROGRAMS) $(BUILT_INS) $(OTHER_PROGRAMS)
 ifneq (,$X)
-       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$p';)
+       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$p';)
 endif
 
 all::
@@ -741,29 +740,34 @@ strip: $(PROGRAMS) git$X
        $(STRIP) $(STRIP_OPTS) $(PROGRAMS) git$X
 
 gitk-wish: gitk GIT-GUI-VARS
-       $(QUIET_GEN)rm -f $@ $@+ && \
+       $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1,3s|^exec .* "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' <gitk >$@+ && \
        chmod +x $@+ && \
        mv -f $@+ $@
 
-git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
-       $(QUIET_LINK)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
-               $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \
+git.o: git.c common-cmds.h GIT-CFLAGS
+       $(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
+               $(ALL_CFLAGS) -c $(filter %.c,$^)
+
+git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
                $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS)
 
 help.o: common-cmds.h
 
 git-merge-subtree$X: git-merge-recursive$X
-       $(QUIET_BUILT_IN)rm -f $@ && ln git-merge-recursive$X $@
+       $(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@
 
 $(BUILT_INS): git$X
-       $(QUIET_BUILT_IN)rm -f $@ && ln git$X $@
+       $(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
+
+common-cmds.h: ./generate-cmdlist.sh
 
 common-cmds.h: $(wildcard Documentation/git-*.txt)
        $(QUIET_GEN)./generate-cmdlist.sh > $@+ && mv $@+ $@
 
 $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
-       $(QUIET_GEN)rm -f $@ $@+ && \
+       $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
@@ -774,20 +778,11 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
 
-$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py
-       rm -f $@ $@+
-       sed -e '1s|#!.*/python|#!$(PYTHON_PATH_SQ)|' \
-           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-           -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           $@.py >$@+
-       chmod +x $@+
-       mv $@+ $@
-
 perl/perl.mak: GIT-CFLAGS
        $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
-       $(QUIET_GEN)rm -f $@ $@+ && \
+       $(QUIET_GEN)$(RM) $@ $@+ && \
        INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
        sed -e '1{' \
            -e '        s|#!.*perl|#!$(PERL_PATH_SQ)|' \
@@ -806,7 +801,7 @@ git-status: git-commit
        $(QUIET_GEN)cp $< $@+ && mv $@+ $@
 
 gitweb/gitweb.cgi: gitweb/gitweb.perl
-       $(QUIET_GEN)rm -f $@ $@+ && \
+       $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \
            -e 's|++GIT_BINDIR++|$(bindir)|g' \
@@ -829,7 +824,7 @@ gitweb/gitweb.cgi: gitweb/gitweb.perl
        mv $@+ $@
 
 git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
-       $(QUIET_GEN)rm -f $@ $@+ && \
+       $(QUIET_GEN)$(RM) $@ $@+ && \
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
@@ -842,20 +837,22 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
        mv $@+ $@
 
 configure: configure.ac
-       $(QUIET_GEN)rm -f $@ $<+ && \
+       $(QUIET_GEN)$(RM) $@ $<+ && \
        sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            $< > $<+ && \
        autoconf -o $@ $<+ && \
-       rm -f $<+
+       $(RM) $<+
 
 # These can record GIT_VERSION
-git$X git.spec \
+git.o git.spec \
        $(patsubst %.sh,%,$(SCRIPT_SH)) \
        $(patsubst %.perl,%,$(SCRIPT_PERL)) \
        : GIT-VERSION-FILE
 
 %.o: %.c GIT-CFLAGS
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
+%.s: %.c GIT-CFLAGS
+       $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
 %.o: %.S
        $(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<
 
@@ -899,7 +896,7 @@ $(patsubst git-%$X,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
 $(DIFF_OBJS): diffcore.h
 
 $(LIB_FILE): $(LIB_OBJS)
-       $(QUIET_AR)rm -f $@ && $(AR) rcs $@ $(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
@@ -907,7 +904,7 @@ $(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
 
 $(XDIFF_LIB): $(XDIFF_OBJS)
-       $(QUIET_AR)rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+       $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(XDIFF_OBJS)
 
 
 perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
@@ -917,13 +914,16 @@ perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
 doc:
        $(MAKE) -C Documentation all
 
+info:
+       $(MAKE) -C Documentation info
+
 TAGS:
-       rm -f TAGS
-       find . -name '*.[hcS]' -print | xargs etags -a
+       $(RM) TAGS
+       $(FIND) . -name '*.[hcS]' -print | xargs etags -a
 
 tags:
-       rm -f tags
-       find . -name '*.[hcS]' -print | xargs ctags -a
+       $(RM) tags
+       $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
 
 ### Detect prefix changes
 TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
@@ -952,7 +952,7 @@ endif
 
 ### Testing rules
 
-TEST_PROGRAMS = test-chmtime$X test-genrandom$X
+TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X test-absolute-path$X
 
 all:: $(TEST_PROGRAMS)
 
@@ -965,26 +965,12 @@ export NO_SVN_TESTS
 test: all
        $(MAKE) -C t/ all
 
-test-date$X: test-date.c date.o ctype.o
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o
-
-test-delta$X: test-delta.o diff-delta.o patch-delta.o $(GITLIBS)
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+test-date$X: date.o ctype.o
 
-test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
+test-delta$X: diff-delta.o patch-delta.o
 
-test-sha1$X: test-sha1.o $(GITLIBS)
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-
-test-match-trees$X: test-match-trees.o $(GITLIBS)
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
-
-test-chmtime$X: test-chmtime.c
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
-
-test-genrandom$X: test-genrandom.c
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $<
+test-%$X: test-%.o $(GITLIBS)
+       $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
 check-sha1:: test-sha1$X
        ./test-sha1.sh
@@ -992,7 +978,8 @@ check-sha1:: test-sha1$X
 check: common-cmds.h
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
 
-
+remove-dashes:
+       ./fixup-builtins $(BUILT_INS)
 
 ### Installation rules
 
@@ -1014,14 +1001,17 @@ endif
                cp '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
                        '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X'; \
        fi
-       $(foreach p,$(BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
+       $(foreach p,$(BUILT_INS), $(RM) '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git$X' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
 ifneq (,$X)
-       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p';)
+       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), $(RM) '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p';)
 endif
 
 install-doc:
        $(MAKE) -C Documentation install
 
+install-info:
+       $(MAKE) -C Documentation install-info
+
 quick-install-doc:
        $(MAKE) -C Documentation quick-install
 
@@ -1034,18 +1024,19 @@ git.spec: git.spec.in
        mv $@+ $@
 
 GIT_TARNAME=git-$(GIT_VERSION)
-dist: git.spec git-archive
+dist: git.spec git-archive configure
        ./git-archive --format=tar \
                --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar
        @mkdir -p $(GIT_TARNAME)
-       @cp git.spec $(GIT_TARNAME)
+       @cp git.spec configure $(GIT_TARNAME)
        @echo $(GIT_VERSION) > $(GIT_TARNAME)/version
        @$(MAKE) -C git-gui TARDIR=../$(GIT_TARNAME)/git-gui dist-version
        $(TAR) rf $(GIT_TARNAME).tar \
                $(GIT_TARNAME)/git.spec \
+               $(GIT_TARNAME)/configure \
                $(GIT_TARNAME)/version \
                $(GIT_TARNAME)/git-gui/version
-       @rm -rf $(GIT_TARNAME)
+       @$(RM) -r $(GIT_TARNAME)
        gzip -f -9 $(GIT_TARNAME).tar
 
 rpm: dist
@@ -1054,13 +1045,13 @@ rpm: dist
 htmldocs = git-htmldocs-$(GIT_VERSION)
 manpages = git-manpages-$(GIT_VERSION)
 dist-doc:
-       rm -fr .doc-tmp-dir
+       $(RM) -r .doc-tmp-dir
        mkdir .doc-tmp-dir
        $(MAKE) -C Documentation WEBDOC_DEST=../.doc-tmp-dir install-webdoc
        cd .doc-tmp-dir && $(TAR) cf ../$(htmldocs).tar .
        gzip -n -9 -f $(htmldocs).tar
        :
-       rm -fr .doc-tmp-dir
+       $(RM) -r .doc-tmp-dir
        mkdir -p .doc-tmp-dir/man1 .doc-tmp-dir/man5 .doc-tmp-dir/man7
        $(MAKE) -C Documentation DESTDIR=./ \
                man1dir=../.doc-tmp-dir/man1 \
@@ -1069,30 +1060,31 @@ dist-doc:
                install
        cd .doc-tmp-dir && $(TAR) cf ../$(manpages).tar .
        gzip -n -9 -f $(manpages).tar
-       rm -fr .doc-tmp-dir
+       $(RM) -r .doc-tmp-dir
 
 ### Cleaning rules
 
 clean:
-       rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
-               test-chmtime$X test-genrandom$X $(LIB_FILE) $(XDIFF_LIB)
-       rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
-       rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
-       rm -rf autom4te.cache
-       rm -f configure config.log config.mak.autogen config.mak.append config.status config.cache
-       rm -rf $(GIT_TARNAME) .doc-tmp-dir
-       rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
-       rm -f $(htmldocs).tar.gz $(manpages).tar.gz
-       rm -f gitweb/gitweb.cgi
+       $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
+               $(LIB_FILE) $(XDIFF_LIB)
+       $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
+       $(RM) $(TEST_PROGRAMS)
+       $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
+       $(RM) -r autom4te.cache
+       $(RM) configure config.log config.mak.autogen config.mak.append config.status config.cache
+       $(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
        $(MAKE) -C perl clean
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
 ifndef NO_TCLTK
-       rm -f gitk-wish
+       $(RM) gitk-wish
        $(MAKE) -C git-gui clean
 endif
-       rm -f GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
+       $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
 
 .PHONY: all install clean strip
 .PHONY: .FORCE-GIT-VERSION-FILE TAGS tags .FORCE-GIT-CFLAGS
index 215243c7cdc7ac11ea7d7bf36df5deae086ab6ce..0de5e663a2ba03a15eb6711079c6bff0cba023cb 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.5.2.5.txt
\ No newline at end of file
+Documentation/RelNotes-1.5.3.txt
\ No newline at end of file
index 33e76576f149d89922deda1de5548be7bc42d0eb..66fe3e375b545613faba4e051dc41c1acb5d8cee 100644 (file)
@@ -167,7 +167,7 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path,
        } else {
                if (verbose)
                        fprintf(stderr, "%.*s\n", path->len, path->buf);
-               if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
+               if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
                        *header.typeflag = TYPEFLAG_DIR;
                        mode = (mode | 0777) & ~tar_umask;
                } else if (S_ISLNK(mode)) {
@@ -280,7 +280,7 @@ static int write_tar_entry(const unsigned char *sha1,
        memcpy(path.buf + baselen, filename, filenamelen);
        path.len = baselen + filenamelen;
        path.buf[path.len] = '\0';
-       if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
+       if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
                strbuf_append_string(&path, "/");
                buffer = NULL;
                size = 0;
index 3cbf6bb8ac4045803140ca5019126015e817155e..444e1623db66fe4f5d8983e1e9e2cf4083b005b4 100644 (file)
@@ -182,7 +182,7 @@ static int write_zip_entry(const unsigned char *sha1,
                goto out;
        }
 
-       if (S_ISDIR(mode) || S_ISDIRLNK(mode)) {
+       if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
                method = 0;
                attr2 = 16;
                result = (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0);
index 11b1a048b489fa2bb15e4b723e5fe36920f48eee..9e3ae038e818f4e21bc50f864fc5204f6fa44daa 100644 (file)
@@ -49,7 +49,7 @@ void SHA1_Update(SHA_CTX *c, const void *p, unsigned long n)
 void SHA1_Final(unsigned char *hash, SHA_CTX *c)
 {
        uint64_t bitlen;
-       uint32_t bitlen_hi, bitlen_lo; 
+       uint32_t bitlen_hi, bitlen_lo;
        unsigned int i, offset, padlen;
        unsigned char bits[8];
        static const unsigned char padding[64] = { 0x80, };
@@ -69,7 +69,7 @@ void SHA1_Final(unsigned char *hash, SHA_CTX *c)
        bits[5] = bitlen_lo >> 16;
        bits[6] = bitlen_lo >> 8;
        bits[7] = bitlen_lo;
-       SHA1_Update(c, bits, 8); 
+       SHA1_Update(c, bits, 8);
 
        for (i = 0; i < 5; i++) {
                uint32_t v = c->hash[i];
index a328b73375a318228e309419c524c8de0860ed89..8c1cb99fb403875af85e4d1524d21f7eb818f59b 100644 (file)
@@ -181,4 +181,3 @@ sha_transform:
 
 .L_sha_K:
        .word   0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6
-
diff --git a/attr.c b/attr.c
index a0712543b2076a90f38d36c5ed0e8efa2639e316..129399310ae061a66527ce0b723cc0aeb30ef34c 100644 (file)
--- a/attr.c
+++ b/attr.c
@@ -257,6 +257,7 @@ static struct attr_stack {
        struct attr_stack *prev;
        char *origin;
        unsigned num_matches;
+       unsigned alloc;
        struct match_attr **attrs;
 } *attr_stack;
 
@@ -287,6 +288,26 @@ static const char *builtin_attr[] = {
        NULL,
 };
 
+static void handle_attr_line(struct attr_stack *res,
+                            const char *line,
+                            const char *src,
+                            int lineno,
+                            int macro_ok)
+{
+       struct match_attr *a;
+
+       a = parse_attr_line(line, src, lineno, macro_ok);
+       if (!a)
+               return;
+       if (res->alloc <= res->num_matches) {
+               res->alloc = alloc_nr(res->num_matches);
+               res->attrs = xrealloc(res->attrs,
+                                     sizeof(struct match_attr *) *
+                                     res->alloc);
+       }
+       res->attrs[res->num_matches++] = a;
+}
+
 static struct attr_stack *read_attr_from_array(const char **list)
 {
        struct attr_stack *res;
@@ -294,42 +315,91 @@ static struct attr_stack *read_attr_from_array(const char **list)
        int lineno = 0;
 
        res = xcalloc(1, sizeof(*res));
-       while ((line = *(list++)) != NULL) {
-               struct match_attr *a;
-
-               a = parse_attr_line(line, "[builtin]", ++lineno, 1);
-               if (!a)
-                       continue;
-               res->attrs = xrealloc(res->attrs,
-                       sizeof(struct match_attr *) * (res->num_matches + 1));
-               res->attrs[res->num_matches++] = a;
-       }
+       while ((line = *(list++)) != NULL)
+               handle_attr_line(res, line, "[builtin]", ++lineno, 1);
        return res;
 }
 
 static struct attr_stack *read_attr_from_file(const char *path, int macro_ok)
 {
-       FILE *fp;
+       FILE *fp = fopen(path, "r");
        struct attr_stack *res;
        char buf[2048];
        int lineno = 0;
 
-       res = xcalloc(1, sizeof(*res));
-       fp = fopen(path, "r");
        if (!fp)
+               return NULL;
+       res = xcalloc(1, sizeof(*res));
+       while (fgets(buf, sizeof(buf), fp))
+               handle_attr_line(res, buf, path, ++lineno, macro_ok);
+       fclose(fp);
+       return res;
+}
+
+static void *read_index_data(const char *path)
+{
+       int pos, len;
+       unsigned long sz;
+       enum object_type type;
+       void *data;
+
+       len = strlen(path);
+       pos = cache_name_pos(path, len);
+       if (pos < 0) {
+               /*
+                * We might be in the middle of a merge, in which
+                * case we would read stage #2 (ours).
+                */
+               int i;
+               for (i = -pos - 1;
+                    (pos < 0 && i < active_nr &&
+                     !strcmp(active_cache[i]->name, path));
+                    i++)
+                       if (ce_stage(active_cache[i]) == 2)
+                               pos = i;
+       }
+       if (pos < 0)
+               return NULL;
+       data = read_sha1_file(active_cache[pos]->sha1, &type, &sz);
+       if (!data || type != OBJ_BLOB) {
+               free(data);
+               return NULL;
+       }
+       return data;
+}
+
+static struct attr_stack *read_attr(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;
 
-       while (fgets(buf, sizeof(buf), fp)) {
-               struct match_attr *a;
+       res = xcalloc(1, sizeof(*res));
 
-               a = parse_attr_line(buf, path, ++lineno, macro_ok);
-               if (!a)
-                       continue;
-               res->attrs = xrealloc(res->attrs,
-                       sizeof(struct match_attr *) * (res->num_matches + 1));
-               res->attrs[res->num_matches++] = a;
+       /*
+        * 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;
+
+       for (sp = buf; *sp; ) {
+               char *ep;
+               int more;
+               for (ep = sp; *ep && *ep != '\n'; ep++)
+                       ;
+               more = (*ep == '\n');
+               *ep = '\0';
+               handle_attr_line(res, sp, path, ++lineno, macro_ok);
+               sp = ep + more;
        }
-       fclose(fp);
+       free(buf);
        return res;
 }
 
@@ -370,13 +440,15 @@ static void bootstrap_attr_stack(void)
                elem->prev = attr_stack;
                attr_stack = elem;
 
-               elem = read_attr_from_file(GITATTRIBUTES_FILE, 1);
+               elem = read_attr(GITATTRIBUTES_FILE, 1);
                elem->origin = strdup("");
                elem->prev = attr_stack;
                attr_stack = elem;
                debug_push(elem);
 
                elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1);
+               if (!elem)
+                       elem = xcalloc(1, sizeof(*elem));
                elem->origin = NULL;
                elem->prev = attr_stack;
                attr_stack = elem;
@@ -441,7 +513,7 @@ static void prepare_attr_stack(const char *path, int dirlen)
                memcpy(pathbuf + dirlen, "/", 2);
                cp = strchr(pathbuf + len + 1, '/');
                strcpy(cp + 1, GITATTRIBUTES_FILE);
-               elem = read_attr_from_file(pathbuf, 0);
+               elem = read_attr(pathbuf, 0);
                *cp = '\0';
                elem->origin = strdup(pathbuf);
                elem->prev = attr_stack;
index 07e3ddfd0a49fd42206cf421924db17620fb690a..3dd4ded937e20d326737d17cceeebb1f3f744f07 100644 (file)
@@ -40,42 +40,29 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
        dir->nr = dst - dir->entries;
 
        for (i = 0; i < specs; i++) {
-               struct stat st;
-               const char *match;
-               if (seen[i])
-                       continue;
-
-               match = pathspec[i];
-               if (!match[0])
-                       continue;
-
-               /* Existing file? We must have ignored it */
-               if (!lstat(match, &st)) {
-                       struct dir_entry *ent;
-
-                       ent = dir_add_name(dir, match, strlen(match));
-                       ent->ignored = 1;
-                       if (S_ISDIR(st.st_mode))
-                               ent->ignored_dir = 1;
-                       continue;
-               }
-               die("pathspec '%s' did not match any files", match);
+               if (!seen[i] && !file_exists(pathspec[i]))
+                       die("pathspec '%s' did not match any files",
+                                       pathspec[i]);
        }
 }
 
-static void fill_directory(struct dir_struct *dir, const char **pathspec)
+static void fill_directory(struct dir_struct *dir, const char **pathspec,
+               int ignored_too)
 {
        const char *path, *base;
        int baselen;
 
        /* Set up the default git porcelain excludes */
        memset(dir, 0, sizeof(*dir));
-       dir->exclude_per_dir = ".gitignore";
-       path = git_path("info/exclude");
-       if (!access(path, R_OK))
-               add_excludes_from_file(dir, path);
-       if (!access(excludes_file, R_OK))
-               add_excludes_from_file(dir, excludes_file);
+       if (!ignored_too) {
+               dir->collect_ignored = 1;
+               dir->exclude_per_dir = ".gitignore";
+               path = git_path("info/exclude");
+               if (!access(path, R_OK))
+                       add_excludes_from_file(dir, path);
+               if (excludes_file != NULL && !access(excludes_file, R_OK))
+                       add_excludes_from_file(dir, excludes_file);
+       }
 
        /*
         * Calculate common prefix for the pathspec, and
@@ -137,6 +124,23 @@ static void update(int verbose, const char *prefix, const char **files)
        run_diff_files(&rev, 0);
 }
 
+static void refresh(int verbose, const char **pathspec)
+{
+       char *seen;
+       int i, specs;
+
+       for (specs = 0; pathspec[specs];  specs++)
+               /* nothing */;
+       seen = xcalloc(specs, 1);
+       if (read_cache() < 0)
+               die("index file corrupt");
+       refresh_index(&the_index, verbose ? 0 : REFRESH_QUIET, pathspec, seen);
+       for (i = 0; i < specs; i++) {
+               if (!seen[i])
+                       die("pathspec '%s' did not match any files", pathspec[i]);
+       }
+}
+
 static int git_add_config(const char *var, const char *value)
 {
        if (!strcmp(var, "core.excludesfile")) {
@@ -157,7 +161,7 @@ static const char ignore_warning[] =
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
        int i, newfd;
-       int verbose = 0, show_only = 0, ignored_too = 0;
+       int verbose = 0, show_only = 0, ignored_too = 0, refresh_only = 0;
        const char **pathspec;
        struct dir_struct dir;
        int add_interactive = 0;
@@ -205,6 +209,10 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        take_worktree_changes = 1;
                        continue;
                }
+               if (!strcmp(arg, "--refresh")) {
+                       refresh_only = 1;
+                       continue;
+               }
                usage(builtin_add_usage);
        }
 
@@ -220,13 +228,16 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        }
        pathspec = get_pathspec(prefix, argv + i);
 
-       fill_directory(&dir, pathspec);
+       if (refresh_only) {
+               refresh(verbose, pathspec);
+               goto finish;
+       }
+
+       fill_directory(&dir, pathspec, ignored_too);
 
        if (show_only) {
                const char *sep = "", *eof = "";
                for (i = 0; i < dir.nr; i++) {
-                       if (!ignored_too && dir.entries[i]->ignored)
-                               continue;
                        printf("%s%s", sep, dir.entries[i]->name);
                        sep = " ";
                        eof = "\n";
@@ -238,25 +249,13 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        if (read_cache() < 0)
                die("index file corrupt");
 
-       if (!ignored_too) {
-               int has_ignored = 0;
-               for (i = 0; i < dir.nr; i++)
-                       if (dir.entries[i]->ignored)
-                               has_ignored = 1;
-               if (has_ignored) {
-                       fprintf(stderr, ignore_warning);
-                       for (i = 0; i < dir.nr; i++) {
-                               if (!dir.entries[i]->ignored)
-                                       continue;
-                               fprintf(stderr, "%s", dir.entries[i]->name);
-                               if (dir.entries[i]->ignored_dir)
-                                       fprintf(stderr, " (directory)");
-                               fputc('\n', stderr);
-                       }
-                       fprintf(stderr,
-                               "Use -f if you really want to add them.\n");
-                       exit(1);
+       if (dir.ignored_nr) {
+               fprintf(stderr, ignore_warning);
+               for (i = 0; i < dir.ignored_nr; i++) {
+                       fprintf(stderr, "%s\n", dir.ignored[i]->name);
                }
+               fprintf(stderr, "Use -f if you really want to add them.\n");
+               exit(1);
        }
 
        for (i = 0; i < dir.nr; i++)
index 9db7cfe74c7a8583995e6c20c4eedebf58391fbc..fc43eed36b55e4966796490b8c0a02fae790229c 100644 (file)
@@ -22,4 +22,3 @@ int cmd_annotate(int argc, const char **argv, const char *prefix)
 
        return cmd_blame(argc + 1, nargv, prefix);
 }
-
index 9ee93936627b4df97ee5fea3af74ba0739becb98..da270755a7b9ecafa3cb47d9f6947c6dc5b2531c 100644 (file)
@@ -55,7 +55,7 @@ static enum whitespace_eol {
 } new_whitespace = warn_on_whitespace;
 static int whitespace_error;
 static int squelch_whitespace_errors = 5;
-static int applied_after_stripping;
+static int applied_after_fixing_ws;
 static const char *patch_input_file;
 
 static void parse_whitespace_option(const char *option)
@@ -1661,7 +1661,7 @@ static int apply_line(char *output, const char *patch, int plen)
        if (add_nl_to_tail)
                output[plen++] = '\n';
        if (fixed)
-               applied_after_stripping++;
+               applied_after_fixing_ws++;
        return output + plen - buf;
 }
 
@@ -1675,6 +1675,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
        char *new = xmalloc(size);
        const char *oldlines, *newlines;
        int oldsize = 0, newsize = 0;
+       int new_blank_lines_at_end = 0;
        unsigned long leading, trailing;
        int pos, lines;
 
@@ -1682,6 +1683,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
                char first;
                int len = linelen(patch, size);
                int plen;
+               int added_blank_line = 0;
 
                if (!len)
                        break;
@@ -1703,6 +1705,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
                        else if (first == '+')
                                first = '-';
                }
+
                switch (first) {
                case '\n':
                        /* Newer GNU diff, empty context line */
@@ -1720,9 +1723,14 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
                                break;
                /* Fall-through for ' ' */
                case '+':
-                       if (first != '+' || !no_add)
-                               newsize += apply_line(new + newsize, patch,
-                                                     plen);
+                       if (first != '+' || !no_add) {
+                               int added = apply_line(new + newsize, patch,
+                                                      plen);
+                               newsize += added;
+                               if (first == '+' &&
+                                   added == 1 && new[newsize-1] == '\n')
+                                       added_blank_line = 1;
+                       }
                        break;
                case '@': case '\\':
                        /* Ignore it, we already handled it */
@@ -1732,6 +1740,10 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
                                error("invalid start of line: '%c'", first);
                        return -1;
                }
+               if (added_blank_line)
+                       new_blank_lines_at_end++;
+               else
+                       new_blank_lines_at_end = 0;
                patch += len;
                size -= len;
        }
@@ -1774,9 +1786,16 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
                if (match_beginning && offset)
                        offset = -1;
                if (offset >= 0) {
-                       int diff = newsize - oldsize;
-                       unsigned long size = desc->size + diff;
-                       unsigned long alloc = desc->alloc;
+                       int diff;
+                       unsigned long size, alloc;
+
+                       if (new_whitespace == strip_whitespace &&
+                           (desc->size - oldsize - offset == 0)) /* end of file? */
+                               newsize -= new_blank_lines_at_end;
+
+                       diff = newsize - oldsize;
+                       size = desc->size + diff;
+                       alloc = desc->alloc;
 
                        /* Warn if it was necessary to reduce the number
                         * of context lines.
@@ -2869,18 +2888,17 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
                                squelched == 1 ? "" : "s");
                }
                if (new_whitespace == error_on_whitespace)
-                       die("%d line%s add%s trailing whitespaces.",
+                       die("%d line%s add%s whitespace errors.",
                            whitespace_error,
                            whitespace_error == 1 ? "" : "s",
                            whitespace_error == 1 ? "s" : "");
-               if (applied_after_stripping)
+               if (applied_after_fixing_ws)
                        fprintf(stderr, "warning: %d line%s applied after"
-                               " stripping trailing whitespaces.\n",
-                               applied_after_stripping,
-                               applied_after_stripping == 1 ? "" : "s");
+                               " fixing whitespace errors.\n",
+                               applied_after_fixing_ws,
+                               applied_after_fixing_ws == 1 ? "" : "s");
                else if (whitespace_error)
-                       fprintf(stderr, "warning: %d line%s add%s trailing"
-                               " whitespaces.\n",
+                       fprintf(stderr, "warning: %d line%s add%s whitespace errors.\n",
                                whitespace_error,
                                whitespace_error == 1 ? "" : "s",
                                whitespace_error == 1 ? "s" : "");
index 7f4e409c998ba4a864bd39032556d71693ff6793..187491bc172571b783a0be4f4dfa3d94d58bb0fe 100644 (file)
@@ -45,7 +45,7 @@ static int run_remote_archiver(const char *remote, int argc,
        }
 
        url = xstrdup(remote);
-       pid = git_connect(fd, url, exec);
+       pid = git_connect(fd, url, exec, 0);
        if (pid < 0)
                return pid;
 
index 35471fc2615992451c8c5b51a346fe171029b572..0519339098f3258ae6cc2b50d30679a8cfc7f2c7 100644 (file)
@@ -20,7 +20,7 @@
 #include "mailmap.h"
 
 static char blame_usage[] =
-"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
+"git-blame [-c] [-b] [-l] [--root] [-t] [-f] [-n] [-s] [-p] [-w] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
 "  -c                  Use the same output mode as git-annotate (Default: off)\n"
 "  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
 "  -l                  Show long commit SHA1 (Default: off)\n"
@@ -30,6 +30,7 @@ static char blame_usage[] =
 "  -n, --show-number   Show original linenumber (Default: off)\n"
 "  -s                  Suppress author name and timestamp (Default: off)\n"
 "  -p, --porcelain     Show in a format designed for machine consumption\n"
+"  -w                  Ignore whitespace differences\n"
 "  -L n,m              Process only line range n,m, counting from 1\n"
 "  -M, -C              Find line movements within and across files\n"
 "  --incremental       Show blame entries as we find them, incrementally\n"
@@ -45,6 +46,7 @@ static int show_root;
 static int blank_boundary;
 static int incremental;
 static int cmd_is_annotate;
+static int xdl_opts = XDF_NEED_MINIMAL;
 static struct path_list mailmap;
 
 #ifndef DEBUG
@@ -515,9 +517,9 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
        xdemitconf_t xecfg;
        xdemitcb_t ecb;
 
-       xpp.flags = XDF_NEED_MINIMAL;
+       xpp.flags = xdl_opts;
+       memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = context;
-       xecfg.flags = 0;
        ecb.outf = xdiff_outf;
        ecb.priv = &state;
        memset(&state, 0, sizeof(state));
@@ -1457,6 +1459,7 @@ static void found_guilty_entry(struct blame_entry *ent)
                                printf("boundary\n");
                }
                write_filename_info(suspect->path);
+               maybe_flush_or_die(stdout, "stdout");
        }
 }
 
@@ -1744,11 +1747,11 @@ static int read_ancestry(const char *graft_file)
  */
 static int lineno_width(int lines)
 {
-        int i, width;
+       int i, width;
 
-        for (width = 1, i = 10; i <= lines + 1; width++)
-                i *= 10;
-        return width;
+       for (width = 1, i = 10; i <= lines + 1; width++)
+               i *= 10;
+       return width;
 }
 
 /*
@@ -2159,6 +2162,8 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                        output_option |= OUTPUT_LONG_OBJECT_NAME;
                else if (!strcmp("-s", arg))
                        output_option |= OUTPUT_NO_AUTHOR;
+               else if (!strcmp("-w", arg))
+                       xdl_opts |= XDF_IGNORE_WHITESPACE;
                else if (!strcmp("-S", arg) && ++i < argc)
                        revs_file = argv[i];
                else if (!prefixcmp(arg, "-M")) {
index 94dba6e232343e9b8f1d6d24bb800880607495ed..5f5c1823cb27cf1173c87f43bd0d2de95a06bf46 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * Builtin "git branch"
  *
- * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
+ * Copyright (c) 2006 Kristian Høgsberg <krh@redhat.com>
  * Based on git-branch.sh by Junio C Hamano.
  */
 
@@ -10,6 +10,7 @@
 #include "refs.h"
 #include "commit.h"
 #include "builtin.h"
+#include "remote.h"
 
 static const char builtin_branch_usage[] =
   "git-branch [-r] (-d | -D) <branchname> | [--track | --no-track] [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length> | --no-abbrev]]";
@@ -22,7 +23,7 @@ static const char builtin_branch_usage[] =
 static const char *head;
 static unsigned char head_sha1[20];
 
-static int branch_track_remotes;
+static int branch_track = 1;
 
 static int branch_use_color;
 static char branch_colors[][COLOR_MAXLEN] = {
@@ -55,7 +56,7 @@ static int parse_branch_color_slot(const char *var, int ofs)
        die("bad config variable '%s'", var);
 }
 
-int git_branch_config(const char *var, const char *value)
+static int git_branch_config(const char *var, const char *value)
 {
        if (!strcmp(var, "color.branch")) {
                branch_use_color = git_config_colorbool(var, value);
@@ -67,12 +68,12 @@ int git_branch_config(const char *var, const char *value)
                return 0;
        }
        if (!strcmp(var, "branch.autosetupmerge"))
-               branch_track_remotes = git_config_bool(var, value);
+                       branch_track = git_config_bool(var, value);
 
        return git_default_config(var, value);
 }
 
-const char *branch_get_color(enum color_branch ix)
+static const char *branch_get_color(enum color_branch ix)
 {
        if (branch_use_color)
                return branch_colors[ix];
@@ -85,6 +86,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
        unsigned char sha1[20];
        char *name = NULL;
        const char *fmt, *remote;
+       char section[PATH_MAX];
        int i;
        int ret = 0;
 
@@ -152,9 +154,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                        error("Error deleting %sbranch '%s'", remote,
                               argv[i]);
                        ret = 1;
-               } else
+               } else {
                        printf("Deleted %sbranch %s.\n", remote, argv[i]);
-
+                       snprintf(section, sizeof(section), "branch.%s",
+                                argv[i]);
+                       if (git_config_rename_section(section, NULL) < 0)
+                               warning("Update of config-file failed");
+               }
        }
 
        if (name)
@@ -242,7 +248,6 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
        char c;
        int color;
        struct commit *commit;
-       char subject[256];
 
        switch (item->kind) {
        case REF_LOCAL_BRANCH:
@@ -263,17 +268,23 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
        }
 
        if (verbose) {
+               char *subject = NULL;
+               unsigned long subject_len = 0;
+               const char *sub = " **** invalid ref ****";
+
                commit = lookup_commit(item->sha1);
-               if (commit && !parse_commit(commit))
+               if (commit && !parse_commit(commit)) {
                        pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
-                                           subject, sizeof(subject), 0,
+                                           &subject, &subject_len, 0,
                                            NULL, NULL, 0);
-               else
-                       strcpy(subject, " **** invalid ref ****");
+                       sub = subject;
+               }
                printf("%c %s%-*s%s %s %s\n", c, branch_get_color(color),
                       maxwidth, item->name,
                       branch_get_color(COLOR_BRANCH_RESET),
-                      find_unique_abbrev(item->sha1, abbrev), subject);
+                      find_unique_abbrev(item->sha1, abbrev), sub);
+               if (subject)
+                       free(subject);
        } else {
                printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
                       branch_get_color(COLOR_BRANCH_RESET));
@@ -314,125 +325,70 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev)
        free_ref_list(&ref_list);
 }
 
-static char *config_repo;
-static char *config_remote;
-static const char *start_ref;
+struct tracking {
+       struct refspec spec;
+       char *src;
+       const char *remote;
+       int matches;
+};
 
-static int get_remote_branch_name(const char *value)
+static int find_tracked_branch(struct remote *remote, void *priv)
 {
-       const char *colon;
-       const char *end;
-
-       if (*value == '+')
-               value++;
-
-       colon = strchr(value, ':');
-       if (!colon)
-               return 0;
-
-       end = value + strlen(value);
-
-       /*
-        * Try an exact match first.  I.e. handle the case where the
-        * value is "$anything:refs/foo/bar/baz" and start_ref is exactly
-        * "refs/foo/bar/baz". Then the name at the remote is $anything.
-        */
-       if (!strcmp(colon + 1, start_ref)) {
-               /* Truncate the value before the colon. */
-               nfasprintf(&config_repo, "%.*s", colon - value, value);
-               return 1;
+       struct tracking *tracking = priv;
+
+       if (!remote_find_tracking(remote, &tracking->spec)) {
+               if (++tracking->matches == 1) {
+                       tracking->src = tracking->spec.src;
+                       tracking->remote = remote->name;
+               } else {
+                       free(tracking->spec.src);
+                       if (tracking->src) {
+                               free(tracking->src);
+                               tracking->src = NULL;
+                       }
+               }
+               tracking->spec.src = NULL;
        }
 
-       /*
-        * Is this a wildcard match?
-        */
-       if ((end - 2 <= value) || end[-2] != '/' || end[-1] != '*' ||
-           (colon - 2 <= value) || colon[-2] != '/' || colon[-1] != '*')
-               return 0;
-
-       /*
-        * Value is "refs/foo/bar/<asterisk>:refs/baz/boa/<asterisk>"
-        * and start_ref begins with "refs/baz/boa/"; the name at the
-        * remote is refs/foo/bar/ with the remaining part of the
-        * start_ref.  The length of the prefix on the RHS is (end -
-        * colon - 2), including the slash immediately before the
-        * asterisk.
-        */
-       if ((strlen(start_ref) < end - colon - 2) ||
-           memcmp(start_ref, colon + 1, end - colon - 2))
-               return 0; /* does not match prefix */
-
-       /* Replace the asterisk with the remote branch name.  */
-       nfasprintf(&config_repo, "%.*s%s",
-                  (colon - 1) - value, value,
-                  start_ref + (end - colon - 2));
-       return 1;
-}
-
-static int get_remote_config(const char *key, const char *value)
-{
-       const char *var;
-       if (prefixcmp(key, "remote."))
-               return 0;
-
-       var = strrchr(key, '.');
-       if (var == key + 6 || strcmp(var, ".fetch"))
-               return 0;
-       /*
-        * Ok, we are looking at key == "remote.$foo.fetch";
-        */
-       if (get_remote_branch_name(value))
-               nfasprintf(&config_remote, "%.*s", var - (key + 7), key + 7);
-
        return 0;
 }
 
-static void set_branch_merge(const char *name, const char *config_remote,
-                            const char *config_repo)
+
+/*
+ * 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
+ * config.
+ */
+static int setup_tracking(const char *new_ref, const char *orig_ref)
 {
        char key[1024];
-       if (sizeof(key) <=
-           snprintf(key, sizeof(key), "branch.%s.remote", name))
-               die("what a long branch name you have!");
-       git_config_set(key, config_remote);
-
-       /*
-        * We do not have to check if we have enough space for
-        * the 'merge' key, since it's shorter than the
-        * previous 'remote' key, which we already checked.
-        */
-       snprintf(key, sizeof(key), "branch.%s.merge", name);
-       git_config_set(key, config_repo);
-}
+       struct tracking tracking;
 
-static void set_branch_defaults(const char *name, const char *real_ref)
-{
-       /*
-        * name is the name of new branch under refs/heads;
-        * real_ref is typically refs/remotes/$foo/$bar, where
-        * $foo is the remote name (there typically are no slashes)
-        * and $bar is the branch name we map from the remote
-        * (it could have slashes).
-        */
-       start_ref = real_ref;
-       git_config(get_remote_config);
-       if (!config_repo && !config_remote &&
-           !prefixcmp(real_ref, "refs/heads/")) {
-               set_branch_merge(name, ".", real_ref);
-               printf("Branch %s set up to track local branch %s.\n",
-                      name, real_ref);
-       }
+       if (strlen(new_ref) > 1024 - 7 - 7 - 1)
+               return error("Tracking not set up: name too long: %s",
+                               new_ref);
 
-       if (config_repo && config_remote) {
-               set_branch_merge(name, config_remote, config_repo);
+       memset(&tracking, 0, sizeof(tracking));
+       tracking.spec.dst = (char *)orig_ref;
+       if (for_each_remote(find_tracked_branch, &tracking) ||
+                       !tracking.matches)
+               return 1;
+
+       if (tracking.matches > 1)
+               return error("Not tracking: ambiguous information for ref %s",
+                               orig_ref);
+
+       if (tracking.matches == 1) {
+               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);
+               free(tracking.src);
                printf("Branch %s set up to track remote branch %s.\n",
-                      name, real_ref);
+                              new_ref, orig_ref);
        }
 
-       if (config_repo)
-               free(config_repo);
-       if (config_remote)
-               free(config_remote);
+       return 0;
 }
 
 static void create_branch(const char *name, const char *start_name,
@@ -477,7 +433,7 @@ static void create_branch(const char *name, const char *start_name,
                die("Not a valid branch point: '%s'.", start_name);
        hashcpy(sha1, commit->object.sha1);
 
-       lock = lock_any_ref_for_update(ref, NULL);
+       lock = lock_any_ref_for_update(ref, NULL, 0);
        if (!lock)
                die("Failed to lock ref for update: %s.", strerror(errno));
 
@@ -495,7 +451,7 @@ static void create_branch(const char *name, const char *start_name,
           automatically merges from there.  So far, this is only done for
           remotes registered via .git/config.  */
        if (real_ref && track)
-               set_branch_defaults(name, real_ref);
+               setup_tracking(name, real_ref);
 
        if (write_ref_sha1(lock, sha1, msg) < 0)
                die("Failed to write ref: %s.", strerror(errno));
@@ -554,7 +510,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
        int i;
 
        git_config(git_branch_config);
-       track = branch_track_remotes;
+       track = branch_track;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
index 306ad29597dbf9002a44ba509c2e9d7a737b159d..1b650069c929744c43f95e62ca49f8a542a70111 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "object.h"
 #include "commit.h"
@@ -43,38 +44,21 @@ struct bundle_header {
        struct ref_list references;
 };
 
-/* this function returns the length of the string */
-static int read_string(int fd, char *buffer, int size)
-{
-       int i;
-       for (i = 0; i < size - 1; i++) {
-               ssize_t count = xread(fd, buffer + i, 1);
-               if (count < 0)
-                       return error("Read error: %s", strerror(errno));
-               if (count == 0) {
-                       i--;
-                       break;
-               }
-               if (buffer[i] == '\n')
-                       break;
-       }
-       buffer[i + 1] = '\0';
-       return i + 1;
-}
-
 /* returns an fd */
 static int read_header(const char *path, struct bundle_header *header) {
        char buffer[1024];
-       int fd = open(path, O_RDONLY);
+       int fd;
+       long fpos;
+       FILE *ffd = fopen(path, "rb");
 
-       if (fd < 0)
+       if (!ffd)
                return error("could not open '%s'", path);
-       if (read_string(fd, buffer, sizeof(buffer)) < 0 ||
+       if (!fgets(buffer, sizeof(buffer), ffd) ||
                        strcmp(buffer, bundle_signature)) {
-               close(fd);
+               fclose(ffd);
                return error("'%s' does not look like a v2 bundle file", path);
        }
-       while (read_string(fd, buffer, sizeof(buffer)) > 0
+       while (fgets(buffer, sizeof(buffer), ffd)
                        && buffer[0] != '\n') {
                int is_prereq = buffer[0] == '-';
                int offset = is_prereq ? 1 : 0;
@@ -96,6 +80,12 @@ static int read_header(const char *path, struct bundle_header *header) {
                add_to_ref_list(sha1, isspace(delim) ?
                                buffer + 41 + offset : "", list);
        }
+       fpos = ftell(ffd);
+       fclose(ffd);
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               return error("could not open '%s'", path);
+       lseek(fd, fpos, SEEK_SET);
        return fd;
 }
 
@@ -199,18 +189,22 @@ static int list_heads(struct bundle_header *header, int argc, const char **argv)
 static int create_bundle(struct bundle_header *header, const char *path,
                int argc, const char **argv)
 {
+       static struct lock_file lock;
        int bundle_fd = -1;
+       int bundle_to_stdout;
        const char **argv_boundary = xmalloc((argc + 4) * sizeof(const char *));
        const char **argv_pack = xmalloc(5 * sizeof(const char *));
        int i, ref_count = 0;
        char buffer[1024];
        struct rev_info revs;
        struct child_process rls;
+       FILE *rls_fout;
 
-       bundle_fd = (!strcmp(path, "-") ? 1 :
-                       open(path, O_CREAT | O_EXCL | O_WRONLY, 0666));
-       if (bundle_fd < 0)
-               return error("Could not create '%s': %s", path, strerror(errno));
+       bundle_to_stdout = !strcmp(path, "-");
+       if (bundle_to_stdout)
+               bundle_fd = 1;
+       else
+               bundle_fd = hold_lock_file_for_update(&lock, path, 1);
 
        /* write signature */
        write_or_die(bundle_fd, bundle_signature, strlen(bundle_signature));
@@ -231,10 +225,11 @@ static int create_bundle(struct bundle_header *header, const char *path,
        rls.git_cmd = 1;
        if (start_command(&rls))
                return -1;
-       while ((i = read_string(rls.out, buffer, sizeof(buffer))) > 0) {
+       rls_fout = fdopen(rls.out, "r");
+       while (fgets(buffer, sizeof(buffer), rls_fout)) {
                unsigned char sha1[20];
                if (buffer[0] == '-') {
-                       write_or_die(bundle_fd, buffer, i);
+                       write_or_die(bundle_fd, buffer, strlen(buffer));
                        if (!get_sha1_hex(buffer + 1, sha1)) {
                                struct object *object = parse_object(sha1);
                                object->flags |= UNINTERESTING;
@@ -245,6 +240,7 @@ static int create_bundle(struct bundle_header *header, const char *path,
                        object->flags |= SHOWN;
                }
        }
+       fclose(rls_fout);
        if (finish_command(&rls))
                return error("rev-list died");
 
@@ -266,12 +262,49 @@ static int create_bundle(struct bundle_header *header, const char *path,
                 * Make sure the refs we wrote out is correct; --max-count and
                 * other limiting options could have prevented all the tips
                 * from getting output.
+                *
+                * Non commit objects such as tags and blobs do not have
+                * this issue as they are not affected by those extra
+                * constraints.
                 */
-               if (!(e->item->flags & SHOWN)) {
+               if (!(e->item->flags & SHOWN) && e->item->type == OBJ_COMMIT) {
                        warning("ref '%s' is excluded by the rev-list options",
                                e->name);
+                       free(ref);
+                       continue;
+               }
+               /*
+                * If you run "git bundle create bndl v1.0..v2.0", the
+                * name of the positive ref is "v2.0" but that is the
+                * commit that is referenced by the tag, and not the tag
+                * itself.
+                */
+               if (hashcmp(sha1, e->item->sha1)) {
+                       /*
+                        * Is this the positive end of a range expressed
+                        * in terms of a tag (e.g. v2.0 from the range
+                        * "v1.0..v2.0")?
+                        */
+                       struct commit *one = lookup_commit_reference(sha1);
+                       struct object *obj;
+
+                       if (e->item == &(one->object)) {
+                               /*
+                                * Need to include e->name as an
+                                * independent ref to the pack-objects
+                                * input, so that the tag is included
+                                * in the output; otherwise we would
+                                * end up triggering "empty bundle"
+                                * error.
+                                */
+                               obj = parse_object(sha1);
+                               obj->flags |= SHOWN;
+                               add_pending_object(&revs, obj, e->name);
+                       }
+                       free(ref);
                        continue;
                }
+
                ref_count++;
                write_or_die(bundle_fd, sha1_to_hex(e->item->sha1), 40);
                write_or_die(bundle_fd, " ", 1);
@@ -307,6 +340,9 @@ static int create_bundle(struct bundle_header *header, const char *path,
        }
        if (finish_command(&rls))
                return error ("pack-objects died");
+       close(bundle_fd);
+       if (!bundle_to_stdout)
+               commit_lock_file(&lock);
        return 0;
 }
 
index 9d77f76ff1230df76278b528ef44d6837edb2741..d94973379cee27c47426b61a13ae0f90508fed9b 100644 (file)
@@ -1,4 +1,5 @@
 #include "builtin.h"
+#include "cache.h"
 #include "attr.h"
 #include "quote.h"
 
@@ -10,6 +11,10 @@ int cmd_check_attr(int argc, const char **argv, const char *prefix)
        struct git_attr_check *check;
        int cnt, i, doubledash;
 
+       if (read_cache() < 0) {
+               die("invalid cache");
+       }
+
        doubledash = -1;
        for (i = 1; doubledash < 0 && i < argc; i++) {
                if (!strcmp(argv[i], "--"))
index 8460f97b6637127d78b58caf2e29d25f3ad0b5a0..75377b9cab75ac75c6d5d7f79fdcf64bdb27cff2 100644 (file)
@@ -36,6 +36,7 @@
  * of "-a" causing problems (not possible in the above example,
  * but get used to it in scripting!).
  */
+#include "builtin.h"
 #include "cache.h"
 #include "strbuf.h"
 #include "quote.h"
index dbc2339d0f98d561e0263354321a5f2566f9918a..0a605e01aca6e1ab91fcfecd3929b8a853ff9f3d 100644 (file)
@@ -2,7 +2,7 @@
 #include "cache.h"
 
 static const char git_config_set_usage[] =
-"git-config [ --global | --system ] [ --bool | --int ] [--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";
+"git-config [ --global | --system | [ -f | --file ] config-file ] [ --bool | --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";
 
 static char *key;
 static regex_t *key_regexp;
@@ -12,14 +12,17 @@ static int use_key_regexp;
 static int do_all;
 static int do_not_match;
 static int seen;
+static char delim = '=';
+static char key_delim = ' ';
+static char term = '\n';
 static enum { T_RAW, T_INT, T_BOOL } type = T_RAW;
 
 static int show_all_config(const char *key_, const char *value_)
 {
        if (value_)
-               printf("%s=%s\n", key_, value_);
+               printf("%s%c%s%c", key_, delim, value_, term);
        else
-               printf("%s\n", key_);
+               printf("%s%c", key_, term);
        return 0;
 }
 
@@ -40,7 +43,7 @@ static int show_config(const char* key_, const char* value_)
 
        if (show_keys) {
                if (value_)
-                       printf("%s ", key_);
+                       printf("%s%c", key_, key_delim);
                else
                        printf("%s", key_);
        }
@@ -58,7 +61,7 @@ static int show_config(const char* key_, const char* value_)
                                key_, vptr);
        }
        else
-               printf("%s\n", vptr);
+               printf("%s%c", vptr, term);
 
        return 0;
 }
@@ -135,9 +138,33 @@ free_strings:
        return ret;
 }
 
+char *normalize_value(const char *key, const char *value)
+{
+       char *normalized;
+
+       if (!value)
+               return NULL;
+
+       if (type == T_RAW)
+               normalized = xstrdup(value);
+       else {
+               normalized = xmalloc(64);
+               if (type == T_INT) {
+                       int v = git_config_int(key, value);
+                       sprintf(normalized, "%d", v);
+               }
+               else if (type == T_BOOL)
+                       sprintf(normalized, "%s",
+                               git_config_bool(key, value) ? "true" : "false");
+       }
+
+       return normalized;
+}
+
 int cmd_config(int argc, const char **argv, const char *prefix)
 {
        int nongit = 0;
+       char* value;
        setup_git_directory_gently(&nongit);
 
        while (1 < argc) {
@@ -151,14 +178,26 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                        char *home = getenv("HOME");
                        if (home) {
                                char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
-                               setenv("GIT_CONFIG", user_config, 1);
+                               setenv(CONFIG_ENVIRONMENT, user_config, 1);
                                free(user_config);
                        } else {
                                die("$HOME not set");
                        }
                }
                else if (!strcmp(argv[1], "--system"))
-                       setenv("GIT_CONFIG", ETC_GITCONFIG, 1);
+                       setenv(CONFIG_ENVIRONMENT, ETC_GITCONFIG, 1);
+               else if (!strcmp(argv[1], "--file") || !strcmp(argv[1], "-f")) {
+                       if (argc < 3)
+                               usage(git_config_set_usage);
+                       setenv(CONFIG_ENVIRONMENT, argv[2], 1);
+                       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)
@@ -209,9 +248,10 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                        use_key_regexp = 1;
                        do_all = 1;
                        return get_value(argv[2], NULL);
-               } else
-
-                       return git_config_set(argv[1], argv[2]);
+               } else {
+                       value = normalize_value(argv[1], argv[2]);
+                       return git_config_set(argv[1], value);
+               }
        case 4:
                if (!strcmp(argv[1], "--unset"))
                        return git_config_set_multivar(argv[2], NULL, argv[3], 0);
@@ -227,17 +267,21 @@ int cmd_config(int argc, const char **argv, const char *prefix)
                        use_key_regexp = 1;
                        do_all = 1;
                        return get_value(argv[2], argv[3]);
-               } else if (!strcmp(argv[1], "--add"))
-                       return git_config_set_multivar(argv[2], argv[3], "^$", 0);
-               else if (!strcmp(argv[1], "--replace-all"))
-
-                       return git_config_set_multivar(argv[2], argv[3], NULL, 1);
-               else
-
-                       return git_config_set_multivar(argv[1], argv[2], argv[3], 0);
+               } 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);
+               }
        case 5:
-               if (!strcmp(argv[1], "--replace-all"))
-                       return git_config_set_multivar(argv[2], argv[3], argv[4], 1);
+               if (!strcmp(argv[1], "--replace-all")) {
+                       value = normalize_value(argv[2], argv[3]);
+                       return git_config_set_multivar(argv[2], value, argv[4], 1);
+               }
        case 1:
        default:
                usage(git_config_set_usage);
index ff90ebd465002882781507ecfcfc33cab2f759fc..4274ec19500953bd7b6775e6d66271e9e116fa86 100644 (file)
@@ -111,6 +111,8 @@ int cmd_count_objects(int ac, const char **av, const char *prefix)
                for (p = packed_git; p; p = p->next) {
                        if (!p->pack_local)
                                continue;
+                       if (open_pack_index(p))
+                               continue;
                        packed += p->num_objects;
                        num_pack++;
                }
index 165917e40db78e7105a16d6e7a1653862a29fdc5..669110cb0645629ca5b152d8328aa91d63be1550 100644 (file)
@@ -3,6 +3,7 @@
 #include "tag.h"
 #include "refs.h"
 #include "builtin.h"
+#include "exec_cmd.h"
 
 #define SEEN           (1u<<0)
 #define MAX_TAGS       (FLAG_BITS - 1)
@@ -242,12 +243,15 @@ static void describe(const char *arg, int last_one)
 int cmd_describe(int argc, const char **argv, const char *prefix)
 {
        int i;
+       int contains = 0;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
                if (*arg != '-')
                        break;
+               else if (!strcmp(arg, "--contains"))
+                       contains = 1;
                else if (!strcmp(arg, "--debug"))
                        debug = 1;
                else if (!strcmp(arg, "--all"))
@@ -272,6 +276,16 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
 
        save_commit_buffer = 0;
 
+       if (contains) {
+               const char **args = xmalloc((4 + argc - i) * sizeof(char*));
+               args[0] = "name-rev";
+               args[1] = "--name-only";
+               args[2] = "--tags";
+               memcpy(args + 3, argv + i, (argc - i) * sizeof(char*));
+               args[3 + argc - i] = NULL;
+               return cmd_name_rev(3 + argc - i, args, prefix);
+       }
+
        if (argc <= i)
                describe("HEAD", 1);
        else
index d90eba95a6be17bd0486e0311c2657b1f69ab7d9..81e7167438ecfc25a6f9a317af663034d653588e 100644 (file)
@@ -23,7 +23,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix)
        argc = setup_revisions(argc, argv, &rev, NULL);
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-                       
+
                if (!strcmp(arg, "--cached"))
                        cached = 1;
                else
index 7f367b6b9d545ea760224fdacb68056261ce1617..6ed7b6842ec533902427f2d47790d57aa5082365 100644 (file)
@@ -222,6 +222,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
        prefix = setup_git_directory_gently(&nongit);
        git_config(git_diff_ui_config);
        init_revisions(&rev, prefix);
+       rev.diffopt.skip_stat_unmatch = 1;
 
        if (!setup_diff_no_index(&rev, argc, argv, nongit, prefix))
                argc = 0;
@@ -233,6 +234,13 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                        die("diff_setup_done failed");
        }
        rev.diffopt.allow_external = 1;
+       rev.diffopt.recursive = 1;
+
+       /* If the user asked for our exit code then don't start a
+        * pager or we would end up reporting its exit code instead.
+        */
+       if (!rev.diffopt.exit_with_status)
+               setup_pager();
 
        /* Do we have --cached and not have a pending object, then
         * default to HEAD by hand.  Eek.
@@ -337,5 +345,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
                                             ent, ents);
        if (rev.diffopt.exit_with_status)
                result = rev.diffopt.has_changes;
+
+       if ((rev.diffopt.output_format & DIFF_FORMAT_PATCH)
+           && (1 < rev.diffopt.skip_stat_unmatch))
+               printf("Warning: %d path%s touched but unmodified. "
+                      "Consider running git-status.\n",
+                      rev.diffopt.skip_stat_unmatch - 1,
+                      rev.diffopt.skip_stat_unmatch == 2 ? "" : "s");
        return result;
 }
index 12adb3833cd60771f8e63b2ebaecff3769fa74e2..e2f8ede9ae4507ed1e431f9d14fc649f6475627d 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "refs.h"
 #include "commit.h"
@@ -42,7 +43,7 @@ static int update_ref(const char *action,
        if (!rla)
                rla = "(reflog update)";
        snprintf(msg, sizeof(msg), "%s: %s", rla, action);
-       lock = lock_any_ref_for_update(refname, oldval);
+       lock = lock_any_ref_for_update(refname, oldval, 0);
        if (!lock)
                return 1;
        if (write_ref_sha1(lock, sha1, msg) < 0)
index 5c145d2165027adc5e5d44c6dda969e1ed86be57..ae60fccea74077b4d2456919d2f911f8a257c5b4 100644 (file)
@@ -357,4 +357,3 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)
 
        return 0;
 }
-
index 2b218425aab2c37a826bfbe22535f2e5b7002a26..0afa1c5c41e79a05ddebb7d874956163510daf0b 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "refs.h"
 #include "object.h"
@@ -796,7 +797,7 @@ static struct ref_sort *default_sort(void)
        return sort;
 }
 
-int cmd_for_each_ref(int ac, const char **av, char *prefix)
+int cmd_for_each_ref(int ac, const char **av, const char *prefix)
 {
        int i, num_refs;
        const char *format = NULL;
index 44ce629a498f986f239e008a61d32e10968907e0..8d12287f037c499acad26ea81acab73490c38d5c 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "commit.h"
 #include "tree.h"
@@ -20,6 +21,8 @@ static int check_strict;
 static int keep_cache_objects;
 static unsigned char head_sha1[20];
 static int errors_found;
+static int write_lost_and_found;
+static int verbose;
 #define ERROR_OBJECT 01
 #define ERROR_REACHABLE 02
 
@@ -137,6 +140,31 @@ static void check_unreachable_object(struct object *obj)
        if (!obj->used) {
                printf("dangling %s %s\n", typename(obj->type),
                       sha1_to_hex(obj->sha1));
+               if (write_lost_and_found) {
+                       char *filename = git_path("lost-found/%s/%s",
+                               obj->type == OBJ_COMMIT ? "commit" : "other",
+                               sha1_to_hex(obj->sha1));
+                       FILE *f;
+
+                       if (safe_create_leading_directories(filename)) {
+                               error("Could not create lost-found");
+                               return;
+                       }
+                       if (!(f = fopen(filename, "w")))
+                               die("Could not open %s", filename);
+                       if (obj->type == OBJ_BLOB) {
+                               enum object_type type;
+                               unsigned long size;
+                               char *buf = read_sha1_file(obj->sha1,
+                                               &type, &size);
+                               if (buf) {
+                                       fwrite(buf, size, 1, f);
+                                       free(buf);
+                               }
+                       } else
+                               fprintf(f, "%s\n", sha1_to_hex(obj->sha1));
+                       fclose(f);
+               }
                return;
        }
 
@@ -149,6 +177,9 @@ static void check_unreachable_object(struct object *obj)
 
 static void check_object(struct object *obj)
 {
+       if (verbose)
+               fprintf(stderr, "Checking %s\n", sha1_to_hex(obj->sha1));
+
        if (obj->flags & REACHABLE)
                check_reachable_object(obj);
        else
@@ -161,6 +192,9 @@ static void check_connectivity(void)
 
        /* Look up all the requirements, warn about missing objects.. */
        max = get_max_object_index();
+       if (verbose)
+               fprintf(stderr, "Checking connectivity (%d objects)\n", max);
+
        for (i = 0; i < max; i++) {
                struct object *obj = get_indexed_object(i);
 
@@ -229,6 +263,10 @@ static int fsck_tree(struct tree *item)
        const char *o_name;
        const unsigned char *o_sha1;
 
+       if (verbose)
+               fprintf(stderr, "Checking tree %s\n",
+                               sha1_to_hex(item->object.sha1));
+
        init_tree_desc(&desc, item->buffer, item->size);
 
        o_mode = 0;
@@ -256,7 +294,7 @@ static int fsck_tree(struct tree *item)
                case S_IFREG | 0644:
                case S_IFLNK:
                case S_IFDIR:
-               case S_IFDIRLNK:
+               case S_IFGITLINK:
                        break;
                /*
                 * This is nonstandard, but we had a few of these
@@ -317,6 +355,10 @@ static int fsck_commit(struct commit *commit)
        char *buffer = commit->buffer;
        unsigned char tree_sha1[20], sha1[20];
 
+       if (verbose)
+               fprintf(stderr, "Checking commit %s\n",
+                       sha1_to_hex(commit->object.sha1));
+
        if (memcmp(buffer, "tree ", 5))
                return objerror(&commit->object, "invalid format - expected 'tree' line");
        if (get_sha1_hex(buffer+5, tree_sha1) || buffer[45] != '\n')
@@ -336,7 +378,7 @@ static int fsck_commit(struct commit *commit)
        if (!commit->parents && show_root)
                printf("root %s\n", sha1_to_hex(commit->object.sha1));
        if (!commit->date)
-               printf("bad commit date in %s\n", 
+               printf("bad commit date in %s\n",
                       sha1_to_hex(commit->object.sha1));
        return 0;
 }
@@ -345,6 +387,10 @@ static int fsck_tag(struct tag *tag)
 {
        struct object *tagged = tag->tagged;
 
+       if (verbose)
+               fprintf(stderr, "Checking tag %s\n",
+                       sha1_to_hex(tag->object.sha1));
+
        if (!tagged) {
                return objerror(&tag->object, "could not load tagged object");
        }
@@ -446,6 +492,9 @@ static void fsck_dir(int i, char *path)
        if (!dir)
                return;
 
+       if (verbose)
+               fprintf(stderr, "Checking directory %s\n", path);
+
        while ((de = readdir(dir)) != NULL) {
                char name[100];
                unsigned char sha1[20];
@@ -480,6 +529,10 @@ static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
 {
        struct object *obj;
 
+       if (verbose)
+               fprintf(stderr, "Checking reflog %s->%s\n",
+                       sha1_to_hex(osha1), sha1_to_hex(nsha1));
+
        if (!is_null_sha1(osha1)) {
                obj = lookup_object(osha1);
                if (obj) {
@@ -549,6 +602,10 @@ static void get_default_heads(void)
 static void fsck_object_dir(const char *path)
 {
        int i;
+
+       if (verbose)
+               fprintf(stderr, "Checking object directory\n");
+
        for (i = 0; i < 256; i++) {
                static char dir[4096];
                sprintf(dir, "%s/%02x", path, i);
@@ -564,6 +621,9 @@ static int fsck_head_link(void)
        int null_is_error = 0;
        const char *head_points_at = resolve_ref("HEAD", sha1, 0, &flag);
 
+       if (verbose)
+               fprintf(stderr, "Checking HEAD link\n");
+
        if (!head_points_at)
                return error("Invalid HEAD");
        if (!strcmp(head_points_at, "HEAD"))
@@ -586,6 +646,9 @@ static int fsck_cache_tree(struct cache_tree *it)
        int i;
        int err = 0;
 
+       if (verbose)
+               fprintf(stderr, "Checking cache tree\n");
+
        if (0 <= it->entry_count) {
                struct object *obj = parse_object(it->sha1);
                if (!obj) {
@@ -605,9 +668,9 @@ static int fsck_cache_tree(struct cache_tree *it)
 
 static const char fsck_usage[] =
 "git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] "
-"[--strict] <head-sha1>*]";
+"[--strict] [--verbose] <head-sha1>*]";
 
-int cmd_fsck(int argc, char **argv, const char *prefix)
+int cmd_fsck(int argc, const char **argv, const char *prefix)
 {
        int i, heads;
 
@@ -645,6 +708,16 @@ int cmd_fsck(int argc, char **argv, const char *prefix)
                        check_strict = 1;
                        continue;
                }
+               if (!strcmp(arg, "--verbose")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--lost-found")) {
+                       check_full = 1;
+                       include_reflogs = 0;
+                       write_lost_and_found = 1;
+                       continue;
+               }
                if (*arg == '-')
                        usage(fsck_usage);
        }
@@ -668,7 +741,10 @@ int cmd_fsck(int argc, char **argv, const char *prefix)
                        verify_pack(p, 0);
 
                for (p = packed_git; p; p = p->next) {
-                       uint32_t i, num = p->num_objects;
+                       uint32_t i, num;
+                       if (open_pack_index(p))
+                               continue;
+                       num = p->num_objects;
                        for (i = 0; i < num; i++)
                                fsck_sha1(nth_packed_object_sha1(p, i));
                }
@@ -676,7 +752,7 @@ int cmd_fsck(int argc, char **argv, const char *prefix)
 
        heads = 0;
        for (i = 1; i < argc; i++) {
-               const char *arg = argv[i]; 
+               const char *arg = argv[i];
 
                if (*arg == '-')
                        continue;
@@ -715,7 +791,7 @@ int cmd_fsck(int argc, char **argv, const char *prefix)
                        struct object *obj;
 
                        mode = ntohl(active_cache[i]->ce_mode);
-                       if (S_ISDIRLNK(mode))
+                       if (S_ISGITLINK(mode))
                                continue;
                        blob = lookup_blob(active_cache[i]->sha1);
                        if (!blob)
index 3b1f8c2f3e6d352b43c671a409898c43cf1119ec..939748261041049f31d62935ec08f062bdfa6e79 100644 (file)
  * Copyright (c) 2006 Shawn O. Pearce
  */
 
+#include "builtin.h"
 #include "cache.h"
 #include "run-command.h"
 
 #define FAILED_RUN "failed to run %s"
 
-static const char builtin_gc_usage[] = "git-gc [--prune]";
+static const char builtin_gc_usage[] = "git-gc [--prune] [--aggressive]";
 
-static int pack_refs = -1;
+static int pack_refs = 1;
+static int aggressive_window = -1;
 
-static const char *argv_pack_refs[] = {"pack-refs", "--prune", NULL};
+#define MAX_ADD 10
+static const char *argv_pack_refs[] = {"pack-refs", "--all", "--prune", NULL};
 static const char *argv_reflog[] = {"reflog", "expire", "--all", NULL};
-static const char *argv_repack[] = {"repack", "-a", "-d", "-l", NULL};
+static const char *argv_repack[MAX_ADD] = {"repack", "-a", "-d", "-l", NULL};
 static const char *argv_prune[] = {"prune", NULL};
 static const char *argv_rerere[] = {"rerere", "gc", NULL};
 
@@ -34,13 +37,31 @@ static int gc_config(const char *var, const char *value)
                        pack_refs = git_config_bool(var, value);
                return 0;
        }
+       if (!strcmp(var, "gc.aggressivewindow")) {
+               aggressive_window = git_config_int(var, value);
+               return 0;
+       }
        return git_default_config(var, value);
 }
 
+static void append_option(const char **cmd, const char *opt, int max_length)
+{
+       int i;
+
+       for (i = 0; cmd[i]; i++)
+               ;
+
+       if (i + 2 >= max_length)
+               die("Too many options specified");
+       cmd[i++] = opt;
+       cmd[i] = NULL;
+}
+
 int cmd_gc(int argc, const char **argv, const char *prefix)
 {
        int i;
        int prune = 0;
+       char buf[80];
 
        git_config(gc_config);
 
@@ -53,6 +74,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
                        prune = 1;
                        continue;
                }
+               if (!strcmp(arg, "--aggressive")) {
+                       append_option(argv_repack, "-f", MAX_ADD);
+                       if (aggressive_window > 0) {
+                               sprintf(buf, "--window=%d", aggressive_window);
+                               append_option(argv_repack, buf, MAX_ADD);
+                       }
+                       continue;
+               }
                /* perhaps other parameters later... */
                break;
        }
index 4df9fd0fad7bbc3fd0cde1e6e57b902fbd277608..0d9b1e0559d04ebda464d2975aaf196378eeb481 100644 (file)
@@ -7,7 +7,7 @@
 #include "builtin.h"
 
 #ifndef DEFAULT_GIT_TEMPLATE_DIR
-#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/"
+#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
 #endif
 
 #ifdef NO_TRUSTABLE_FILEMODE
@@ -40,7 +40,8 @@ static int copy_file(const char *dst, const char *src, int mode)
                return fdo;
        }
        status = copy_fd(fdi, fdo);
-       close(fdo);
+       if (close(fdo) != 0)
+               return error("%s: write error: %s", dst, strerror(errno));
 
        if (!status && adjust_shared_perm(dst))
                return -1;
@@ -252,20 +253,22 @@ static int create_default_files(const char *git_dir, const char *template_path)
        }
        git_config_set("core.filemode", filemode ? "true" : "false");
 
-       if (is_bare_repository()) {
+       if (is_bare_repository())
                git_config_set("core.bare", "true");
-       }
        else {
+               const char *work_tree = get_git_work_tree();
                git_config_set("core.bare", "false");
                /* allow template config file to override the default */
                if (log_all_ref_updates == -1)
                    git_config_set("core.logallrefupdates", "true");
+               if (work_tree != git_work_tree_cfg)
+                       git_config_set("core.worktree", work_tree);
        }
        return reinit;
 }
 
 static const char init_db_usage[] =
-"git-init [--template=<template-directory>] [--shared]";
+"git-init [-q | --quiet] [--template=<template-directory>] [--shared]";
 
 /*
  * If you want to, you can share the DB area with any number of branches.
@@ -280,6 +283,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        const char *template_dir = NULL;
        char *path;
        int len, i, reinit;
+       int quiet = 0;
 
        for (i = 1; i < argc; i++, argv++) {
                const char *arg = argv[1];
@@ -289,10 +293,18 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                        shared_repository = PERM_GROUP;
                else if (!prefixcmp(arg, "--shared="))
                        shared_repository = git_config_perm("arg", arg+9);
+               else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet"))
+                       quiet = 1;
                else
                        usage(init_db_usage);
        }
 
+       git_work_tree_cfg = xcalloc(PATH_MAX, 1);
+       if (!getcwd(git_work_tree_cfg, PATH_MAX))
+               die ("Cannot access current working directory.");
+       if (access(get_git_work_tree(), X_OK))
+               die ("Cannot access work tree '%s'", get_git_work_tree());
+
        /*
         * Set up the default .git directory contents
         */
@@ -335,10 +347,11 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                git_config_set("receive.denyNonFastforwards", "true");
        }
 
-       printf("%s%s Git repository in %s/\n",
-               reinit ? "Reinitialized existing" : "Initialized empty",
-               shared_repository ? " shared" : "",
-               git_dir);
+       if (!quiet)
+               printf("%s%s Git repository in %s/\n",
+                      reinit ? "Reinitialized existing" : "Initialized empty",
+                      shared_repository ? " shared" : "",
+                      git_dir);
 
        return 0;
 }
index 37447123f924149f012a298eaf31dacb0c87b724..13bae3110e05b804db22149e9eb366669a8147c7 100644 (file)
@@ -16,6 +16,7 @@
 #include "refs.h"
 
 static int default_show_root = 1;
+static const char *fmt_patch_subject_prefix = "PATCH";
 
 /* this is in builtin-diff.c */
 void add_head(struct rev_info *revs);
@@ -55,18 +56,18 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
        rev->commit_format = CMIT_FMT_DEFAULT;
        rev->verbose_header = 1;
        rev->show_root_diff = default_show_root;
+       rev->subject_prefix = fmt_patch_subject_prefix;
        argc = setup_revisions(argc, argv, rev, "HEAD");
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
                rev->always_show_header = 0;
+       if (rev->diffopt.follow_renames) {
+               rev->always_show_header = 0;
+               if (rev->diffopt.nr_paths != 1)
+                       usage("git logs can only follow renames on one pathname at a time");
+       }
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
-               if (!prefixcmp(arg, "--encoding=")) {
-                       arg += 11;
-                       if (strcmp(arg, "none"))
-                               git_log_output_encoding = xstrdup(arg);
-                       else
-                               git_log_output_encoding = "";
-               } else if (!strcmp(arg, "--decorate")) {
+               if (!strcmp(arg, "--decorate")) {
                        if (!decorate)
                                for_each_ref(add_ref_decoration, NULL);
                        decorate = 1;
@@ -95,6 +96,12 @@ static int cmd_log_walk(struct rev_info *rev)
 
 static int git_log_config(const char *var, const char *value)
 {
+       if (!strcmp(var, "format.subjectprefix")) {
+               if (!value)
+                       die("format.subjectprefix without value");
+               fmt_patch_subject_prefix = xstrdup(value);
+               return 0;
+       }
        if (!strcmp(var, "log.showroot")) {
                default_show_root = git_config_bool(var, value);
                return 0;
@@ -291,6 +298,7 @@ static int git_format_config(const char *var, const char *value)
        if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                return 0;
        }
+
        return git_log_config(var, value);
 }
 
@@ -298,7 +306,8 @@ static int git_format_config(const char *var, const char *value)
 static FILE *realstdout = NULL;
 static const char *output_directory = NULL;
 
-static int reopen_stdout(struct commit *commit, int nr, int keep_subject)
+static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
+                        int numbered_files)
 {
        char filename[PATH_MAX];
        char *sol;
@@ -315,53 +324,61 @@ static int reopen_stdout(struct commit *commit, int nr, int keep_subject)
                        filename[len++] = '/';
        }
 
-       sprintf(filename + len, "%04d", nr);
-       len = strlen(filename);
-
-       sol = strstr(commit->buffer, "\n\n");
-       if (sol) {
-               int j, space = 1;
-
-               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 (numbered_files) {
+               sprintf(filename + len, "%d", nr);
+               len = strlen(filename);
+
+       } else {
+               sprintf(filename + len, "%04d", nr);
+               len = strlen(filename);
 
-               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;
+               sol = strstr(commit->buffer, "\n\n");
+               if (sol) {
+                       int j, space = 1;
+
+                       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;
                                }
-                               filename[len++] = sol[j];
-                               if (sol[j] == '.')
-                                       while (sol[j + 1] == '.')
-                                               j++;
-                       } else
-                               space = 1;
+                       }
+
+                       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;
+                       }
+                       while (filename[len - 1] == '.'
+                              || filename[len - 1] == '-')
+                               len--;
+                       filename[len] = 0;
                }
-               while (filename[len - 1] == '.' || filename[len - 1] == '-')
-                       len--;
-               filename[len] = 0;
+               if (len + suffix_len >= sizeof(filename))
+                       return error("Patch pathname too long");
+               strcpy(filename + len, fmt_patch_suffix);
        }
-       if (len + suffix_len >= sizeof(filename))
-               return error("Patch pathname too long");
-       strcpy(filename + len, fmt_patch_suffix);
+
        fprintf(realstdout, "%s\n", filename);
        if (freopen(filename, "w", stdout) == NULL)
                return error("Cannot open patch file %s",filename);
-       return 0;
 
+       return 0;
 }
 
 static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const char *prefix)
@@ -431,6 +448,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        int numbered = 0;
        int start_number = -1;
        int keep_subject = 0;
+       int numbered_files = 0;         /* _just_ numbers */
        int subject_prefix = 0;
        int ignore_if_in_upstream = 0;
        int thread = 0;
@@ -450,6 +468,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        rev.diffopt.msg_sep = "";
        rev.diffopt.recursive = 1;
 
+       rev.subject_prefix = fmt_patch_subject_prefix;
        rev.extra_headers = extra_headers;
 
        /*
@@ -465,6 +484,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        numbered = 1;
                else if (!prefixcmp(argv[i], "--start-number="))
                        start_number = strtol(argv[i] + 15, NULL, 10);
+               else if (!strcmp(argv[i], "--numbered-files"))
+                       numbered_files = 1;
                else if (!strcmp(argv[i], "--start-number")) {
                        i++;
                        if (i == argc)
@@ -540,6 +561,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                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)
@@ -576,7 +599,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                get_patch_ids(&rev, &ids, prefix);
 
        if (!use_stdout)
-               realstdout = fdopen(dup(1), "w");
+               realstdout = xfdopen(xdup(1), "w");
 
        prepare_revision_walk(&rev);
        while ((commit = get_revision(&rev)) != NULL) {
@@ -614,7 +637,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                        rev.message_id = message_id;
                }
                if (!use_stdout)
-                       if (reopen_stdout(commit, rev.nr, keep_subject))
+                       if (reopen_stdout(commit, rev.nr, keep_subject,
+                                         numbered_files))
                                die("Failed to create output files");
                shown = log_tree_commit(&rev, commit);
                free(commit->buffer);
@@ -733,11 +757,13 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)
                        sign = '-';
 
                if (verbose) {
-                       static char buf[16384];
+                       char *buf = NULL;
+                       unsigned long buflen = 0;
                        pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
-                                           buf, sizeof(buf), 0, NULL, NULL, 0);
+                                           &buf, &buflen, 0, NULL, NULL, 0);
                        printf("%c %s %s\n", sign,
                               sha1_to_hex(commit->object.sha1), buf);
+                       free(buf);
                }
                else {
                        printf("%c %s\n", sign,
index f7c066b24b7a6a728fd2f0bf4a92a31fb4a695dd..d36181a75541df10c5dc1595ccca5a6fc429591f 100644 (file)
@@ -117,7 +117,7 @@ static void show_other_files(struct dir_struct *dir)
                if (0 <= pos)
                        continue;       /* exact match */
                pos = -pos - 1;
-               if (pos < active_nr) { 
+               if (pos < active_nr) {
                        ce = active_cache[pos];
                        if (ce_namelen(ce) == len &&
                            !memcmp(ce->name, ent->name, len))
@@ -469,9 +469,11 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                break;
        }
 
-       if (require_work_tree &&
-                       (is_bare_repository() || is_inside_git_dir()))
-               die("This operation must be run in a work tree");
+       if (require_work_tree && !is_inside_work_tree()) {
+               const char *work_tree = get_git_work_tree();
+               if (!work_tree || chdir(work_tree))
+                       die("This operation must be run in a work tree");
+       }
 
        pathspec = get_pathspec(prefix, argv + i);
 
index 1cb4dca277b511315d3b914239c57621fc60bcf3..cb4be4fabb84bafd5518e81d2fd0ed6ee191641c 100644 (file)
@@ -15,6 +15,7 @@ static int line_termination = '\n';
 #define LS_TREE_ONLY 2
 #define LS_SHOW_TREES 4
 #define LS_NAME_ONLY 8
+#define LS_SHOW_SIZE 16
 static int abbrev;
 static int ls_options;
 static const char **pathspec;
@@ -22,7 +23,7 @@ static int chomp_prefix;
 static const char *ls_tree_prefix;
 
 static const char ls_tree_usage[] =
-       "git-ls-tree [-d] [-r] [-t] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
+       "git-ls-tree [-d] [-r] [-t] [-l] [-z] [--name-only] [--name-status] [--full-name] [--abbrev[=<n>]] <tree-ish> [path...]";
 
 static int show_recursive(const char *base, int baselen, const char *pathname)
 {
@@ -59,8 +60,9 @@ 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_ISDIRLNK(mode)) {
+       if (S_ISGITLINK(mode)) {
                /*
                 * Maybe we want to have some recursive version here?
                 *
@@ -92,10 +94,24 @@ static int show_tree(const unsigned char *sha1, const char *base, int baselen,
            (baselen < chomp_prefix || memcmp(ls_tree_prefix, base, chomp_prefix)))
                return 0;
 
-       if (!(ls_options & LS_NAME_ONLY))
-               printf("%06o %s %s\t", mode, type,
-                               abbrev ? find_unique_abbrev(sha1,abbrev)
-                                       : sha1_to_hex(sha1));
+       if (!(ls_options & LS_NAME_ONLY)) {
+               if (ls_options & LS_SHOW_SIZE) {
+                       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);
+                       } else
+                               printf("%06o %s %s %7c\t", mode, type,
+                                      abbrev ? find_unique_abbrev(sha1, abbrev)
+                                             : sha1_to_hex(sha1),
+                                      '-');
+               } else
+                       printf("%06o %s %s\t", mode, type,
+                              abbrev ? find_unique_abbrev(sha1, abbrev)
+                                     : sha1_to_hex(sha1));
+       }
        write_name_quoted(base + chomp_prefix, baselen - chomp_prefix,
                          pathname,
                          line_termination, stdout);
@@ -126,12 +142,19 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
                case 't':
                        ls_options |= LS_SHOW_TREES;
                        break;
+               case 'l':
+                       ls_options |= LS_SHOW_SIZE;
+                       break;
                case '-':
                        if (!strcmp(argv[1]+2, "name-only") ||
                            !strcmp(argv[1]+2, "name-status")) {
                                ls_options |= LS_NAME_ONLY;
                                break;
                        }
+                       if (!strcmp(argv[1]+2, "long")) {
+                               ls_options |= LS_SHOW_SIZE;
+                               break;
+                       }
                        if (!strcmp(argv[1]+2, "full-name")) {
                                chomp_prefix = 0;
                                break;
index c95e477e831dd436f074bbca9b2b9ca5ad5a5eb1..b558754142b26399df78472b0bb124144dc194da 100644 (file)
@@ -237,8 +237,6 @@ static int eatspace(char *line)
 
 static char *cleanup_subject(char *subject)
 {
-       if (keep_subject)
-               return subject;
        for (;;) {
                char *p;
                int len, remove;
@@ -425,6 +423,7 @@ static int read_one_header_line(char *line, int sz, FILE *in)
                        if (addlen >= sz - len)
                                addlen = sz - len - 1;
                        memcpy(line + len, continuation, addlen);
+                       line[len] = '\n';
                        len += addlen;
                }
        }
@@ -499,15 +498,42 @@ static int decode_b_segment(char *in, char *ot, char *ep)
        return 0;
 }
 
+/*
+ * When there is no known charset, guess.
+ *
+ * Right now we assume that if the target is UTF-8 (the default),
+ * and it already looks like UTF-8 (which includes US-ASCII as its
+ * subset, of course) then that is what it is and there is nothing
+ * to do.
+ *
+ * Otherwise, we default to assuming it is Latin1 for historical
+ * reasons.
+ */
+static const char *guess_charset(const char *line, const char *target_charset)
+{
+       if (is_encoding_utf8(target_charset)) {
+               if (is_utf8(line))
+                       return NULL;
+       }
+       return "latin1";
+}
+
 static void convert_to_utf8(char *line, const char *charset)
 {
-       static const char latin_one[] = "latin1";
-       const char *input_charset = *charset ? charset : latin_one;
-       char *out = reencode_string(line, metainfo_charset, input_charset);
+       char *out;
+
+       if (!charset || !*charset) {
+               charset = guess_charset(line, metainfo_charset);
+               if (!charset)
+                       return;
+       }
 
+       if (!strcmp(metainfo_charset, charset))
+               return;
+       out = reencode_string(line, metainfo_charset, charset);
        if (!out)
                die("cannot convert from %s to %s\n",
-                   input_charset, metainfo_charset);
+                   charset, metainfo_charset);
        strcpy(line, out);
        free(out);
 }
@@ -819,6 +845,22 @@ static void handle_body(void)
        return;
 }
 
+static void output_header_lines(FILE *fout, const char *hdr, char *data)
+{
+       while (1) {
+               char *ep = strchr(data, '\n');
+               int len;
+               if (!ep)
+                       len = strlen(data);
+               else
+                       len = ep - data;
+               fprintf(fout, "%s: %.*s\n", hdr, len, data);
+               if (!ep)
+                       break;
+               data = ep + 1;
+       }
+}
+
 static void handle_info(void)
 {
        char *sub;
@@ -836,9 +878,13 @@ static void handle_info(void)
                        continue;
 
                if (!memcmp(header[i], "Subject", 7)) {
-                       sub = cleanup_subject(hdr);
-                       cleanup_space(sub);
-                       fprintf(fout, "Subject: %s\n", sub);
+                       if (keep_subject)
+                               sub = hdr;
+                       else {
+                               sub = cleanup_subject(hdr);
+                               cleanup_space(sub);
+                       }
+                       output_header_lines(fout, "Subject", sub);
                } else if (!memcmp(header[i], "From", 4)) {
                        handle_from(hdr);
                        fprintf(fout, "Author: %s\n", name);
@@ -851,8 +897,8 @@ static void handle_info(void)
        fprintf(fout, "\n");
 }
 
-int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
-            const char *msg, const char *patch)
+static int mailinfo(FILE *in, FILE *out, int ks, const char *encoding,
+                   const char *msg, const char *patch)
 {
        keep_subject = ks;
        metainfo_charset = encoding;
index 3bca855aae857cde15f2a249f8e0a5c30b3e7d82..43fc373a15cbe935054b47f9bd67c04ecf216e4e 100644 (file)
@@ -6,9 +6,10 @@
  */
 #include "cache.h"
 #include "builtin.h"
+#include "path-list.h"
 
 static const char git_mailsplit_usage[] =
-"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> <mbox>...";
+"git-mailsplit [-d<prec>] [-f<n>] [-b] -o<directory> <mbox>|<Maildir>...";
 
 static int is_from_line(const char *line, int len)
 {
@@ -96,44 +97,107 @@ static int split_one(FILE *mbox, const char *name, int allow_bare)
        exit(1);
 }
 
-int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip)
+static int populate_maildir_list(struct path_list *list, const char *path)
 {
-       char *name = xmalloc(strlen(dir) + 2 + 3 * sizeof(skip));
+       DIR *dir;
+       struct dirent *dent;
+
+       if ((dir = opendir(path)) == NULL) {
+               error("cannot opendir %s (%s)", path, strerror(errno));
+               return -1;
+       }
+
+       while ((dent = readdir(dir)) != NULL) {
+               if (dent->d_name[0] == '.')
+                       continue;
+               path_list_insert(dent->d_name, list);
+       }
+
+       closedir(dir);
+
+       return 0;
+}
+
+static int split_maildir(const char *maildir, const char *dir,
+       int nr_prec, int skip)
+{
+       char file[PATH_MAX];
+       char curdir[PATH_MAX];
+       char name[PATH_MAX];
        int ret = -1;
+       int i;
+       struct path_list list = {NULL, 0, 0, 1};
 
-       while (*mbox) {
-               const char *file = *mbox++;
-               FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
-               int file_done = 0;
+       snprintf(curdir, sizeof(curdir), "%s/cur", maildir);
+       if (populate_maildir_list(&list, curdir) < 0)
+               goto out;
 
-               if ( !f ) {
-                       error("cannot open mbox %s", file);
+       for (i = 0; i < list.nr; i++) {
+               FILE *f;
+               snprintf(file, sizeof(file), "%s/%s", curdir, list.items[i].path);
+               f = fopen(file, "r");
+               if (!f) {
+                       error("cannot open mail %s (%s)", file, strerror(errno));
                        goto out;
                }
 
                if (fgets(buf, sizeof(buf), f) == NULL) {
-                       if (f == stdin)
-                               break; /* empty stdin is OK */
-                       error("cannot read mbox %s", file);
+                       error("cannot read mail %s (%s)", file, strerror(errno));
                        goto out;
                }
 
-               while (!file_done) {
-                       sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
-                       file_done = split_one(f, name, allow_bare);
+               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               split_one(f, name, 1);
+
+               fclose(f);
+       }
+
+       path_list_clear(&list, 1);
+
+       ret = skip;
+out:
+       return ret;
+}
+
+static int split_mbox(const char *file, const char *dir, int allow_bare,
+                     int nr_prec, int skip)
+{
+       char name[PATH_MAX];
+       int ret = -1;
+
+       FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r");
+       int file_done = 0;
+
+       if (!f) {
+               error("cannot open mbox %s", file);
+               goto out;
+       }
+
+       if (fgets(buf, sizeof(buf), f) == NULL) {
+               /* empty stdin is OK */
+               if (f != stdin) {
+                       error("cannot read mbox %s", file);
+                       goto out;
                }
+               file_done = 1;
+       }
 
-               if (f != stdin)
-                       fclose(f);
+       while (!file_done) {
+               sprintf(name, "%s/%0*d", dir, nr_prec, ++skip);
+               file_done = split_one(f, name, allow_bare);
        }
+
+       if (f != stdin)
+               fclose(f);
+
        ret = skip;
 out:
-       free(name);
        return ret;
 }
+
 int cmd_mailsplit(int argc, const char **argv, const char *prefix)
 {
-       int nr = 0, nr_prec = 4, ret;
+       int nr = 0, nr_prec = 4, num = 0;
        int allow_bare = 0;
        const char *dir = NULL;
        const char **argp;
@@ -186,9 +250,41 @@ int cmd_mailsplit(int argc, const char **argv, const char *prefix)
                        argp = stdin_only;
        }
 
-       ret = split_mbox(argp, dir, allow_bare, nr_prec, nr);
-       if (ret != -1)
-               printf("%d\n", ret);
+       while (*argp) {
+               const char *arg = *argp++;
+               struct stat argstat;
+               int ret = 0;
+
+               if (arg[0] == '-' && arg[1] == 0) {
+                       ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
+                       if (ret < 0) {
+                               error("cannot split patches from stdin");
+                               return 1;
+                       }
+                       num += (ret - nr);
+                       nr = ret;
+                       continue;
+               }
+
+               if (stat(arg, &argstat) == -1) {
+                       error("cannot stat %s (%s)", arg, strerror(errno));
+                       return 1;
+               }
+
+               if (S_ISDIR(argstat.st_mode))
+                       ret = split_maildir(arg, dir, nr_prec, nr);
+               else
+                       ret = split_mbox(arg, dir, allow_bare, nr_prec, nr);
+
+               if (ret < 0) {
+                       error("cannot split patches from %s", arg);
+                       return 1;
+               }
+               num += (ret - nr);
+               nr = ret;
+       }
+
+       printf("%d\n", num);
 
-       return ret == -1;
+       return 0;
 }
index e35d362f2697ebc7e627f230283aedb8ed92857f..0108e22adee8b4de922a2b00b514dd4d4cf23c55 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "commit.h"
 
index 913577390862a847857a650516740059072b60ad..58deb62ac08507901c40e89aec0cea7fcdc78f1e 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "xdiff/xdiff.h"
 #include "xdiff-interface.h"
@@ -5,9 +6,9 @@
 static const char merge_file_usage[] =
 "git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2";
 
-int cmd_merge_file(int argc, char **argv, char **envp)
+int cmd_merge_file(int argc, const char **argv, const char *prefix)
 {
-       char *names[3];
+       const char *names[3];
        mmfile_t mmfs[3];
        mmbuffer_t result = {NULL, 0};
        xpparam_t xpp = {XDF_NEED_MINIMAL};
@@ -36,9 +37,13 @@ int cmd_merge_file(int argc, char **argv, char **envp)
        for (; i < 3; i++)
                names[i] = argv[i + 1];
 
-       for (i = 0; i < 3; i++)
+       for (i = 0; i < 3; i++) {
                if (read_mmfile(mmfs + i, argv[i + 1]))
                        return -1;
+               if (buffer_is_binary(mmfs[i].ptr, mmfs[i].size))
+                       return error("Cannot merge binary files: %s\n",
+                                       argv[i + 1]);
+       }
 
        ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
                        &xpp, XDL_MERGE_ZEALOUS, &result);
@@ -47,7 +52,7 @@ int cmd_merge_file(int argc, char **argv, char **envp)
                free(mmfs[i].ptr);
 
        if (ret >= 0) {
-               char *filename = argv[1];
+               const char *filename = argv[1];
                FILE *f = to_stdout ? stdout : fopen(filename, "wb");
 
                if (!f)
index 2d94eaaa6a90f2efe4401873930533b12b85522f..61eba343ab781341a0baf127b323508a0c2af332 100644 (file)
@@ -85,6 +85,7 @@ copy_data:
 
 struct name_ref_data {
        int tags_only;
+       int name_only;
        const char *ref_filter;
 };
 
@@ -112,6 +113,10 @@ static int name_ref(const char *path, const unsigned char *sha1, int flags, void
 
                if (!prefixcmp(path, "refs/heads/"))
                        path = path + 11;
+               else if (data->tags_only
+                   && data->name_only
+                   && !prefixcmp(path, "refs/tags/"))
+                       path = path + 10;
                else if (!prefixcmp(path, "refs/"))
                        path = path + 5;
 
@@ -151,7 +156,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
 {
        struct object_array revs = { 0, 0, NULL };
        int as_is = 0, all = 0, transform_stdin = 0;
-       struct name_ref_data data = { 0, NULL };
+       struct name_ref_data data = { 0, 0, NULL };
 
        git_config(git_default_config);
 
@@ -167,6 +172,9 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                        if (!strcmp(*argv, "--")) {
                                as_is = 1;
                                continue;
+                       } else if (!strcmp(*argv, "--name-only")) {
+                               data.name_only = 1;
+                               continue;
                        } else if (!strcmp(*argv, "--tags")) {
                                data.tags_only = 1;
                                continue;
@@ -267,16 +275,18 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix)
                        struct object * obj = get_indexed_object(i);
                        if (!obj)
                                continue;
-                       printf("%s %s\n", sha1_to_hex(obj->sha1), get_rev_name(obj));
+                       if (!data.name_only)
+                               printf("%s ", sha1_to_hex(obj->sha1));
+                       printf("%s\n", get_rev_name(obj));
                }
        } else {
                int i;
-               for (i = 0; i < revs.nr; i++)
-                       printf("%s %s\n",
-                               revs.objects[i].name,
-                               get_rev_name(revs.objects[i].item));
+               for (i = 0; i < revs.nr; i++) {
+                       if (!data.name_only)
+                               printf("%s ", revs.objects[i].name);
+                       printf("%s\n", get_rev_name(revs.objects[i].item));
+               }
        }
 
        return 0;
 }
-
index 966f843e4357d45b8c9acf96617bdacd8dc1b133..51a850e1113f6d89cbd7730949a444b330231b1c 100644 (file)
@@ -1,5 +1,6 @@
 #include "builtin.h"
 #include "cache.h"
+#include "attr.h"
 #include "object.h"
 #include "blob.h"
 #include "commit.h"
 
 static const char pack_usage[] = "\
 git-pack-objects [{ -q | --progress | --all-progress }] \n\
-       [--local] [--incremental] [--window=N] [--depth=N] \n\
-       [--no-reuse-delta] [--delta-base-offset] [--non-empty] \n\
-       [--revs [--unpacked | --all]*] [--reflog] [--stdout | base-name] \n\
-       [<ref-list | <object-list]";
+       [--max-pack-size=N] [--local] [--incremental] \n\
+       [--window=N] [--window-memory=N] [--depth=N] \n\
+       [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
+       [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
+       [--stdout | base-name] [<ref-list | <object-list]";
 
 struct object_entry {
-       unsigned char sha1[20];
-       uint32_t crc32;         /* crc of raw pack data for this object */
-       off_t offset;           /* offset into the final pack file */
+       struct pack_idx_entry idx;
        unsigned long size;     /* uncompressed size */
-       unsigned int hash;      /* name hint hash */
-       unsigned int depth;     /* delta depth */
        struct packed_git *in_pack;     /* already in pack */
        off_t in_pack_offset;
        struct object_entry *delta;     /* delta base object */
@@ -35,14 +33,17 @@ struct object_entry {
        struct object_entry *delta_sibling; /* other deltified objects who
                                             * uses the same base as me
                                             */
+       void *delta_data;       /* cached delta (uncompressed) */
        unsigned long delta_size;       /* delta data size (uncompressed) */
+       unsigned int hash;      /* name hint hash */
        enum object_type type;
        enum object_type in_pack_type;  /* could be delta */
        unsigned char in_pack_header_size;
        unsigned char preferred_base; /* we do not pack this, but is available
-                                      * to be used as the base objectto delta
+                                      * to be used as the base object to delta
                                       * objects against.
                                       */
+       unsigned char no_try_delta;
 };
 
 /*
@@ -52,22 +53,33 @@ struct object_entry {
  * nice "minimum seek" order.
  */
 static struct object_entry *objects;
-static uint32_t nr_objects, nr_alloc, nr_result;
+static struct object_entry **written_list;
+static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
 
 static int non_empty;
-static int no_reuse_delta;
+static int no_reuse_delta, no_reuse_object;
 static int local;
 static int incremental;
 static int allow_ofs_delta;
 static const char *pack_tmp_name, *idx_tmp_name;
 static char tmpname[PATH_MAX];
-static unsigned char pack_file_sha1[20];
+static const char *base_name;
 static int progress = 1;
 static int window = 10;
+static uint32_t pack_size_limit;
 static int depth = 50;
 static int pack_to_stdout;
 static int num_preferred_base;
 static struct progress progress_state;
+static int pack_compression_level = Z_DEFAULT_COMPRESSION;
+static int pack_compression_seen;
+
+static unsigned long delta_cache_size = 0;
+static unsigned long max_delta_cache_size = 0;
+static unsigned long cache_max_small_delta_size = 1000;
+
+static unsigned long window_memory_usage = 0;
+static unsigned long window_memory_limit = 0;
 
 /*
  * The object names in objects array are hashed with this hashtable,
@@ -234,15 +246,15 @@ static void *delta_against(void *buf, unsigned long size, struct object_entry *e
 {
        unsigned long othersize, delta_size;
        enum object_type type;
-       void *otherbuf = read_sha1_file(entry->delta->sha1, &type, &othersize);
+       void *otherbuf = read_sha1_file(entry->delta->idx.sha1, &type, &othersize);
        void *delta_buf;
 
        if (!otherbuf)
-               die("unable to read %s", sha1_to_hex(entry->delta->sha1));
+               die("unable to read %s", sha1_to_hex(entry->delta->idx.sha1));
         delta_buf = diff_delta(otherbuf, othersize,
                               buf, size, &delta_size, 0);
         if (!delta_buf || delta_size != entry->delta_size)
-               die("delta size changed");
+               die("delta size changed");
         free(buf);
         free(otherbuf);
        return delta_buf;
@@ -346,76 +358,45 @@ static void copy_pack_data(struct sha1file *f,
        }
 }
 
-static int check_loose_inflate(unsigned char *data, unsigned long len, unsigned long expect)
-{
-       z_stream stream;
-       unsigned char fakebuf[4096];
-       int st;
-
-       memset(&stream, 0, sizeof(stream));
-       stream.next_in = data;
-       stream.avail_in = len;
-       stream.next_out = fakebuf;
-       stream.avail_out = sizeof(fakebuf);
-       inflateInit(&stream);
-
-       while (1) {
-               st = inflate(&stream, Z_FINISH);
-               if (st == Z_STREAM_END || st == Z_OK) {
-                       st = (stream.total_out == expect &&
-                             stream.total_in == len) ? 0 : -1;
-                       break;
-               }
-               if (st != Z_BUF_ERROR) {
-                       st = -1;
-                       break;
-               }
-               stream.next_out = fakebuf;
-               stream.avail_out = sizeof(fakebuf);
-       }
-       inflateEnd(&stream);
-       return st;
-}
-
-static int revalidate_loose_object(struct object_entry *entry,
-                                  unsigned char *map,
-                                  unsigned long mapsize)
-{
-       /* we already know this is a loose object with new type header. */
-       enum object_type type;
-       unsigned long size, used;
-
-       if (pack_to_stdout)
-               return 0;
-
-       used = unpack_object_header_gently(map, mapsize, &type, &size);
-       if (!used)
-               return -1;
-       map += used;
-       mapsize -= used;
-       return check_loose_inflate(map, mapsize, size);
-}
-
 static unsigned long write_object(struct sha1file *f,
-                                 struct object_entry *entry)
+                                 struct object_entry *entry,
+                                 off_t write_offset)
 {
        unsigned long size;
        enum object_type type;
        void *buf;
        unsigned char header[10];
+       unsigned char dheader[10];
        unsigned hdrlen;
        off_t datalen;
        enum object_type obj_type;
        int to_reuse = 0;
+       /* write limit if limited packsize and not first object */
+       unsigned long limit = pack_size_limit && nr_written ?
+                               pack_size_limit - write_offset : 0;
+                               /* no if no delta */
+       int usable_delta =      !entry->delta ? 0 :
+                               /* yes if unlimited packfile */
+                               !pack_size_limit ? 1 :
+                               /* no if base written to previous pack */
+                               entry->delta->idx.offset == (off_t)-1 ? 0 :
+                               /* otherwise double-check written to this
+                                * pack,  like we do below
+                                */
+                               entry->delta->idx.offset ? 1 : 0;
 
        if (!pack_to_stdout)
                crc32_begin(f);
 
        obj_type = entry->type;
-       if (! entry->in_pack)
+       if (no_reuse_object)
+               to_reuse = 0;   /* explicit */
+       else if (!entry->in_pack)
                to_reuse = 0;   /* can't reuse what we don't have */
        else if (obj_type == OBJ_REF_DELTA || obj_type == OBJ_OFS_DELTA)
-               to_reuse = 1;   /* check_object() decided it for us */
+                               /* check_object() decided it for us ... */
+               to_reuse = usable_delta;
+                               /* ... but pack split may override that */
        else if (obj_type != entry->in_pack_type)
                to_reuse = 0;   /* pack has delta which is unusable */
        else if (entry->delta)
@@ -425,44 +406,49 @@ static unsigned long write_object(struct sha1file *f,
                                 * and we do not need to deltify it.
                                 */
 
-       if (!entry->in_pack && !entry->delta) {
-               unsigned char *map;
-               unsigned long mapsize;
-               map = map_sha1_file(entry->sha1, &mapsize);
-               if (map && !legacy_loose_object(map)) {
-                       /* We can copy straight into the pack file */
-                       if (revalidate_loose_object(entry, map, mapsize))
-                               die("corrupt loose object %s",
-                                   sha1_to_hex(entry->sha1));
-                       sha1write(f, map, mapsize);
-                       munmap(map, mapsize);
-                       written++;
-                       reused++;
-                       return mapsize;
-               }
-               if (map)
-                       munmap(map, mapsize);
-       }
-
        if (!to_reuse) {
-               buf = read_sha1_file(entry->sha1, &type, &size);
-               if (!buf)
-                       die("unable to read %s", sha1_to_hex(entry->sha1));
-               if (size != entry->size)
-                       die("object %s size inconsistency (%lu vs %lu)",
-                           sha1_to_hex(entry->sha1), size, entry->size);
-               if (entry->delta) {
+               z_stream stream;
+               unsigned long maxsize;
+               void *out;
+               if (!usable_delta) {
+                       buf = read_sha1_file(entry->idx.sha1, &obj_type, &size);
+                       if (!buf)
+                               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
+               } else if (entry->delta_data) {
+                       size = entry->delta_size;
+                       buf = entry->delta_data;
+                       entry->delta_data = NULL;
+                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
+                               OBJ_OFS_DELTA : OBJ_REF_DELTA;
+               } else {
+                       buf = read_sha1_file(entry->idx.sha1, &type, &size);
+                       if (!buf)
+                               die("unable to read %s", sha1_to_hex(entry->idx.sha1));
                        buf = delta_against(buf, size, entry);
                        size = entry->delta_size;
-                       obj_type = (allow_ofs_delta && entry->delta->offset) ?
+                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                }
+               /* compress the data to store and put compressed length in datalen */
+               memset(&stream, 0, sizeof(stream));
+               deflateInit(&stream, pack_compression_level);
+               maxsize = deflateBound(&stream, size);
+               out = xmalloc(maxsize);
+               /* Compress it */
+               stream.next_in = buf;
+               stream.avail_in = size;
+               stream.next_out = out;
+               stream.avail_out = maxsize;
+               while (deflate(&stream, Z_FINISH) == Z_OK)
+                       /* nothing */;
+               deflateEnd(&stream);
+               datalen = stream.total_out;
+               deflateEnd(&stream);
                /*
                 * The object header is a byte of 'type' followed by zero or
                 * more bytes of length.
                 */
                hdrlen = encode_header(obj_type, size, header);
-               sha1write(f, header, hdrlen);
 
                if (obj_type == OBJ_OFS_DELTA) {
                        /*
@@ -470,22 +456,42 @@ static unsigned long write_object(struct sha1file *f,
                         * encoding of the relative offset for the delta
                         * base from this object's position in the pack.
                         */
-                       off_t ofs = entry->offset - entry->delta->offset;
-                       unsigned pos = sizeof(header) - 1;
-                       header[pos] = ofs & 127;
+                       off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+                       unsigned pos = sizeof(dheader) - 1;
+                       dheader[pos] = ofs & 127;
                        while (ofs >>= 7)
-                               header[--pos] = 128 | (--ofs & 127);
-                       sha1write(f, header + pos, sizeof(header) - pos);
-                       hdrlen += sizeof(header) - pos;
+                               dheader[--pos] = 128 | (--ofs & 127);
+                       if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit) {
+                               free(out);
+                               free(buf);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, dheader + pos, sizeof(dheader) - pos);
+                       hdrlen += sizeof(dheader) - pos;
                } else if (obj_type == OBJ_REF_DELTA) {
                        /*
                         * Deltas with a base reference contain
                         * an additional 20 bytes for the base sha1.
                         */
-                       sha1write(f, entry->delta->sha1, 20);
+                       if (limit && hdrlen + 20 + datalen + 20 >= limit) {
+                               free(out);
+                               free(buf);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, entry->delta->idx.sha1, 20);
                        hdrlen += 20;
+               } else {
+                       if (limit && hdrlen + datalen + 20 >= limit) {
+                               free(out);
+                               free(buf);
+                               return 0;
+                       }
+                       sha1write(f, header, hdrlen);
                }
-               datalen = sha1write_compressed(f, buf, size);
+               sha1write(f, out, datalen);
+               free(out);
                free(buf);
        }
        else {
@@ -495,45 +501,54 @@ static unsigned long write_object(struct sha1file *f,
                off_t offset;
 
                if (entry->delta) {
-                       obj_type = (allow_ofs_delta && entry->delta->offset) ?
+                       obj_type = (allow_ofs_delta && entry->delta->idx.offset) ?
                                OBJ_OFS_DELTA : OBJ_REF_DELTA;
                        reused_delta++;
                }
                hdrlen = encode_header(obj_type, entry->size, header);
-               sha1write(f, header, hdrlen);
-               if (obj_type == OBJ_OFS_DELTA) {
-                       off_t ofs = entry->offset - entry->delta->offset;
-                       unsigned pos = sizeof(header) - 1;
-                       header[pos] = ofs & 127;
-                       while (ofs >>= 7)
-                               header[--pos] = 128 | (--ofs & 127);
-                       sha1write(f, header + pos, sizeof(header) - pos);
-                       hdrlen += sizeof(header) - pos;
-               } else if (obj_type == OBJ_REF_DELTA) {
-                       sha1write(f, entry->delta->sha1, 20);
-                       hdrlen += 20;
-               }
-
                offset = entry->in_pack_offset;
                revidx = find_packed_object(p, offset);
                datalen = revidx[1].offset - offset;
                if (!pack_to_stdout && p->index_version > 1 &&
                    check_pack_crc(p, &w_curs, offset, datalen, revidx->nr))
-                       die("bad packed object CRC for %s", sha1_to_hex(entry->sha1));
+                       die("bad packed object CRC for %s", sha1_to_hex(entry->idx.sha1));
                offset += entry->in_pack_header_size;
                datalen -= entry->in_pack_header_size;
+               if (obj_type == OBJ_OFS_DELTA) {
+                       off_t ofs = entry->idx.offset - entry->delta->idx.offset;
+                       unsigned pos = sizeof(dheader) - 1;
+                       dheader[pos] = ofs & 127;
+                       while (ofs >>= 7)
+                               dheader[--pos] = 128 | (--ofs & 127);
+                       if (limit && hdrlen + sizeof(dheader) - pos + datalen + 20 >= limit)
+                               return 0;
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, dheader + pos, sizeof(dheader) - pos);
+                       hdrlen += sizeof(dheader) - pos;
+               } else if (obj_type == OBJ_REF_DELTA) {
+                       if (limit && hdrlen + 20 + datalen + 20 >= limit)
+                               return 0;
+                       sha1write(f, header, hdrlen);
+                       sha1write(f, entry->delta->idx.sha1, 20);
+                       hdrlen += 20;
+               } else {
+                       if (limit && hdrlen + datalen + 20 >= limit)
+                               return 0;
+                       sha1write(f, header, hdrlen);
+               }
+
                if (!pack_to_stdout && p->index_version == 1 &&
                    check_pack_inflate(p, &w_curs, offset, datalen, entry->size))
-                       die("corrupt packed object for %s", sha1_to_hex(entry->sha1));
+                       die("corrupt packed object for %s", sha1_to_hex(entry->idx.sha1));
                copy_pack_data(f, p, &w_curs, offset, datalen);
                unuse_pack(&w_curs);
                reused++;
        }
-       if (entry->delta)
+       if (usable_delta)
                written_delta++;
        written++;
        if (!pack_to_stdout)
-               entry->crc32 = crc32_end(f);
+               entry->idx.crc32 = crc32_end(f);
        return hdrlen + datalen;
 }
 
@@ -544,15 +559,23 @@ static off_t write_one(struct sha1file *f,
        unsigned long size;
 
        /* offset is non zero if object is written already. */
-       if (e->offset || e->preferred_base)
+       if (e->idx.offset || e->preferred_base)
                return offset;
 
        /* if we are deltified, write out base object first. */
-       if (e->delta)
+       if (e->delta) {
                offset = write_one(f, e->delta, offset);
+               if (!offset)
+                       return 0;
+       }
 
-       e->offset = offset;
-       size = write_object(f, e);
+       e->idx.offset = offset;
+       size = write_object(f, e, offset);
+       if (!size) {
+               e->idx.offset = 0;
+               return 0;
+       }
+       written_list[nr_written++] = e;
 
        /* make sure off_t is sufficiently large not to wrap */
        if (offset > offset + size)
@@ -563,177 +586,115 @@ static off_t write_one(struct sha1file *f,
 static int open_object_dir_tmp(const char *path)
 {
     snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path);
-    return mkstemp(tmpname);
+    return xmkstemp(tmpname);
 }
 
-static off_t write_pack_file(void)
+/* forward declaration for write_pack_file */
+static int adjust_perm(const char *path, mode_t mode);
+
+static void write_pack_file(void)
 {
-       uint32_t i;
+       uint32_t i = 0, j;
        struct sha1file *f;
-       off_t offset, last_obj_offset = 0;
+       off_t offset, offset_one, last_obj_offset = 0;
        struct pack_header hdr;
-       int do_progress = progress;
-
-       if (pack_to_stdout) {
-               f = sha1fd(1, "<stdout>");
-               do_progress >>= 1;
-       } else {
-               int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
-               if (fd < 0)
-                       die("unable to create %s: %s\n", tmpname, strerror(errno));
-               pack_tmp_name = xstrdup(tmpname);
-               f = sha1fd(fd, pack_tmp_name);
-       }
+       int do_progress = progress >> pack_to_stdout;
+       uint32_t nr_remaining = nr_result;
 
        if (do_progress)
                start_progress(&progress_state, "Writing %u objects...", "", nr_result);
+       written_list = xmalloc(nr_objects * sizeof(struct object_entry *));
 
-       hdr.hdr_signature = htonl(PACK_SIGNATURE);
-       hdr.hdr_version = htonl(PACK_VERSION);
-       hdr.hdr_entries = htonl(nr_result);
-       sha1write(f, &hdr, sizeof(hdr));
-       offset = sizeof(hdr);
-       if (!nr_result)
-               goto done;
-       for (i = 0; i < nr_objects; i++) {
-               last_obj_offset = offset;
-               offset = write_one(f, objects + i, offset);
-               if (do_progress)
-                       display_progress(&progress_state, written);
-       }
-       if (do_progress)
-               stop_progress(&progress_state);
- done:
-       if (written != nr_result)
-               die("wrote %u objects while expecting %u", written, nr_result);
-       sha1close(f, pack_file_sha1, 1);
-
-       return last_obj_offset;
-}
-
-static int sha1_sort(const void *_a, const void *_b)
-{
-       const struct object_entry *a = *(struct object_entry **)_a;
-       const struct object_entry *b = *(struct object_entry **)_b;
-       return hashcmp(a->sha1, b->sha1);
-}
-
-static uint32_t index_default_version = 1;
-static uint32_t index_off32_limit = 0x7fffffff;
-
-static void write_index_file(off_t last_obj_offset, unsigned char *sha1)
-{
-       struct sha1file *f;
-       struct object_entry **sorted_by_sha, **list, **last;
-       uint32_t array[256];
-       uint32_t i, index_version;
-       SHA_CTX ctx;
-
-       int fd = open_object_dir_tmp("tmp_idx_XXXXXX");
-       if (fd < 0)
-               die("unable to create %s: %s\n", tmpname, strerror(errno));
-       idx_tmp_name = xstrdup(tmpname);
-       f = sha1fd(fd, idx_tmp_name);
-
-       if (nr_result) {
-               uint32_t j = 0;
-               sorted_by_sha =
-                       xcalloc(nr_result, sizeof(struct object_entry *));
-               for (i = 0; i < nr_objects; i++)
-                       if (!objects[i].preferred_base)
-                               sorted_by_sha[j++] = objects + i;
-               if (j != nr_result)
-                       die("listed %u objects while expecting %u", j, nr_result);
-               qsort(sorted_by_sha, nr_result, sizeof(*sorted_by_sha), sha1_sort);
-               list = sorted_by_sha;
-               last = sorted_by_sha + nr_result;
-       } else
-               sorted_by_sha = list = last = NULL;
-
-       /* if last object's offset is >= 2^31 we should use index V2 */
-       index_version = (last_obj_offset >> 31) ? 2 : index_default_version;
+       do {
+               unsigned char sha1[20];
+
+               if (pack_to_stdout) {
+                       f = sha1fd(1, "<stdout>");
+               } else {
+                       int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
+                       pack_tmp_name = xstrdup(tmpname);
+                       f = sha1fd(fd, pack_tmp_name);
+               }
 
-       /* index versions 2 and above need a header */
-       if (index_version >= 2) {
-               struct pack_idx_header hdr;
-               hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
-               hdr.idx_version = htonl(index_version);
+               hdr.hdr_signature = htonl(PACK_SIGNATURE);
+               hdr.hdr_version = htonl(PACK_VERSION);
+               hdr.hdr_entries = htonl(nr_remaining);
                sha1write(f, &hdr, sizeof(hdr));
-       }
-
-       /*
-        * Write the first-level table (the list is sorted,
-        * but we use a 256-entry lookup to be able to avoid
-        * having to do eight extra binary search iterations).
-        */
-       for (i = 0; i < 256; i++) {
-               struct object_entry **next = list;
-               while (next < last) {
-                       struct object_entry *entry = *next;
-                       if (entry->sha1[0] != i)
+               offset = sizeof(hdr);
+               nr_written = 0;
+               for (; i < nr_objects; i++) {
+                       last_obj_offset = offset;
+                       offset_one = write_one(f, objects + i, offset);
+                       if (!offset_one)
                                break;
-                       next++;
+                       offset = offset_one;
+                       if (do_progress)
+                               display_progress(&progress_state, written);
                }
-               array[i] = htonl(next - sorted_by_sha);
-               list = next;
-       }
-       sha1write(f, array, 256 * 4);
-
-       /* Compute the SHA1 hash of sorted object names. */
-       SHA1_Init(&ctx);
-
-       /* Write the actual SHA1 entries. */
-       list = sorted_by_sha;
-       for (i = 0; i < nr_result; i++) {
-               struct object_entry *entry = *list++;
-               if (index_version < 2) {
-                       uint32_t offset = htonl(entry->offset);
-                       sha1write(f, &offset, 4);
-               }
-               sha1write(f, entry->sha1, 20);
-               SHA1_Update(&ctx, entry->sha1, 20);
-       }
 
-       if (index_version >= 2) {
-               unsigned int nr_large_offset = 0;
-
-               /* write the crc32 table */
-               list = sorted_by_sha;
-               for (i = 0; i < nr_objects; i++) {
-                       struct object_entry *entry = *list++;
-                       uint32_t crc32_val = htonl(entry->crc32);
-                       sha1write(f, &crc32_val, 4);
+               /*
+                * Did we write the wrong # entries in the header?
+                * If so, rewrite it like in fast-import
+                */
+               if (pack_to_stdout || nr_written == nr_remaining) {
+                       sha1close(f, sha1, 1);
+               } else {
+                       sha1close(f, sha1, 0);
+                       fixup_pack_header_footer(f->fd, sha1, pack_tmp_name, nr_written);
+                       close(f->fd);
                }
 
-               /* write the 32-bit offset table */
-               list = sorted_by_sha;
-               for (i = 0; i < nr_objects; i++) {
-                       struct object_entry *entry = *list++;
-                       uint32_t offset = (entry->offset <= index_off32_limit) ?
-                               entry->offset : (0x80000000 | nr_large_offset++);
-                       offset = htonl(offset);
-                       sha1write(f, &offset, 4);
+               if (!pack_to_stdout) {
+                       mode_t mode = umask(0);
+
+                       umask(mode);
+                       mode = 0444 & ~mode;
+
+                       idx_tmp_name = write_idx_file(NULL,
+                               (struct pack_idx_entry **) written_list, nr_written, sha1);
+                       snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
+                                base_name, sha1_to_hex(sha1));
+                       if (adjust_perm(pack_tmp_name, mode))
+                               die("unable to make temporary pack file readable: %s",
+                                   strerror(errno));
+                       if (rename(pack_tmp_name, tmpname))
+                               die("unable to rename temporary pack file: %s",
+                                   strerror(errno));
+                       snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
+                                base_name, sha1_to_hex(sha1));
+                       if (adjust_perm(idx_tmp_name, mode))
+                               die("unable to make temporary index file readable: %s",
+                                   strerror(errno));
+                       if (rename(idx_tmp_name, tmpname))
+                               die("unable to rename temporary index file: %s",
+                                   strerror(errno));
+                       puts(sha1_to_hex(sha1));
                }
 
-               /* write the large offset table */
-               list = sorted_by_sha;
-               while (nr_large_offset) {
-                       struct object_entry *entry = *list++;
-                       uint64_t offset = entry->offset;
-                       if (offset > index_off32_limit) {
-                               uint32_t split[2];
-                               split[0]        = htonl(offset >> 32);
-                               split[1] = htonl(offset & 0xffffffff);
-                               sha1write(f, split, 8);
-                               nr_large_offset--;
-                       }
+               /* mark written objects as written to previous pack */
+               for (j = 0; j < nr_written; j++) {
+                       written_list[j]->idx.offset = (off_t)-1;
                }
-       }
+               nr_remaining -= nr_written;
+       } while (nr_remaining && i < nr_objects);
 
-       sha1write(f, pack_file_sha1, 20);
-       sha1close(f, NULL, 1);
-       free(sorted_by_sha);
-       SHA1_Final(sha1, &ctx);
+       free(written_list);
+       if (do_progress)
+               stop_progress(&progress_state);
+       if (written != nr_result)
+               die("wrote %u objects while expecting %u", written, nr_result);
+       /*
+        * We have scanned through [0 ... i).  Since we have written
+        * the correct number of objects,  the remaining [i ... nr_objects)
+        * items must be either already written (due to out-of-order delta base)
+        * or a preferred base.  Count those which are neither and complain if any.
+        */
+       for (j = 0; i < nr_objects; i++) {
+               struct object_entry *e = objects + i;
+               j += !e->idx.offset && !e->preferred_base;
+       }
+       if (j)
+               die("wrote %u objects as expected but %u unwritten", written, j);
 }
 
 static int locate_object_entry_hash(const unsigned char *sha1)
@@ -743,7 +704,7 @@ static int locate_object_entry_hash(const unsigned char *sha1)
        memcpy(&ui, sha1, sizeof(unsigned int));
        i = ui % object_ix_hashsz;
        while (0 < object_ix[i]) {
-               if (!hashcmp(sha1, objects[object_ix[i] - 1].sha1))
+               if (!hashcmp(sha1, objects[object_ix[i] - 1].idx.sha1))
                        return i;
                if (++i == object_ix_hashsz)
                        i = 0;
@@ -775,7 +736,7 @@ static void rehash_objects(void)
        object_ix = xrealloc(object_ix, sizeof(int) * object_ix_hashsz);
        memset(object_ix, 0, sizeof(int) * object_ix_hashsz);
        for (i = 0, oe = objects; i < nr_objects; i++, oe++) {
-               int ix = locate_object_entry_hash(oe->sha1);
+               int ix = locate_object_entry_hash(oe->idx.sha1);
                if (0 <= ix)
                        continue;
                ix = -1 - ix;
@@ -788,6 +749,9 @@ static unsigned name_hash(const char *name)
        unsigned char c;
        unsigned hash = 0;
 
+       if (!name)
+               return 0;
+
        /*
         * This effectively just creates a sortable number from the
         * last sixteen non-whitespace characters. Last characters
@@ -801,13 +765,36 @@ static unsigned name_hash(const char *name)
        return hash;
 }
 
+static void setup_delta_attr_check(struct git_attr_check *check)
+{
+       static struct git_attr *attr_delta;
+
+       if (!attr_delta)
+               attr_delta = git_attr("delta", 5);
+
+       check[0].attr = attr_delta;
+}
+
+static int no_try_delta(const char *path)
+{
+       struct git_attr_check check[1];
+
+       setup_delta_attr_check(check);
+       if (git_checkattr(path, ARRAY_SIZE(check), check))
+               return 0;
+       if (ATTR_FALSE(check->value))
+               return 1;
+       return 0;
+}
+
 static int add_object_entry(const unsigned char *sha1, enum object_type type,
-                           unsigned hash, int exclude)
+                           const char *name, int exclude)
 {
        struct object_entry *entry;
        struct packed_git *p, *found_pack = NULL;
        off_t found_offset = 0;
        int ix;
+       unsigned hash = name_hash(name);
 
        ix = nr_objects ? locate_object_entry_hash(sha1) : -1;
        if (ix >= 0) {
@@ -843,7 +830,7 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
 
        entry = objects + nr_objects++;
        memset(entry, 0, sizeof(*entry));
-       hashcpy(entry->sha1, sha1);
+       hashcpy(entry->idx.sha1, sha1);
        entry->hash = hash;
        if (type)
                entry->type = type;
@@ -864,6 +851,9 @@ static int add_object_entry(const unsigned char *sha1, enum object_type type,
        if (progress)
                display_progress(&progress_state, nr_objects);
 
+       if (name && no_try_delta(name))
+               entry->no_try_delta = 1;
+
        return 1;
 }
 
@@ -996,10 +986,9 @@ static void add_pbase_object(struct tree_desc *tree,
                if (cmp < 0)
                        return;
                if (name[cmplen] != '/') {
-                       unsigned hash = name_hash(fullname);
                        add_object_entry(entry.sha1,
                                         S_ISDIR(entry.mode) ? OBJ_TREE : OBJ_BLOB,
-                                        hash, 1);
+                                        fullname, 1);
                        return;
                }
                if (S_ISDIR(entry.mode)) {
@@ -1059,10 +1048,11 @@ static int check_pbase_path(unsigned hash)
        return 0;
 }
 
-static void add_preferred_base_object(const char *name, unsigned hash)
+static void add_preferred_base_object(const char *name)
 {
        struct pbase_tree *it;
        int cmplen;
+       unsigned hash = name_hash(name);
 
        if (!num_preferred_base || check_pbase_path(hash))
                return;
@@ -1070,7 +1060,7 @@ static void add_preferred_base_object(const char *name, unsigned hash)
        cmplen = name_cmp_len(name);
        for (it = pbase_tree; it; it = it->next) {
                if (cmplen == 0) {
-                       add_object_entry(it->pcache.sha1, OBJ_TREE, 0, 1);
+                       add_object_entry(it->pcache.sha1, OBJ_TREE, NULL, 1);
                }
                else {
                        struct tree_desc tree;
@@ -1125,8 +1115,8 @@ static void check_object(struct object_entry *entry)
                buf = use_pack(p, &w_curs, entry->in_pack_offset, &avail);
 
                /*
-                * We want in_pack_type even if we do not reuse delta.
-                * There is no point not reusing non-delta representations.
+                * We want in_pack_type even if we do not reuse delta
+                * since non-delta representations could still be reused.
                 */
                used = unpack_object_header_gently(buf, avail,
                                                   &entry->in_pack_type,
@@ -1160,13 +1150,13 @@ static void check_object(struct object_entry *entry)
                                ofs += 1;
                                if (!ofs || MSB(ofs, 7))
                                        die("delta base offset overflow in pack for %s",
-                                           sha1_to_hex(entry->sha1));
+                                           sha1_to_hex(entry->idx.sha1));
                                c = buf[used_0++];
                                ofs = (ofs << 7) + (c & 127);
                        }
                        if (ofs >= entry->in_pack_offset)
                                die("delta base offset out of bound for %s",
-                                   sha1_to_hex(entry->sha1));
+                                   sha1_to_hex(entry->idx.sha1));
                        ofs = entry->in_pack_offset - ofs;
                        if (!no_reuse_delta && !entry->preferred_base)
                                base_ref = find_packed_object_name(p, ofs);
@@ -1213,10 +1203,10 @@ static void check_object(struct object_entry *entry)
                unuse_pack(&w_curs);
        }
 
-       entry->type = sha1_object_info(entry->sha1, &entry->size);
+       entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
        if (entry->type < 0)
                die("unable to get type of object %s",
-                   sha1_to_hex(entry->sha1));
+                   sha1_to_hex(entry->idx.sha1));
 }
 
 static int pack_offset_sort(const void *_a, const void *_b)
@@ -1226,7 +1216,7 @@ static int pack_offset_sort(const void *_a, const void *_b)
 
        /* avoid filesystem trashing with loose objects */
        if (!a->in_pack && !b->in_pack)
-               return hashcmp(a->sha1, b->sha1);
+               return hashcmp(a->idx.sha1, b->idx.sha1);
 
        if (a->in_pack < b->in_pack)
                return -1;
@@ -1280,8 +1270,26 @@ struct unpacked {
        struct object_entry *entry;
        void *data;
        struct delta_index *index;
+       unsigned depth;
 };
 
+static int delta_cacheable(struct unpacked *trg, struct unpacked *src,
+                           unsigned long src_size, unsigned long trg_size,
+                           unsigned long delta_size)
+{
+       if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size)
+               return 0;
+
+       if (delta_size < cache_max_small_delta_size)
+               return 1;
+
+       /* cache delta, if objects are large enough compared to delta size */
+       if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10))
+               return 1;
+
+       return 0;
+}
+
 /*
  * We search for deltas _backwards_ in a list sorted by type and
  * by size, so that we see progressively smaller and smaller files.
@@ -1296,6 +1304,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        struct object_entry *trg_entry = trg->entry;
        struct object_entry *src_entry = src->entry;
        unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+       unsigned ref_depth;
        enum object_type type;
        void *delta_buf;
 
@@ -1320,49 +1329,79 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                return 0;
 
        /* Let's not bust the allowed depth. */
-       if (src_entry->depth >= max_depth)
+       if (src->depth >= max_depth)
                return 0;
 
        /* Now some size filtering heuristics. */
        trg_size = trg_entry->size;
-       max_size = trg_size/2 - 20;
-       max_size = max_size * (max_depth - src_entry->depth) / max_depth;
+       if (!trg_entry->delta) {
+               max_size = trg_size/2 - 20;
+               ref_depth = 1;
+       } else {
+               max_size = trg_entry->delta_size;
+               ref_depth = trg->depth;
+       }
+       max_size = max_size * (max_depth - src->depth) /
+                                               (max_depth - ref_depth + 1);
        if (max_size == 0)
                return 0;
-       if (trg_entry->delta && trg_entry->delta_size <= max_size)
-               max_size = trg_entry->delta_size-1;
        src_size = src_entry->size;
        sizediff = src_size < trg_size ? trg_size - src_size : 0;
        if (sizediff >= max_size)
                return 0;
+       if (trg_size < src_size / 32)
+               return 0;
 
        /* Load data if not already done */
        if (!trg->data) {
-               trg->data = read_sha1_file(trg_entry->sha1, &type, &sz);
+               trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
                if (sz != trg_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
-                           sha1_to_hex(trg_entry->sha1), sz, trg_size);
+                           sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
+               window_memory_usage += sz;
        }
        if (!src->data) {
-               src->data = read_sha1_file(src_entry->sha1, &type, &sz);
+               src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
                if (sz != src_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
-                           sha1_to_hex(src_entry->sha1), sz, src_size);
+                           sha1_to_hex(src_entry->idx.sha1), sz, src_size);
+               window_memory_usage += sz;
        }
        if (!src->index) {
                src->index = create_delta_index(src->data, src_size);
-               if (!src->index)
-                       die("out of memory");
+               if (!src->index) {
+                       static int warned = 0;
+                       if (!warned++)
+                               warning("suboptimal pack - out of memory");
+                       return 0;
+               }
+               window_memory_usage += sizeof_delta_index(src->index);
        }
 
        delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
 
+       if (trg_entry->delta_data) {
+               /* Prefer only shallower same-sized deltas. */
+               if (delta_size == trg_entry->delta_size &&
+                   src->depth + 1 >= trg->depth) {
+                       free(delta_buf);
+                       return 0;
+               }
+               delta_cache_size -= trg_entry->delta_size;
+               free(trg_entry->delta_data);
+               trg_entry->delta_data = NULL;
+       }
        trg_entry->delta = src_entry;
        trg_entry->delta_size = delta_size;
-       trg_entry->depth = src_entry->depth + 1;
-       free(delta_buf);
+       trg->depth = src->depth + 1;
+
+       if (delta_cacheable(src, trg, src_size, trg_size, delta_size)) {
+               trg_entry->delta_data = xrealloc(delta_buf, delta_size);
+               delta_cache_size += trg_entry->delta_size;
+       } else
+               free(delta_buf);
        return 1;
 }
 
@@ -1379,9 +1418,23 @@ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
        return m;
 }
 
+static void free_unpacked(struct unpacked *n)
+{
+       window_memory_usage -= sizeof_delta_index(n->index);
+       free_delta_index(n->index);
+       n->index = NULL;
+       if (n->data) {
+               free(n->data);
+               n->data = NULL;
+               window_memory_usage -= n->entry->size;
+       }
+       n->entry = NULL;
+       n->depth = 0;
+}
+
 static void find_deltas(struct object_entry **list, int window, int depth)
 {
-       uint32_t i = nr_objects, idx = 0, processed = 0;
+       uint32_t i = nr_objects, idx = 0, count = 0, processed = 0;
        unsigned int array_size = window * sizeof(struct unpacked);
        struct unpacked *array;
        int max_depth;
@@ -1412,12 +1465,21 @@ static void find_deltas(struct object_entry **list, int window, int depth)
 
                if (entry->size < 50)
                        continue;
-               free_delta_index(n->index);
-               n->index = NULL;
-               free(n->data);
-               n->data = NULL;
+
+               if (entry->no_try_delta)
+                       continue;
+
+               free_unpacked(n);
                n->entry = entry;
 
+               while (window_memory_limit &&
+                      window_memory_usage > window_memory_limit &&
+                      count > 1) {
+                       uint32_t tail = (idx + window - count) % window;
+                       free_unpacked(array + tail);
+                       count--;
+               }
+
                /*
                 * If the current object is at pack edge, take the depth the
                 * objects that depend on the current object into account
@@ -1447,11 +1509,13 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
                 */
-               if (entry->delta && depth <= entry->depth)
+               if (entry->delta && depth <= n->depth)
                        continue;
 
                next:
                idx++;
+               if (count + 1 < window)
+                       count++;
                if (idx >= window)
                        idx = 0;
        } while (i > 0);
@@ -1490,10 +1554,32 @@ static int git_pack_config(const char *k, const char *v)
                window = git_config_int(k, v);
                return 0;
        }
-       if(!strcmp(k, "pack.depth")) {
+       if (!strcmp(k, "pack.windowmemory")) {
+               window_memory_limit = git_config_ulong(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.depth")) {
                depth = git_config_int(k, v);
                return 0;
        }
+       if (!strcmp(k, "pack.compression")) {
+               int level = git_config_int(k, v);
+               if (level == -1)
+                       level = Z_DEFAULT_COMPRESSION;
+               else if (level < 0 || level > Z_BEST_COMPRESSION)
+                       die("bad pack compression level %d", level);
+               pack_compression_level = level;
+               pack_compression_seen = 1;
+               return 0;
+       }
+       if (!strcmp(k, "pack.deltacachesize")) {
+               max_delta_cache_size = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.deltacachelimit")) {
+               cache_max_small_delta_size = git_config_int(k, v);
+               return 0;
+       }
        return git_default_config(k, v);
 }
 
@@ -1501,7 +1587,6 @@ static void read_object_list_from_stdin(void)
 {
        char line[40 + 1 + PATH_MAX + 2];
        unsigned char sha1[20];
-       unsigned hash;
 
        for (;;) {
                if (!fgets(line, sizeof(line), stdin)) {
@@ -1524,22 +1609,20 @@ static void read_object_list_from_stdin(void)
                if (get_sha1_hex(line, sha1))
                        die("expected sha1, got garbage:\n %s", line);
 
-               hash = name_hash(line+41);
-               add_preferred_base_object(line+41, hash);
-               add_object_entry(sha1, 0, hash, 0);
+               add_preferred_base_object(line+41);
+               add_object_entry(sha1, 0, line+41, 0);
        }
 }
 
 static void show_commit(struct commit *commit)
 {
-       add_object_entry(commit->object.sha1, OBJ_COMMIT, 0, 0);
+       add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
 }
 
 static void show_object(struct object_array_entry *p)
 {
-       unsigned hash = name_hash(p->name);
-       add_preferred_base_object(p->name, hash);
-       add_object_entry(p->item->sha1, p->item->type, hash, 0);
+       add_preferred_base_object(p->name);
+       add_object_entry(p->item->sha1, p->item->type, p->name, 0);
 }
 
 static void show_edge(struct commit *commit)
@@ -1592,8 +1675,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        int use_internal_rev_list = 0;
        int thin = 0;
        uint32_t i;
-       off_t last_obj_offset;
-       const char *base_name = NULL;
        const char **rp_av;
        int rp_ac_alloc = 64;
        int rp_ac;
@@ -1605,6 +1686,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        rp_ac = 2;
 
        git_config(git_pack_config);
+       if (!pack_compression_seen && core_compression_seen)
+               pack_compression_level = core_compression_level;
 
        progress = isatty(2);
        for (i = 1; i < argc; i++) {
@@ -1625,6 +1708,25 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        incremental = 1;
                        continue;
                }
+               if (!prefixcmp(arg, "--compression=")) {
+                       char *end;
+                       int level = strtoul(arg+14, &end, 0);
+                       if (!arg[14] || *end)
+                               usage(pack_usage);
+                       if (level == -1)
+                               level = Z_DEFAULT_COMPRESSION;
+                       else if (level < 0 || level > Z_BEST_COMPRESSION)
+                               die("bad pack compression level %d", level);
+                       pack_compression_level = level;
+                       continue;
+               }
+               if (!prefixcmp(arg, "--max-pack-size=")) {
+                       char *end;
+                       pack_size_limit = strtoul(arg+16, &end, 0) * 1024 * 1024;
+                       if (!arg[16] || *end)
+                               usage(pack_usage);
+                       continue;
+               }
                if (!prefixcmp(arg, "--window=")) {
                        char *end;
                        window = strtoul(arg+9, &end, 0);
@@ -1632,6 +1734,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                                usage(pack_usage);
                        continue;
                }
+               if (!prefixcmp(arg, "--window-memory=")) {
+                       if (!git_parse_ulong(arg+16, &window_memory_limit))
+                               usage(pack_usage);
+                       continue;
+               }
                if (!prefixcmp(arg, "--depth=")) {
                        char *end;
                        depth = strtoul(arg+8, &end, 0);
@@ -1655,6 +1762,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        no_reuse_delta = 1;
                        continue;
                }
+               if (!strcmp("--no-reuse-object", arg)) {
+                       no_reuse_object = no_reuse_delta = 1;
+                       continue;
+               }
                if (!strcmp("--delta-base-offset", arg)) {
                        allow_ofs_delta = 1;
                        continue;
@@ -1688,12 +1799,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
                if (!prefixcmp(arg, "--index-version=")) {
                        char *c;
-                       index_default_version = strtoul(arg + 16, &c, 10);
-                       if (index_default_version > 2)
+                       pack_idx_default_version = strtoul(arg + 16, &c, 10);
+                       if (pack_idx_default_version > 2)
                                die("bad %s", arg);
                        if (*c == ',')
-                               index_off32_limit = strtoul(c+1, &c, 0);
-                       if (*c || index_off32_limit & 0x80000000)
+                               pack_idx_off32_limit = strtoul(c+1, &c, 0);
+                       if (*c || pack_idx_off32_limit & 0x80000000)
                                die("bad %s", arg);
                        continue;
                }
@@ -1719,6 +1830,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
        if (pack_to_stdout != !base_name)
                usage(pack_usage);
 
+       if (pack_to_stdout && pack_size_limit)
+               die("--max-pack-size cannot be used to build a pack for transfer.");
+
        if (!pack_to_stdout && thin)
                die("--thin cannot be used to build an indexable pack.");
 
@@ -1744,33 +1858,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                fprintf(stderr, "Result has %u objects.\n", nr_result);
        if (nr_result)
                prepare_pack(window, depth);
-       last_obj_offset = write_pack_file();
-       if (!pack_to_stdout) {
-               unsigned char object_list_sha1[20];
-               mode_t mode = umask(0);
-
-               umask(mode);
-               mode = 0444 & ~mode;
-
-               write_index_file(last_obj_offset, object_list_sha1);
-               snprintf(tmpname, sizeof(tmpname), "%s-%s.pack",
-                        base_name, sha1_to_hex(object_list_sha1));
-               if (adjust_perm(pack_tmp_name, mode))
-                       die("unable to make temporary pack file readable: %s",
-                           strerror(errno));
-               if (rename(pack_tmp_name, tmpname))
-                       die("unable to rename temporary pack file: %s",
-                           strerror(errno));
-               snprintf(tmpname, sizeof(tmpname), "%s-%s.idx",
-                        base_name, sha1_to_hex(object_list_sha1));
-               if (adjust_perm(idx_tmp_name, mode))
-                       die("unable to make temporary index file readable: %s",
-                           strerror(errno));
-               if (rename(idx_tmp_name, tmpname))
-                       die("unable to rename temporary index file: %s",
-                           strerror(errno));
-               puts(sha1_to_hex(object_list_sha1));
-       }
+       write_pack_file();
        if (progress)
                fprintf(stderr, "Total %u (delta %u), reused %u (delta %u)\n",
                        written, written_delta, reused, reused_delta);
index d080e30d67a913529d46e0da169faa4b0be75580..09df4e11a8bb89cded9be4af06bb162b7a92bd4c 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "refs.h"
 #include "object.h"
@@ -12,9 +13,11 @@ struct ref_to_prune {
        char name[FLEX_ARRAY];
 };
 
+#define PACK_REFS_PRUNE        0x0001
+#define PACK_REFS_ALL  0x0002
+
 struct pack_refs_cb_data {
-       int prune;
-       int all;
+       unsigned int flags;
        struct ref_to_prune *ref_to_prune;
        FILE *refs_file;
 };
@@ -39,7 +42,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
        is_tag_ref = !prefixcmp(path, "refs/tags/");
 
        /* ALWAYS pack refs that were already packed or are tags */
-       if (!cb->all && !is_tag_ref && !(flags & REF_ISPACKED))
+       if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref && !(flags & REF_ISPACKED))
                return 0;
 
        fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
@@ -53,7 +56,7 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
                }
        }
 
-       if (cb->prune && !do_not_prune(flags)) {
+       if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
                int namelen = strlen(path) + 1;
                struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
                hashcpy(n->sha1, sha1);
@@ -85,26 +88,53 @@ static void prune_refs(struct ref_to_prune *r)
 
 static struct lock_file packed;
 
-int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+static int pack_refs(unsigned int flags)
 {
-       int fd, i;
+       int fd;
        struct pack_refs_cb_data cbdata;
 
        memset(&cbdata, 0, sizeof(cbdata));
+       cbdata.flags = flags;
+
+       fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
+       cbdata.refs_file = fdopen(fd, "w");
+       if (!cbdata.refs_file)
+               die("unable to create ref-pack file structure (%s)",
+                   strerror(errno));
+
+       /* perhaps other traits later as well */
+       fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
+
+       for_each_ref(handle_one_ref, &cbdata);
+       if (ferror(cbdata.refs_file))
+               die("failed to write ref-pack file");
+       if (fflush(cbdata.refs_file) || fsync(fd) || fclose(cbdata.refs_file))
+               die("failed to write ref-pack file (%s)", strerror(errno));
+       if (commit_lock_file(&packed) < 0)
+               die("unable to overwrite old ref-pack file (%s)", strerror(errno));
+       if (cbdata.flags & PACK_REFS_PRUNE)
+               prune_refs(cbdata.ref_to_prune);
+       return 0;
+}
 
-       cbdata.prune = 1;
+int cmd_pack_refs(int argc, const char **argv, const char *prefix)
+{
+       int i;
+       unsigned int flags;
+
+       flags = PACK_REFS_PRUNE;
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "--prune")) {
-                       cbdata.prune = 1; /* now the default */
+                       flags |= PACK_REFS_PRUNE; /* now the default */
                        continue;
                }
                if (!strcmp(arg, "--no-prune")) {
-                       cbdata.prune = 0;
+                       flags &= ~PACK_REFS_PRUNE;
                        continue;
                }
                if (!strcmp(arg, "--all")) {
-                       cbdata.all = 1;
+                       flags |= PACK_REFS_ALL;
                        continue;
                }
                /* perhaps other parameters later... */
@@ -113,22 +143,5 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
        if (i != argc)
                usage(builtin_pack_refs_usage);
 
-       fd = hold_lock_file_for_update(&packed, git_path("packed-refs"), 1);
-       cbdata.refs_file = fdopen(fd, "w");
-       if (!cbdata.refs_file)
-               die("unable to create ref-pack file structure (%s)",
-                   strerror(errno));
-
-       /* perhaps other traits later as well */
-       fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
-
-       for_each_ref(handle_one_ref, &cbdata);
-       fflush(cbdata.refs_file);
-       fsync(fd);
-       fclose(cbdata.refs_file);
-       if (commit_lock_file(&packed) < 0)
-               die("unable to overwrite old ref-pack file (%s)", strerror(errno));
-       if (cbdata.prune)
-               prune_refs(cbdata.ref_to_prune);
-       return 0;
+       return pack_refs(flags);
 }
index cb78401c946eac9019c2e4e953c3b87ef9e214f3..2612f07f74855ad6dec8ccd605279ab3a502e5e2 100644 (file)
@@ -5,17 +5,13 @@
 #include "refs.h"
 #include "run-command.h"
 #include "builtin.h"
-
-#define MAX_URI (16)
+#include "remote.h"
 
 static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]";
 
-static int all, tags, force, thin = 1, verbose;
+static int all, force, thin = 1, verbose;
 static const char *receivepack;
 
-#define BUF_SIZE (2084)
-static char buffer[BUF_SIZE];
-
 static const char **refspec;
 static int refspec_nr;
 
@@ -27,285 +23,47 @@ static void add_refspec(const char *ref)
        refspec_nr = nr;
 }
 
-static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
-{
-       /* Ignore the "refs/" at the beginning of the refname */
-       ref += 5;
-
-       if (!prefixcmp(ref, "tags/"))
-               add_refspec(xstrdup(ref));
-       return 0;
-}
-
-static void expand_refspecs(void)
-{
-       if (all) {
-               if (refspec_nr)
-                       die("cannot mix '--all' and a refspec");
-
-               /*
-                * No need to expand "--all" - we'll just use
-                * the "--all" flag to send-pack
-                */
-               return;
-       }
-       if (!tags)
-               return;
-       for_each_ref(expand_one_ref, NULL);
-}
-
-struct wildcard_cb {
-       const char *from_prefix;
-       int from_prefix_len;
-       const char *to_prefix;
-       int to_prefix_len;
-       int force;
-};
-
-static int expand_wildcard_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct wildcard_cb *cb = cb_data;
-       int len = strlen(ref);
-       char *expanded, *newref;
-
-       if (len < cb->from_prefix_len ||
-           memcmp(cb->from_prefix, ref, cb->from_prefix_len))
-               return 0;
-       expanded = xmalloc(len * 2 + cb->force +
-                          (cb->to_prefix_len - cb->from_prefix_len) + 2);
-       newref = expanded + cb->force;
-       if (cb->force)
-               expanded[0] = '+';
-       memcpy(newref, ref, len);
-       newref[len] = ':';
-       memcpy(newref + len + 1, cb->to_prefix, cb->to_prefix_len);
-       strcpy(newref + len + 1 + cb->to_prefix_len,
-              ref + cb->from_prefix_len);
-       add_refspec(expanded);
-       return 0;
-}
-
-static int wildcard_ref(const char *ref)
-{
-       int len;
-       const char *colon;
-       struct wildcard_cb cb;
-
-       memset(&cb, 0, sizeof(cb));
-       if (ref[0] == '+') {
-               cb.force = 1;
-               ref++;
-       }
-       len = strlen(ref);
-       colon = strchr(ref, ':');
-       if (! (colon && ref < colon &&
-              colon[-2] == '/' && colon[-1] == '*' &&
-              /* "<mine>/<asterisk>:<yours>/<asterisk>" is at least 7 bytes */
-              7 <= len &&
-              ref[len-2] == '/' && ref[len-1] == '*') )
-               return 0 ;
-       cb.from_prefix = ref;
-       cb.from_prefix_len = colon - ref - 1;
-       cb.to_prefix = colon + 1;
-       cb.to_prefix_len = len - (colon - ref) - 2;
-       for_each_ref(expand_wildcard_ref, &cb);
-       return 1;
-}
-
 static void set_refspecs(const char **refs, int nr)
 {
-       if (nr) {
-               int i;
-               for (i = 0; i < nr; i++) {
-                       const char *ref = refs[i];
-                       if (!strcmp("tag", ref)) {
-                               char *tag;
-                               int len;
-                               if (nr <= ++i)
-                                       die("tag shorthand without <tag>");
-                               len = strlen(refs[i]) + 11;
-                               tag = xmalloc(len);
-                               strcpy(tag, "refs/tags/");
-                               strcat(tag, refs[i]);
-                               ref = tag;
-                       }
-                       else if (wildcard_ref(ref))
-                               continue;
-                       add_refspec(ref);
-               }
-       }
-       expand_refspecs();
-}
-
-static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
-{
-       int n = 0;
-       FILE *f = fopen(git_path("remotes/%s", repo), "r");
-       int has_explicit_refspec = refspec_nr || all || tags;
-
-       if (!f)
-               return -1;
-       while (fgets(buffer, BUF_SIZE, f)) {
-               int is_refspec;
-               char *s, *p;
-
-               if (!prefixcmp(buffer, "URL:")) {
-                       is_refspec = 0;
-                       s = buffer + 4;
-               } else if (!prefixcmp(buffer, "Push:")) {
-                       is_refspec = 1;
-                       s = buffer + 5;
-               } else
-                       continue;
-
-               /* Remove whitespace at the head.. */
-               while (isspace(*s))
-                       s++;
-               if (!*s)
-                       continue;
-
-               /* ..and at the end */
-               p = s + strlen(s);
-               while (isspace(p[-1]))
-                       *--p = 0;
-
-               if (!is_refspec) {
-                       if (n < MAX_URI)
-                               uri[n++] = xstrdup(s);
-                       else
-                               error("more than %d URL's specified, ignoring the rest", MAX_URI);
-               }
-               else if (is_refspec && !has_explicit_refspec) {
-                       if (!wildcard_ref(s))
-                               add_refspec(xstrdup(s));
-               }
-       }
-       fclose(f);
-       if (!n)
-               die("remote '%s' has no URL", repo);
-       return n;
-}
-
-static const char **config_uri;
-static const char *config_repo;
-static int config_repo_len;
-static int config_current_uri;
-static int config_get_refspecs;
-static int config_get_receivepack;
-
-static int get_remote_config(const char* key, const char* value)
-{
-       if (!prefixcmp(key, "remote.") &&
-           !strncmp(key + 7, config_repo, config_repo_len)) {
-               if (!strcmp(key + 7 + config_repo_len, ".url")) {
-                       if (config_current_uri < MAX_URI)
-                               config_uri[config_current_uri++] = xstrdup(value);
-                       else
-                               error("more than %d URL's specified, ignoring the rest", MAX_URI);
-               }
-               else if (config_get_refspecs &&
-                        !strcmp(key + 7 + config_repo_len, ".push")) {
-                       if (!wildcard_ref(value))
-                               add_refspec(xstrdup(value));
-               }
-               else if (config_get_receivepack &&
-                        !strcmp(key + 7 + config_repo_len, ".receivepack")) {
-                       if (!receivepack) {
-                               char *rp = xmalloc(strlen(value) + 16);
-                               sprintf(rp, "--receive-pack=%s", value);
-                               receivepack = rp;
-                       } else
-                               error("more than one receivepack given, using the first");
-               }
-       }
-       return 0;
-}
-
-static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
-{
-       config_repo_len = strlen(repo);
-       config_repo = repo;
-       config_current_uri = 0;
-       config_uri = uri;
-       config_get_refspecs = !(refspec_nr || all || tags);
-       config_get_receivepack = (receivepack == NULL);
-
-       git_config(get_remote_config);
-       return config_current_uri;
-}
-
-static int get_branches_uri(const char *repo, const char *uri[MAX_URI])
-{
-       const char *slash = strchr(repo, '/');
-       int n = slash ? slash - repo : 1000;
-       FILE *f = fopen(git_path("branches/%.*s", n, repo), "r");
-       char *s, *p;
-       int len;
-
-       if (!f)
-               return 0;
-       s = fgets(buffer, BUF_SIZE, f);
-       fclose(f);
-       if (!s)
-               return 0;
-       while (isspace(*s))
-               s++;
-       if (!*s)
-               return 0;
-       p = s + strlen(s);
-       while (isspace(p[-1]))
-               *--p = 0;
-       len = p - s;
-       if (slash)
-               len += strlen(slash);
-       p = xmalloc(len + 1);
-       strcpy(p, s);
-       if (slash)
-               strcat(p, slash);
-       uri[0] = p;
-       return 1;
-}
-
-/*
- * Read remotes and branches file, fill the push target URI
- * list.  If there is no command line refspecs, read Push: lines
- * to set up the *refspec list as well.
- * return the number of push target URIs
- */
-static int read_config(const char *repo, const char *uri[MAX_URI])
-{
-       int n;
-
-       if (*repo != '/') {
-               n = get_remotes_uri(repo, uri);
-               if (n > 0)
-                       return n;
-
-               n = get_config_remotes_uri(repo, uri);
-               if (n > 0)
-                       return n;
-
-               n = get_branches_uri(repo, uri);
-               if (n > 0)
-                       return n;
+       int i;
+       for (i = 0; i < nr; i++) {
+               const char *ref = refs[i];
+               if (!strcmp("tag", ref)) {
+                       char *tag;
+                       int len;
+                       if (nr <= ++i)
+                               die("tag shorthand without <tag>");
+                       len = strlen(refs[i]) + 11;
+                       tag = xmalloc(len);
+                       strcpy(tag, "refs/tags/");
+                       strcat(tag, refs[i]);
+                       ref = tag;
+               }
+               add_refspec(ref);
        }
-
-       uri[0] = repo;
-       return 1;
 }
 
 static int do_push(const char *repo)
 {
-       const char *uri[MAX_URI];
-       int i, n, errs;
+       int i, errs;
        int common_argc;
        const char **argv;
        int argc;
+       struct remote *remote = remote_get(repo);
 
-       n = read_config(repo, uri);
-       if (n <= 0)
+       if (!remote)
                die("bad repository '%s'", repo);
 
+       if (remote->receivepack) {
+               char *rp = xmalloc(strlen(remote->receivepack) + 16);
+               sprintf(rp, "--receive-pack=%s", remote->receivepack);
+               receivepack = rp;
+       }
+       if (!refspec && !all && remote->push_refspec_nr) {
+               refspec = remote->push_refspec;
+               refspec_nr = remote->push_refspec_nr;
+       }
+
        argv = xmalloc((refspec_nr + 10) * sizeof(char *));
        argv[0] = "dummy-send-pack";
        argc = 1;
@@ -318,18 +76,23 @@ static int do_push(const char *repo)
        common_argc = argc;
 
        errs = 0;
-       for (i = 0; i < n; i++) {
+       for (i = 0; i < remote->uri_nr; i++) {
                int err;
                int dest_argc = common_argc;
                int dest_refspec_nr = refspec_nr;
                const char **dest_refspec = refspec;
-               const char *dest = uri[i];
+               const char *dest = remote->uri[i];
                const char *sender = "send-pack";
                if (!prefixcmp(dest, "http://") ||
                    !prefixcmp(dest, "https://"))
                        sender = "http-push";
-               else if (thin)
-                       argv[dest_argc++] = "--thin";
+               else {
+                       char *rem = xmalloc(strlen(remote->name) + 10);
+                       sprintf(rem, "--remote=%s", remote->name);
+                       argv[dest_argc++] = rem;
+                       if (thin)
+                               argv[dest_argc++] = "--thin";
+               }
                argv[0] = sender;
                argv[dest_argc++] = dest;
                while (dest_refspec_nr--)
@@ -341,7 +104,7 @@ static int do_push(const char *repo)
                if (!err)
                        continue;
 
-               error("failed to push to '%s'", uri[i]);
+               error("failed to push to '%s'", remote->uri[i]);
                switch (err) {
                case -ERR_RUN_COMMAND_FORK:
                        error("unable to fork for %s", sender);
@@ -362,7 +125,7 @@ static int do_push(const char *repo)
 int cmd_push(int argc, const char **argv, const char *prefix)
 {
        int i;
-       const char *repo = "origin";    /* default repository */
+       const char *repo = NULL;        /* default repository */
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -385,7 +148,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        continue;
                }
                if (!strcmp(arg, "--tags")) {
-                       tags = 1;
+                       add_refspec("refs/tags/*");
                        continue;
                }
                if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) {
@@ -411,5 +174,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                usage(push_usage);
        }
        set_refspecs(argv + i, argc - i);
+       if (all && refspec)
+               usage(push_usage);
+
        return do_push(repo);
 }
index 316fb0f8dae022b35a89b71c94a22331a77a500a..f6764b97392a582f442c356945d71a0bb8887308 100644 (file)
 #include "dir.h"
 #include "builtin.h"
 
-static struct object_list *trees;
+#define MAX_TREES 4
+static int nr_trees;
+static struct tree *trees[MAX_TREES];
 
 static int list_tree(unsigned char *sha1)
 {
-       struct tree *tree = parse_tree_indirect(sha1);
+       struct tree *tree;
+
+       if (nr_trees >= 4)
+               return -1;
+       tree = parse_tree_indirect(sha1);
        if (!tree)
                return -1;
-       object_list_append(&tree->object, &trees);
+       trees[nr_trees++] = tree;
        return 0;
 }
 
@@ -76,15 +82,14 @@ static void prime_cache_tree_rec(struct cache_tree *it, struct tree *tree)
 
 static void prime_cache_tree(void)
 {
-       struct tree *tree = (struct tree *)trees->item;
-       if (!tree)
+       if (!nr_trees)
                return;
        active_cache_tree = cache_tree();
-       prime_cache_tree_rec(active_cache_tree, tree);
+       prime_cache_tree_rec(active_cache_tree, trees[0]);
 
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] [--index-output=<file>] <sha1> [<sha2> [<sha3>]])";
+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;
 
@@ -92,12 +97,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
 {
        int i, newfd, stage = 0;
        unsigned char sha1[20];
+       struct tree_desc t[MAX_TREES];
        struct unpack_trees_options opts;
 
        memset(&opts, 0, sizeof(opts));
        opts.head_idx = -1;
 
-       setup_git_directory();
        git_config(git_default_config);
 
        newfd = hold_locked_index(&lock_file, 1);
@@ -259,7 +264,15 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        opts.head_idx = 1;
        }
 
-       unpack_trees(trees, &opts);
+       if (MAX_TREES < nr_trees)
+               die("I cannot read more than %d trees", MAX_TREES);
+
+       for (i = 0; i < nr_trees; i++) {
+               struct tree *tree = trees[i];
+               parse_tree(tree);
+               init_tree_desc(t+i, tree->buffer, tree->size);
+       }
+       unpack_trees(nr_trees, t, &opts);
 
        /*
         * When reading only one tree (either the most basic form,
@@ -267,7 +280,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
         * valid cache-tree because the index must match exactly
         * what came from the tree.
         */
-       if (trees && trees->item && !opts.prefix && (!opts.merge || (stage == 2))) {
+       if (nr_trees && !opts.prefix && (!opts.merge || (stage == 2))) {
                cache_tree_free(&active_cache_tree);
                prime_cache_tree();
        }
index 4c39f1da98e5e690f28f5145a1ab5dba790da68d..ce093cad78ce8008cd8a60d3ab6be5663a712a9d 100644 (file)
@@ -249,7 +249,7 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
        /* we take the lock for the ref itself to prevent it from
         * getting updated.
         */
-       lock = lock_any_ref_for_update(ref, sha1);
+       lock = lock_any_ref_for_update(ref, sha1, 0);
        if (!lock)
                return error("cannot lock ref '%s'", ref);
        log_file = xstrdup(git_path("logs/%s", ref));
index 8c2c8bdc18a69e4dff7386548e5f7ea78a133f7b..29d057c98cc255e718df540a62177101d9789abe 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "path-list.h"
 #include "xdiff/xdiff.h"
@@ -12,6 +13,9 @@ static const char git_rerere_usage[] =
 static int cutoff_noresolve = 15;
 static int cutoff_resolve = 60;
 
+/* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
+static int rerere_enabled = -1;
+
 static char *merge_rr_path;
 
 static const char *rr_path(const char *name, const char *file)
@@ -57,7 +61,8 @@ static int write_rr(struct path_list *rr, int out_fd)
                    write_in_full(out_fd, path, length) != length)
                        die("unable to write rerere record");
        }
-       close(out_fd);
+       if (close(out_fd) != 0)
+               die("unable to write rerere record");
        return commit_lock_file(&write_lock);
 }
 
@@ -164,19 +169,16 @@ static int find_conflict(struct path_list *conflict)
        int i;
        if (read_cache() < 0)
                return error("Could not read index");
-       for (i = 0; i + 2 < active_nr; i++) {
-               struct cache_entry *e1 = active_cache[i];
-               struct cache_entry *e2 = active_cache[i+1];
-               struct cache_entry *e3 = active_cache[i+2];
-               if (ce_stage(e1) == 1 &&
-                   ce_stage(e2) == 2 &&
+       for (i = 0; i+1 < active_nr; i++) {
+               struct cache_entry *e2 = active_cache[i];
+               struct cache_entry *e3 = active_cache[i+1];
+               if (ce_stage(e2) == 2 &&
                    ce_stage(e3) == 3 &&
-                   ce_same_name(e1, e2) && ce_same_name(e1, e3) &&
-                   S_ISREG(ntohl(e1->ce_mode)) &&
+                   ce_same_name(e2, e3) &&
                    S_ISREG(ntohl(e2->ce_mode)) &&
                    S_ISREG(ntohl(e3->ce_mode))) {
-                       path_list_insert((const char *)e1->name, conflict);
-                       i += 2;
+                       path_list_insert((const char *)e2->name, conflict);
+                       i++; /* skip over both #2 and #3 */
                }
        }
        return 0;
@@ -281,8 +283,8 @@ static int diff_two(const char *file1, const char *label1,
        printf("--- a/%s\n+++ b/%s\n", label1, label2);
        fflush(stdout);
        xpp.flags = XDF_NEED_MINIMAL;
+       memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = 3;
-       xecfg.flags = 0;
        ecb.outf = outf;
        xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
 
@@ -386,21 +388,41 @@ static int git_rerere_config(const char *var, const char *value)
                cutoff_resolve = git_config_int(var, value);
        else if (!strcmp(var, "gc.rerereunresolved"))
                cutoff_noresolve = git_config_int(var, value);
+       else if (!strcmp(var, "rerere.enabled"))
+               rerere_enabled = git_config_bool(var, value);
        else
                return git_default_config(var, value);
        return 0;
 }
 
-int cmd_rerere(int argc, const char **argv, const char *prefix)
+static int is_rerere_enabled(void)
 {
-       struct path_list merge_rr = { NULL, 0, 0, 1 };
-       int i, fd = -1;
        struct stat st;
+       const char *rr_cache;
+       int rr_cache_exists;
 
-       if (stat(git_path("rr-cache"), &st) || !S_ISDIR(st.st_mode))
+       if (!rerere_enabled)
                return 0;
 
+       rr_cache = git_path("rr-cache");
+       rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
+       if (rerere_enabled < 0)
+               return rr_cache_exists;
+
+       if (!rr_cache_exists &&
+           (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
+               die("Could not create directory %s", rr_cache);
+       return 1;
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+       struct path_list merge_rr = { NULL, 0, 0, 1 };
+       int i, fd = -1;
+
        git_config(git_rerere_config);
+       if (!is_rerere_enabled())
+               return 0;
 
        merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
        fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
@@ -410,6 +432,7 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
                return do_plain_rerere(&merge_rr, fd);
        else if (!strcmp(argv[1], "clear")) {
                for (i = 0; i < merge_rr.nr; i++) {
+                       struct stat st;
                        const char *name = (const char *)merge_rr.items[i].util;
                        if (!stat(git_path("rr-cache/%s", name), &st) &&
                                        S_ISDIR(st.st_mode) &&
@@ -434,4 +457,3 @@ int cmd_rerere(int argc, const char **argv, const char *prefix)
        path_list_clear(&merge_rr, 1);
        return 0;
 }
-
index 2eb919f39cfe589bf7cfa8c77ec9b1ae29676efe..ac551d59f3f6cb209a645ad708c46e4e88791295 100644 (file)
@@ -70,21 +70,9 @@ static void show_commit(struct commit *commit)
        if (revs.parents) {
                struct commit_list *parents = commit->parents;
                while (parents) {
-                       struct object *o = &(parents->item->object);
+                       printf(" %s", sha1_to_hex(parents->item->object.sha1));
                        parents = parents->next;
-                       if (o->flags & TMP_MARK)
-                               continue;
-                       printf(" %s", sha1_to_hex(o->sha1));
-                       o->flags |= TMP_MARK;
                }
-               /* TMP_MARK is a general purpose flag that can
-                * be used locally, but the user should clean
-                * things up after it is done with them.
-                */
-               for (parents = commit->parents;
-                    parents;
-                    parents = parents->next)
-                       parents->item->object.flags &= ~TMP_MARK;
        }
        if (revs.commit_format == CMIT_FMT_ONELINE)
                putchar(' ');
@@ -92,13 +80,15 @@ static void show_commit(struct commit *commit)
                putchar('\n');
 
        if (revs.verbose_header) {
-               static char pretty_header[16384];
+               char *buf = NULL;
+               unsigned long buflen = 0;
                pretty_print_commit(revs.commit_format, commit, ~0,
-                                   pretty_header, sizeof(pretty_header),
+                                   &buf, &buflen,
                                    revs.abbrev, NULL, NULL, revs.date_mode);
-               printf("%s%c", pretty_header, hdr_termination);
+               printf("%s%c", buf, hdr_termination);
+               free(buf);
        }
-       fflush(stdout);
+       maybe_flush_or_die(stdout, "stdout");
        if (commit->parents) {
                free_commit_list(commit->parents);
                commit->parents = NULL;
index 37addb25fafbedba9bad6e99746ee65bacdee7d3..8d78b69c902a99738cfc337222ba71493ff45370 100644 (file)
@@ -321,6 +321,13 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                        }
                        if (!strcmp(arg, "--show-cdup")) {
                                const char *pfx = prefix;
+                               if (!is_inside_work_tree()) {
+                                       const char *work_tree =
+                                               get_git_work_tree();
+                                       if (work_tree)
+                                               printf("%s\n", work_tree);
+                                       continue;
+                               }
                                while (pfx) {
                                        pfx = strchr(pfx, '/');
                                        if (pfx) {
@@ -352,6 +359,16 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                                : "false");
                                continue;
                        }
+                       if (!strcmp(arg, "--is-inside-work-tree")) {
+                               printf("%s\n", is_inside_work_tree() ? "true"
+                                               : "false");
+                               continue;
+                       }
+                       if (!strcmp(arg, "--is-bare-repository")) {
+                               printf("%s\n", is_bare_repository() ? "true"
+                                               : "false");
+                               continue;
+                       }
                        if (!prefixcmp(arg, "--since=")) {
                                show_datestring("--max-age=", arg+8);
                                continue;
index ea2f15b977a8b62189755ae89e3ee704b45afae8..499bbe7343a635f1c7fc024ed6a1436042dcb8ac 100644 (file)
@@ -25,7 +25,7 @@ static const char *cherry_pick_usage = "git-cherry-pick [--edit] [-n] [-r] [-x]
 
 static int edit;
 static int replay;
-enum { REVERT, CHERRY_PICK } action;
+static enum { REVERT, CHERRY_PICK } action;
 static int no_commit;
 static struct commit *commit;
 static int needed_deref;
@@ -45,8 +45,10 @@ static void parse_options(int argc, const char **argv)
        if (argc < 2)
                usage(usage_str);
 
-       for (i = 1; i < argc - 1; i++) {
+       for (i = 1; i < argc; i++) {
                arg = argv[i];
+               if (arg[0] != '-')
+                       break;
                if (!strcmp(arg, "-n") || !strcmp(arg, "--no-commit"))
                        no_commit = 1;
                else if (!strcmp(arg, "-e") || !strcmp(arg, "--edit"))
@@ -59,7 +61,8 @@ static void parse_options(int argc, const char **argv)
                else if (strcmp(arg, "-r"))
                        usage(usage_str);
        }
-
+       if (i != argc - 1)
+               usage(usage_str);
        arg = argv[argc - 1];
        if (get_sha1(arg, sha1))
                die ("Cannot find '%s'", arg);
@@ -104,7 +107,7 @@ static char *get_oneline(const char *message)
        return result;
 }
 
-char *get_encoding(const char *message)
+static char *get_encoding(const char *message)
 {
        const char *p = message, *eol;
 
@@ -126,7 +129,7 @@ char *get_encoding(const char *message)
        return NULL;
 }
 
-struct lock_file msg_file;
+static struct lock_file msg_file;
 static int msg_fd;
 
 static void add_to_msg(const char *string)
index 4a0bd93c8b3b644fb86ce05686b09d79b180bffc..9a808c1bf96ec52d836b0a69816f6118f0291a45 100644 (file)
@@ -46,7 +46,7 @@ static int remove_file(const char *name)
        return ret;
 }
 
-static int check_local_mod(unsigned char *head)
+static int check_local_mod(unsigned char *head, int index_only)
 {
        /* items in list are already sorted in the cache order,
         * so we could do this a lot more efficiently by using
@@ -65,6 +65,8 @@ static int check_local_mod(unsigned char *head)
                const char *name = list.name[i];
                unsigned char sha1[20];
                unsigned mode;
+               int local_changes = 0;
+               int staged_changes = 0;
 
                pos = cache_name_pos(name, strlen(name));
                if (pos < 0)
@@ -87,14 +89,32 @@ static int check_local_mod(unsigned char *head)
                        continue;
                }
                if (ce_match_stat(ce, &st, 0))
-                       errs = error("'%s' has local modifications "
-                                    "(hint: try -f)", ce->name);
+                       local_changes = 1;
                if (no_head
                     || get_tree_entry(head, name, sha1, &mode)
                     || ce->ce_mode != create_ce_mode(mode)
                     || hashcmp(ce->sha1, sha1))
-                       errs = error("'%s' has changes staged in the index "
-                                    "(hint: try -f)", name);
+                       staged_changes = 1;
+
+               if (local_changes && staged_changes)
+                       errs = error("'%s' has staged content different "
+                                    "from both the file and the HEAD\n"
+                                    "(use -f to force removal)", name);
+               else if (!index_only) {
+                       /* It's not dangerous to git-rm --cached a
+                        * file if the index matches the file or the
+                        * HEAD, since it means the deleted content is
+                        * still available somewhere.
+                        */
+                       if (staged_changes)
+                               errs = error("'%s' has changes staged in the index\n"
+                                            "(use --cached to keep the file, "
+                                            "or -f to force removal)", name);
+                       if (local_changes)
+                               errs = error("'%s' has local modifications\n"
+                                            "(use --cached to keep the file, "
+                                            "or -f to force removal)", name);
+               }
        }
        return errs;
 }
@@ -192,7 +212,7 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                unsigned char sha1[20];
                if (get_sha1("HEAD", sha1))
                        hashclr(sha1);
-               if (check_local_mod(sha1))
+               if (check_local_mod(sha1, index_only))
                        exit(1);
        }
 
index 4b489b1214e1ae693a9b0b5166909800f5ca94ac..2db25c88bf648e4d5f2f41502fd29c5f7324e56b 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "wt-status.h"
 
index 8d3f742d432c966e5afe03e8d0628eb92a73920c..16af6199ab2bc8a663d16f78a5d461a5bee05bc7 100644 (file)
@@ -331,4 +331,3 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
 
        return 0;
 }
-
index c892f1f7a643b3d7e5c298837424a72cbc2c4f78..4fa87f6081f74fa667a415038ca64c5f4a7cf775 100644 (file)
@@ -259,17 +259,19 @@ static void join_revs(struct commit_list **list_p,
 
 static void show_one_commit(struct commit *commit, int no_name)
 {
-       char pretty[256], *cp;
+       char *pretty = NULL;
+       const char *pretty_str = "(unavailable)";
+       unsigned long pretty_len = 0;
        struct commit_name *name = commit->util;
-       if (commit->object.parsed)
+
+       if (commit->object.parsed) {
                pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
-                                   pretty, sizeof(pretty), 0, NULL, NULL, 0);
-       else
-               strcpy(pretty, "(unavailable)");
-       if (!prefixcmp(pretty, "[PATCH] "))
-               cp = pretty + 8;
-       else
-               cp = pretty;
+                                   &pretty, &pretty_len,
+                                   0, NULL, NULL, 0);
+               pretty_str = pretty;
+       }
+       if (!prefixcmp(pretty_str, "[PATCH] "))
+               pretty_str += 8;
 
        if (!no_name) {
                if (name && name->head_name) {
@@ -286,7 +288,8 @@ static void show_one_commit(struct commit *commit, int no_name)
                        printf("[%s] ",
                               find_unique_abbrev(commit->object.sha1, 7));
        }
-       puts(cp);
+       puts(pretty_str);
+       free(pretty);
 }
 
 static char *ref_name[MAX_REVS + 1];
index 9463ff0e69b15fc0e544259e64960bc942a98368..65051d14fde44c14d12099df656ac423bc1c347e 100644 (file)
@@ -1,3 +1,4 @@
+#include "builtin.h"
 #include "cache.h"
 #include "refs.h"
 #include "object.h"
index f0d4d9e2d10446a229094c991363f5a17ce4c666..916355ca5d04ec571d4100c98b969b693c830a18 100644 (file)
@@ -1,58 +1,99 @@
 #include "builtin.h"
+#include "cache.h"
 
 /*
- * Remove empty lines from the beginning and end.
+ * Returns the length of a line, without trailing spaces.
  *
- * Turn multiple consecutive empty lines into just one
- * empty line.  Return true if it is an incomplete line.
+ * If the line ends with newline, it will be removed too.
  */
-static int cleanup(char *line)
+static size_t cleanup(char *line, size_t len)
 {
-       int len = strlen(line);
+       if (len) {
+               if (line[len - 1] == '\n')
+                       len--;
 
-       if (len && line[len-1] == '\n') {
-               if (len == 1)
-                       return 0;
-               do {
-                       unsigned char c = line[len-2];
+               while (len) {
+                       unsigned char c = line[len - 1];
                        if (!isspace(c))
                                break;
-                       line[len-2] = '\n';
                        len--;
-                       line[len] = 0;
-               } while (len > 1);
-               return 0;
+               }
        }
-       return 1;
+       return len;
 }
 
-void stripspace(FILE *in, FILE *out)
+/*
+ * Remove empty lines from the beginning and end
+ * and also trailing spaces from every line.
+ *
+ * Note that the buffer will not be NUL-terminated.
+ *
+ * Turn multiple consecutive empty lines between paragraphs
+ * into just one empty line.
+ *
+ * If the input has only empty lines and spaces,
+ * no output will be produced.
+ *
+ * If last line has a newline at the end, it will be removed.
+ *
+ * Enable skip_comments to skip every line starting with "#".
+ */
+size_t stripspace(char *buffer, size_t length, int skip_comments)
 {
        int empties = -1;
-       int incomplete = 0;
-       char line[1024];
+       size_t i, j, len, newlen;
+       char *eol;
 
-       while (fgets(line, sizeof(line), in)) {
-               incomplete = cleanup(line);
+       for (i = j = 0; i < length; i += len, j += newlen) {
+               eol = memchr(buffer + i, '\n', length - i);
+               len = eol ? eol - (buffer + i) + 1 : length - i;
+
+               if (skip_comments && len && buffer[i] == '#') {
+                       newlen = 0;
+                       continue;
+               }
+               newlen = cleanup(buffer + i, len);
 
                /* Not just an empty line? */
-               if (line[0] != '\n') {
+               if (newlen) {
+                       if (empties != -1)
+                               buffer[j++] = '\n';
                        if (empties > 0)
-                               fputc('\n', out);
+                               buffer[j++] = '\n';
                        empties = 0;
-                       fputs(line, out);
+                       memmove(buffer + j, buffer + i, newlen);
                        continue;
                }
                if (empties < 0)
                        continue;
                empties++;
        }
-       if (incomplete)
-               fputc('\n', out);
+
+       return j;
 }
 
 int cmd_stripspace(int argc, const char **argv, const char *prefix)
 {
-       stripspace(stdin, stdout);
+       char *buffer;
+       unsigned long size;
+       int strip_comments = 0;
+
+       if (argc > 1 && (!strcmp(argv[1], "-s") ||
+                               !strcmp(argv[1], "--strip-comments")))
+               strip_comments = 1;
+
+       size = 1024;
+       buffer = xmalloc(size);
+       if (read_fd(0, &buffer, &size)) {
+               free(buffer);
+               die("could not read the input");
+       }
+
+       size = stripspace(buffer, size, strip_comments);
+       write_or_die(1, buffer, size);
+       if (size)
+               putc('\n', stdout);
+
+       free(buffer);
        return 0;
 }
index d41b40640b96e7d5568174c7d16088c29d361d42..9eb95e50da5656bc66e9db4d8cd23ef327a5ca8e 100644 (file)
@@ -43,8 +43,6 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
                        msg = argv[1];
                        if (!*msg)
                                die("Refusing to perform update with empty message");
-                       if (strchr(msg, '\n'))
-                               die("Refusing to perform update with \\n in message");
                }
                else if (!strcmp("--", arg)) {
                        argc--;
diff --git a/builtin-tag.c b/builtin-tag.c
new file mode 100644 (file)
index 0000000..d6d38ad
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ * Builtin "git tag"
+ *
+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
+ *                    Carlos Rica <jasampler@gmail.com>
+ * Based on git-tag.sh and mktag.c by Linus Torvalds.
+ */
+
+#include "cache.h"
+#include "builtin.h"
+#include "refs.h"
+#include "tag.h"
+#include "run-command.h"
+
+static const char builtin_tag_usage[] =
+  "git-tag [-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg> | -F <file>] <tagname> [<head>]";
+
+static char signingkey[1000];
+
+static void launch_editor(const char *path, char **buffer, unsigned long *len)
+{
+       const char *editor, *terminal;
+       struct child_process child;
+       const char *args[3];
+       int fd;
+
+       editor = getenv("GIT_EDITOR");
+       if (!editor && editor_program)
+               editor = editor_program;
+       if (!editor)
+               editor = getenv("VISUAL");
+       if (!editor)
+               editor = getenv("EDITOR");
+
+       terminal = getenv("TERM");
+       if (!editor && (!terminal || !strcmp(terminal, "dumb"))) {
+               fprintf(stderr,
+               "Terminal is dumb but no VISUAL nor EDITOR defined.\n"
+               "Please supply the message using either -m or -F option.\n");
+               exit(1);
+       }
+
+       if (!editor)
+               editor = "vi";
+
+       memset(&child, 0, sizeof(child));
+       child.argv = args;
+       args[0] = editor;
+       args[1] = path;
+       args[2] = NULL;
+
+       if (run_command(&child))
+               die("There was a problem with the editor %s.", editor);
+
+       fd = open(path, O_RDONLY);
+       if (fd < 0)
+               die("could not open '%s': %s", path, strerror(errno));
+       if (read_fd(fd, buffer, len)) {
+               free(*buffer);
+               die("could not read message file '%s': %s",
+                                               path, strerror(errno));
+       }
+       close(fd);
+}
+
+struct tag_filter {
+       const char *pattern;
+       int lines;
+};
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int show_reference(const char *refname, const unsigned char *sha1,
+                         int flag, void *cb_data)
+{
+       struct tag_filter *filter = cb_data;
+
+       if (!fnmatch(filter->pattern, refname, 0)) {
+               int i;
+               unsigned long size;
+               enum object_type type;
+               char *buf, *sp, *eol;
+               size_t len;
+
+               if (!filter->lines) {
+                       printf("%s\n", refname);
+                       return 0;
+               }
+               printf("%-15s ", refname);
+
+               sp = buf = read_sha1_file(sha1, &type, &size);
+               if (!buf)
+                       return 0;
+               if (!size) {
+                       free(buf);
+                       return 0;
+               }
+               /* skip header */
+               while (sp + 1 < buf + size &&
+                               !(sp[0] == '\n' && sp[1] == '\n'))
+                       sp++;
+               /* only take up to "lines" lines, and strip the signature */
+               for (i = 0, sp += 2;
+                               i < filter->lines && sp < buf + size &&
+                               prefixcmp(sp, PGP_SIGNATURE "\n");
+                               i++) {
+                       if (i)
+                               printf("\n    ");
+                       eol = memchr(sp, '\n', size - (sp - buf));
+                       len = eol ? eol - sp : size - (sp - buf);
+                       fwrite(sp, len, 1, stdout);
+                       if (!eol)
+                               break;
+                       sp = eol + 1;
+               }
+               putchar('\n');
+               free(buf);
+       }
+
+       return 0;
+}
+
+static int list_tags(const char *pattern, int lines)
+{
+       struct tag_filter filter;
+       char *newpattern;
+
+       if (pattern == NULL)
+               pattern = "";
+
+       /* prepend/append * to the shell pattern: */
+       newpattern = xmalloc(strlen(pattern) + 3);
+       sprintf(newpattern, "*%s*", pattern);
+
+       filter.pattern = newpattern;
+       filter.lines = lines;
+
+       for_each_tag_ref(show_reference, (void *) &filter);
+
+       free(newpattern);
+
+       return 0;
+}
+
+typedef int (*each_tag_name_fn)(const char *name, const char *ref,
+                               const unsigned char *sha1);
+
+static int for_each_tag_name(const char **argv, each_tag_name_fn fn)
+{
+       const char **p;
+       char ref[PATH_MAX];
+       int had_error = 0;
+       unsigned char sha1[20];
+
+       for (p = argv; *p; p++) {
+               if (snprintf(ref, sizeof(ref), "refs/tags/%s", *p)
+                                       >= sizeof(ref)) {
+                       error("tag name too long: %.*s...", 50, *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (!resolve_ref(ref, sha1, 1, NULL)) {
+                       error("tag '%s' not found.", *p);
+                       had_error = 1;
+                       continue;
+               }
+               if (fn(*p, ref, sha1))
+                       had_error = 1;
+       }
+       return had_error;
+}
+
+static int delete_tag(const char *name, const char *ref,
+                               const unsigned char *sha1)
+{
+       if (delete_ref(ref, sha1))
+               return 1;
+       printf("Deleted tag '%s'\n", name);
+       return 0;
+}
+
+static int verify_tag(const char *name, const char *ref,
+                               const unsigned char *sha1)
+{
+       const char *argv_verify_tag[] = {"git-verify-tag",
+                                       "-v", "SHA1_HEX", NULL};
+       argv_verify_tag[2] = sha1_to_hex(sha1);
+
+       if (run_command_v_opt(argv_verify_tag, 0))
+               return error("could not verify the tag '%s'", name);
+       return 0;
+}
+
+static ssize_t do_sign(char *buffer, size_t size, size_t max)
+{
+       struct child_process gpg;
+       const char *args[4];
+       char *bracket;
+       int len;
+
+       if (!*signingkey) {
+               if (strlcpy(signingkey, git_committer_info(1),
+                               sizeof(signingkey)) > sizeof(signingkey) - 1)
+                       return error("committer info too long.");
+               bracket = strchr(signingkey, '>');
+               if (bracket)
+                       bracket[1] = '\0';
+       }
+
+       memset(&gpg, 0, sizeof(gpg));
+       gpg.argv = args;
+       gpg.in = -1;
+       gpg.out = -1;
+       args[0] = "gpg";
+       args[1] = "-bsau";
+       args[2] = signingkey;
+       args[3] = NULL;
+
+       if (start_command(&gpg))
+               return error("could not run gpg.");
+
+       write_or_die(gpg.in, buffer, size);
+       close(gpg.in);
+       gpg.close_in = 0;
+       len = read_in_full(gpg.out, buffer + size, max - size);
+
+       finish_command(&gpg);
+
+       if (len == max - size)
+               return error("could not read the entire signature from gpg.");
+
+       return size + len;
+}
+
+static const char tag_template[] =
+       "\n"
+       "#\n"
+       "# Write a tag message\n"
+       "#\n";
+
+static int git_tag_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "user.signingkey")) {
+               if (!value)
+                       die("user.signingkey without value");
+               if (strlcpy(signingkey, value, sizeof(signingkey))
+                                               >= sizeof(signingkey))
+                       die("user.signingkey value too long");
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
+#define MAX_SIGNATURE_LENGTH 1024
+/* message must be NULL or allocated, it will be reallocated and freed */
+static void create_tag(const unsigned char *object, const char *tag,
+                      char *message, int sign, unsigned char *result)
+{
+       enum object_type type;
+       char header_buf[1024], *buffer = NULL;
+       int header_len, max_size;
+       unsigned long size = 0;
+
+       type = sha1_object_info(object, NULL);
+       if (type <= OBJ_NONE)
+           die("bad object type.");
+
+       header_len = snprintf(header_buf, sizeof(header_buf),
+                         "object %s\n"
+                         "type %s\n"
+                         "tag %s\n"
+                         "tagger %s\n\n",
+                         sha1_to_hex(object),
+                         typename(type),
+                         tag,
+                         git_committer_info(1));
+
+       if (header_len > sizeof(header_buf) - 1)
+               die("tag header too big.");
+
+       if (!message) {
+               char *path;
+               int fd;
+
+               /* write the template message before editing: */
+               path = xstrdup(git_path("TAG_EDITMSG"));
+               fd = open(path, O_CREAT | O_TRUNC | O_WRONLY, 0600);
+               if (fd < 0)
+                       die("could not create file '%s': %s",
+                                               path, strerror(errno));
+               write_or_die(fd, tag_template, strlen(tag_template));
+               close(fd);
+
+               launch_editor(path, &buffer, &size);
+
+               unlink(path);
+               free(path);
+       }
+       else {
+               buffer = message;
+               size = strlen(message);
+       }
+
+       size = stripspace(buffer, size, 1);
+
+       if (!message && !size)
+               die("no tag message?");
+
+       /* insert the header and add the '\n' if needed: */
+       max_size = header_len + size + (sign ? MAX_SIGNATURE_LENGTH : 0) + 1;
+       buffer = xrealloc(buffer, max_size);
+       if (size)
+               buffer[size++] = '\n';
+       memmove(buffer + header_len, buffer, size);
+       memcpy(buffer, header_buf, header_len);
+       size += header_len;
+
+       if (sign) {
+               size = do_sign(buffer, size, max_size);
+               if (size < 0)
+                       die("unable to sign the tag");
+       }
+
+       if (write_sha1_file(buffer, size, tag_type, result) < 0)
+               die("unable to write tag file");
+       free(buffer);
+}
+
+int cmd_tag(int argc, const char **argv, const char *prefix)
+{
+       unsigned char object[20], prev[20];
+       int annotate = 0, sign = 0, force = 0, lines = 0;
+       char *message = NULL;
+       char ref[PATH_MAX];
+       const char *object_ref, *tag;
+       int i;
+       struct ref_lock *lock;
+
+       git_config(git_tag_config);
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (arg[0] != '-')
+                       break;
+               if (!strcmp(arg, "-a")) {
+                       annotate = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-s")) {
+                       annotate = 1;
+                       sign = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-f")) {
+                       force = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-n")) {
+                       if (i + 1 == argc || *argv[i + 1] == '-')
+                               /* no argument */
+                               lines = 1;
+                       else
+                               lines = isdigit(*argv[++i]) ?
+                                       atoi(argv[i]) : 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-m")) {
+                       annotate = 1;
+                       i++;
+                       if (i == argc)
+                               die("option -m needs an argument.");
+                       if (message)
+                               die("only one -F or -m option is allowed.");
+                       message = xstrdup(argv[i]);
+                       continue;
+               }
+               if (!strcmp(arg, "-F")) {
+                       unsigned long len;
+                       int fd;
+
+                       annotate = 1;
+                       i++;
+                       if (i == argc)
+                               die("option -F needs an argument.");
+                       if (message)
+                               die("only one -F or -m option is allowed.");
+
+                       if (!strcmp(argv[i], "-"))
+                               fd = 0;
+                       else {
+                               fd = open(argv[i], O_RDONLY);
+                               if (fd < 0)
+                                       die("could not open '%s': %s",
+                                               argv[i], strerror(errno));
+                       }
+                       len = 1024;
+                       message = xmalloc(len);
+                       if (read_fd(fd, &message, &len)) {
+                               free(message);
+                               die("cannot read %s", argv[i]);
+                       }
+                       continue;
+               }
+               if (!strcmp(arg, "-u")) {
+                       annotate = 1;
+                       sign = 1;
+                       i++;
+                       if (i == argc)
+                               die("option -u needs an argument.");
+                       if (strlcpy(signingkey, argv[i], sizeof(signingkey))
+                                                       >= sizeof(signingkey))
+                               die("argument to option -u too long");
+                       continue;
+               }
+               if (!strcmp(arg, "-l"))
+                       return list_tags(argv[i + 1], lines);
+               if (!strcmp(arg, "-d"))
+                       return for_each_tag_name(argv + i + 1, delete_tag);
+               if (!strcmp(arg, "-v"))
+                       return for_each_tag_name(argv + i + 1, verify_tag);
+               usage(builtin_tag_usage);
+       }
+
+       if (i == argc) {
+               if (annotate)
+                       usage(builtin_tag_usage);
+               return list_tags(NULL, lines);
+       }
+       tag = argv[i++];
+
+       object_ref = i < argc ? argv[i] : "HEAD";
+       if (i + 1 < argc)
+               die("too many params");
+
+       if (get_sha1(object_ref, object))
+               die("Failed to resolve '%s' as a valid ref.", object_ref);
+
+       if (snprintf(ref, sizeof(ref), "refs/tags/%s", tag) > sizeof(ref) - 1)
+               die("tag name too long: %.*s...", 50, tag);
+       if (check_ref_format(ref))
+               die("'%s' is not a valid tag name.", tag);
+
+       if (!resolve_ref(ref, prev, 1, NULL))
+               hashclr(prev);
+       else if (!force)
+               die("tag '%s' already exists", tag);
+
+       if (annotate)
+               create_tag(object, tag, message, sign, object);
+
+       lock = lock_any_ref_for_update(ref, prev, 0);
+       if (!lock)
+               die("%s: cannot lock the ref", ref);
+       if (write_ref_sha1(lock, object, NULL) < 0)
+               die("%s: cannot update the ref", ref);
+
+       return 0;
+}
index 8f9899178b1755ee0acd4a34ee185a4f54fb4219..a7a4574f2bff2a7db4a1c25aa4a514ad99760381 100644 (file)
@@ -86,9 +86,15 @@ static int process_lstat_error(const char *path, int err)
 
 static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
 {
-       int option, size = cache_entry_size(len);
-       struct cache_entry *ce = xcalloc(1, size);
+       int option, size;
+       struct cache_entry *ce;
+
+       /* Was the old index entry already up-to-date? */
+       if (old && !ce_stage(old) && !ce_match_stat(old, st, 0))
+               return 0;
 
+       size = cache_entry_size(len);
+       ce = xcalloc(1, size);
        memcpy(ce->name, path, len);
        ce->ce_flags = htons(len);
        fill_stat_cache_info(ce, st);
@@ -134,7 +140,7 @@ static int process_directory(const char *path, int len, struct stat *st)
        /* Exact match: file or existing gitlink */
        if (pos >= 0) {
                struct cache_entry *ce = active_cache[pos];
-               if (S_ISDIRLNK(ntohl(ce->ce_mode))) {
+               if (S_ISGITLINK(ntohl(ce->ce_mode))) {
 
                        /* Do nothing to the index if there is no HEAD! */
                        if (resolve_gitlink_ref(path, "HEAD", sha1) < 0)
@@ -178,7 +184,7 @@ static int process_file(const char *path, int len, struct stat *st)
        int pos = cache_name_pos(path, len);
        struct cache_entry *ce = pos < 0 ? NULL : active_cache[pos];
 
-       if (ce && S_ISDIRLNK(ntohl(ce->ce_mode)))
+       if (ce && S_ISGITLINK(ntohl(ce->ce_mode)))
                return error("%s is already a gitlink, not replacing", path);
 
        return add_one_path(ce, path, len, st);
index 5ee960bf41c4518b6c5530acb123a7ed668538fc..8339cf19e2b68a19e88c1a014c52672bc6f0c7b5 100644 (file)
@@ -3,16 +3,17 @@
 #include "builtin.h"
 
 static const char git_update_ref_usage[] =
-"git-update-ref [-m <reason>] (-d <refname> <value> | <refname> <value> [<oldval>])";
+"git-update-ref [-m <reason>] (-d <refname> <value> | [--no-deref] <refname> <value> [<oldval>])";
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
        const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
        struct ref_lock *lock;
        unsigned char sha1[20], oldsha1[20];
-       int i, delete;
+       int i, delete, ref_flags;
 
        delete = 0;
+       ref_flags = 0;
        git_config(git_default_config);
 
        for (i = 1; i < argc; i++) {
@@ -22,14 +23,16 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                        msg = argv[++i];
                        if (!*msg)
                                die("Refusing to perform update with empty message.");
-                       if (strchr(msg, '\n'))
-                               die("Refusing to perform update with \\n in message.");
                        continue;
                }
                if (!strcmp("-d", argv[i])) {
                        delete = 1;
                        continue;
                }
+               if (!strcmp("--no-deref", argv[i])) {
+                       ref_flags |= REF_NODEREF;
+                       continue;
+               }
                if (!refname) {
                        refname = argv[i];
                        continue;
@@ -59,7 +62,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        if (oldval && *oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
-       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
+       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, ref_flags);
        if (!lock)
                die("%s: cannot lock the ref", refname);
        if (write_ref_sha1(lock, sha1, msg) < 0)
diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c
new file mode 100644 (file)
index 0000000..dfcfcd0
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Builtin "git verify-tag"
+ *
+ * Copyright (c) 2007 Carlos Rica <jasampler@gmail.com>
+ *
+ * Based on git-verify-tag.sh
+ */
+#include "cache.h"
+#include "builtin.h"
+#include "tag.h"
+#include "run-command.h"
+#include <signal.h>
+
+static const char builtin_verify_tag_usage[] =
+               "git-verify-tag [-v|--verbose] <tag>...";
+
+#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
+
+static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
+{
+       struct child_process gpg;
+       const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
+       char path[PATH_MAX], *eol;
+       size_t len;
+       int fd, ret;
+
+       fd = git_mkstemp(path, PATH_MAX, ".git_vtag_tmpXXXXXX");
+       if (fd < 0)
+               return error("could not create temporary file '%s': %s",
+                                               path, strerror(errno));
+       if (write_in_full(fd, buf, size) < 0)
+               return error("failed writing temporary file '%s': %s",
+                                               path, strerror(errno));
+       close(fd);
+
+       /* find the length without signature */
+       len = 0;
+       while (len < size && prefixcmp(buf + len, PGP_SIGNATURE "\n")) {
+               eol = memchr(buf + len, '\n', size - len);
+               len += eol ? eol - (buf + len) + 1 : size - len;
+       }
+       if (verbose)
+               write_in_full(1, buf, len);
+
+       memset(&gpg, 0, sizeof(gpg));
+       gpg.argv = args_gpg;
+       gpg.in = -1;
+       gpg.out = 1;
+       args_gpg[2] = path;
+       if (start_command(&gpg))
+               return error("could not run gpg.");
+
+       write_in_full(gpg.in, buf, len);
+       close(gpg.in);
+       gpg.close_in = 0;
+       ret = finish_command(&gpg);
+
+       unlink(path);
+
+       return ret;
+}
+
+static int verify_tag(const char *name, int verbose)
+{
+       enum object_type type;
+       unsigned char sha1[20];
+       char *buf;
+       unsigned long size;
+       int ret;
+
+       if (get_sha1(name, sha1))
+               return error("tag '%s' not found.", name);
+
+       type = sha1_object_info(sha1, NULL);
+       if (type != OBJ_TAG)
+               return error("%s: cannot verify a non-tag object of type %s.",
+                               name, typename(type));
+
+       buf = read_sha1_file(sha1, &type, &size);
+       if (!buf)
+               return error("%s: unable to read file.", name);
+
+       ret = run_gpg_verify(buf, size, verbose);
+
+       free(buf);
+       return ret;
+}
+
+int cmd_verify_tag(int argc, const char **argv, const char *prefix)
+{
+       int i = 1, verbose = 0, had_error = 0;
+
+       git_config(git_default_config);
+
+       if (argc == 1)
+               usage(builtin_verify_tag_usage);
+
+       if (!strcmp(argv[i], "-v") || !strcmp(argv[i], "--verbose")) {
+               verbose = 1;
+               i++;
+       }
+
+       /* sometimes the program was terminated because this signal
+        * was received in the process of writing the gpg input: */
+       signal(SIGPIPE, SIG_IGN);
+       while (i < argc)
+               if (verify_tag(argv[i++], verbose))
+                       had_error = 1;
+       return had_error;
+}
index 391de53972ebf77d2e08f1b405969e065bd8b371..88f34ba7d6e2426fae5169eb28b12e93e72082eb 100644 (file)
@@ -52,6 +52,8 @@ int write_tree(unsigned char *sha1, int missing_ok, const char *prefix)
        if (prefix) {
                struct cache_tree *subtree =
                        cache_tree_find(active_cache_tree, prefix);
+               if (!subtree)
+                       die("git-write-tree: prefix %s not found", prefix);
                hashcpy(sha1, subtree->sha1);
        }
        else
index d3f3a7496e1c1adbe3f8bae36603fafee374c8c5..bb720004afeb632005a5622ecb9cd25f95f5caa5 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -7,9 +7,7 @@ extern const char git_version_string[];
 extern const char git_usage_string[];
 
 extern void help_unknown_cmd(const char *cmd);
-extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch);
-extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip);
-extern void stripspace(FILE *in, FILE *out);
+extern size_t stripspace(char *buffer, size_t length, int skip_comments);
 extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix);
 extern void prune_packed_objects(int);
 
@@ -72,12 +70,14 @@ extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
 extern int cmd_stripspace(int argc, const char **argv, const char *prefix);
 extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix);
+extern int cmd_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_tar_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix);
 extern int cmd_update_index(int argc, const char **argv, const char *prefix);
 extern int cmd_update_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_archive(int argc, const char **argv, const char *prefix);
 extern int cmd_upload_tar(int argc, const char **argv, const char *prefix);
+extern int cmd_verify_tag(int argc, const char **argv, const char *prefix);
 extern int cmd_version(int argc, const char **argv, const char *prefix);
 extern int cmd_whatchanged(int argc, const char **argv, const char *prefix);
 extern int cmd_write_tree(int argc, const char **argv, const char *prefix);
index 6369cc7c536ba7b82a6afcb191628beefe889b72..077f03436941e9c0bf31d3bb2002c1e36b8817b9 100644 (file)
@@ -326,7 +326,7 @@ static int update_one(struct cache_tree *it,
                        mode = ntohl(ce->ce_mode);
                        entlen = pathlen - baselen;
                }
-               if (mode != S_IFDIRLNK && !missing_ok && !has_sha1_file(sha1))
+               if (mode != S_IFGITLINK && !missing_ok && !has_sha1_file(sha1))
                        return error("invalid object %s", sha1_to_hex(sha1));
 
                if (!ce->ce_mode)
@@ -478,7 +478,7 @@ static struct cache_tree *read_one(const char **buffer, unsigned long *size_p)
        if (0 <= it->entry_count) {
                if (size < 20)
                        goto free_return;
-               hashcpy(it->sha1, (unsigned char*)buf);
+               hashcpy(it->sha1, (const unsigned char*)buf);
                buf += 20;
                size -= 20;
        }
diff --git a/cache.h b/cache.h
index 5dff2f1d73eb1a41f664d959d18e6f2c0e102e64..c7e00e7b0528d640af9e70a2d84455e7cf727993 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -40,8 +40,8 @@
  * happens that everybody shares the same bit representation
  * in the UNIX world (and apparently wider too..)
  */
-#define S_IFDIRLNK     0160000
-#define S_ISDIRLNK(m)  (((m) & S_IFMT) == S_IFDIRLNK)
+#define S_IFGITLINK    0160000
+#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
 
 /*
  * Intensive research over the course of many years has shown that
@@ -123,8 +123,8 @@ static inline unsigned int create_ce_mode(unsigned int mode)
 {
        if (S_ISLNK(mode))
                return htonl(S_IFLNK);
-       if (S_ISDIR(mode) || S_ISDIRLNK(mode))
-               return htonl(S_IFDIRLNK);
+       if (S_ISDIR(mode) || S_ISGITLINK(mode))
+               return htonl(S_IFGITLINK);
        return htonl(S_IFREG | ce_permissions(mode));
 }
 static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned int mode)
@@ -142,7 +142,7 @@ static inline unsigned int ce_mode_from_stat(struct cache_entry *ce, unsigned in
 }
 #define canon_mode(mode) \
        (S_ISREG(mode) ? (S_IFREG | ce_permissions(mode)) : \
-       S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFDIRLNK)
+       S_ISLNK(mode) ? S_IFLNK : S_ISDIR(mode) ? S_IFDIR : S_IFGITLINK)
 
 #define cache_entry_size(len) ((offsetof(struct cache_entry,name) + (len) + 8) & ~7)
 
@@ -173,7 +173,7 @@ extern struct index_state the_index;
 #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
 #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
 #define add_file_to_cache(path, verbose) add_file_to_index(&the_index, (path), (verbose))
-#define refresh_cache(flags) refresh_index(&the_index, flags)
+#define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL)
 #define ce_match_stat(ce, st, really) ie_match_stat(&the_index, (ce), (st), (really))
 #define ce_modified(ce, st, really) ie_modified(&the_index, (ce), (st), (really))
 #endif
@@ -192,6 +192,7 @@ enum object_type {
 };
 
 #define GIT_DIR_ENVIRONMENT "GIT_DIR"
+#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
 #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
 #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
@@ -207,11 +208,15 @@ enum object_type {
 extern int is_bare_repository_cfg;
 extern int is_bare_repository(void);
 extern int is_inside_git_dir(void);
+extern char *git_work_tree_cfg;
+extern int is_inside_work_tree(void);
 extern const char *get_git_dir(void);
 extern char *get_object_directory(void);
 extern char *get_refs_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
+extern int set_git_dir(const char *path);
+extern const char *get_git_work_tree(void);
 
 #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
 
@@ -225,6 +230,24 @@ extern void verify_non_filename(const char *prefix, const char *name);
 
 #define alloc_nr(x) (((x)+16)*3/2)
 
+/*
+ * Realloc the buffer pointed at by variable 'x' so that it can hold
+ * at least 'nr' entries; the number of entries currently allocated
+ * is 'alloc', using the standard growing factor alloc_nr() macro.
+ *
+ * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
+ */
+#define ALLOC_GROW(x, nr, alloc) \
+       do { \
+               if ((nr) > alloc) { \
+                       if (alloc_nr(alloc) < (nr)) \
+                               alloc = (nr); \
+                       else \
+                               alloc = alloc_nr(alloc); \
+                       x = xrealloc((x), alloc * sizeof(*(x))); \
+               } \
+       } while(0)
+
 /* Initialize and use the cache information */
 extern int read_index(struct index_state *);
 extern int read_index_from(struct index_state *, const char *path);
@@ -235,6 +258,7 @@ extern int index_name_pos(struct index_state *, const char *name, int namelen);
 #define ADD_CACHE_OK_TO_ADD 1          /* Ok to add */
 #define ADD_CACHE_OK_TO_REPLACE 2      /* Ok to replace file/directory */
 #define ADD_CACHE_SKIP_DFCHECK 4       /* Ok to skip DF conflict checks */
+#define ADD_CACHE_JUST_APPEND 8                /* Append only; tree.c::read_tree() */
 extern int add_index_entry(struct index_state *, struct cache_entry *ce, int option);
 extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int really);
 extern int remove_index_entry_at(struct index_state *, int pos);
@@ -245,7 +269,7 @@ extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat
 extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
 extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
 extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
-extern int read_pipe(int fd, char** return_buf, unsigned long* return_size);
+extern int read_fd(int fd, char **return_buf, unsigned long *return_size);
 extern int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object);
 extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
 extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
@@ -254,7 +278,7 @@ extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
 #define REFRESH_UNMERGED       0x0002  /* allow unmerged */
 #define REFRESH_QUIET          0x0004  /* be quiet about it */
 #define REFRESH_IGNORE_MISSING 0x0008  /* ignore non-existent */
-extern int refresh_index(struct index_state *, unsigned int flags);
+extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen);
 
 struct lock_file {
        struct lock_file *next;
@@ -273,8 +297,8 @@ extern void rollback_lock_file(struct lock_file *);
 extern int delete_ref(const char *, const unsigned char *sha1);
 
 /* Environment bits from configuration mechanism */
-extern int use_legacy_headers;
 extern int trust_executable_bit;
+extern int quote_path_fully;
 extern int has_symlinks;
 extern int assume_unchanged;
 extern int prefer_symlink_refs;
@@ -283,6 +307,8 @@ extern int warn_ambiguous_refs;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
 extern int zlib_compression_level;
+extern int core_compression_level;
+extern int core_compression_seen;
 extern size_t packed_git_window_size;
 extern size_t packed_git_limit;
 extern size_t delta_base_cache_limit;
@@ -336,6 +362,11 @@ int git_config_perm(const char *var, const char *value);
 int adjust_shared_perm(const char *path);
 int safe_create_leading_directories(char *path);
 char *enter_repo(char *path, int strict);
+static inline int is_absolute_path(const char *path)
+{
+       return path[0] == '/';
+}
+const char *make_absolute_path(const char *path);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int sha1_object_info(const unsigned char *, unsigned long *);
@@ -353,8 +384,6 @@ extern int move_temp_to_file(const char *tmpfile, const char *filename);
 
 extern int has_sha1_pack(const unsigned char *sha1, const char **ignore);
 extern int has_sha1_file(const unsigned char *sha1);
-extern void *map_sha1_file(const unsigned char *sha1, unsigned long *);
-extern int legacy_loose_object(unsigned char *);
 
 extern int has_pack_file(const unsigned char *sha1);
 extern int has_pack_index(const unsigned char *sha1);
@@ -389,9 +418,16 @@ extern void *read_object_with_reference(const unsigned char *sha1,
                                        unsigned long *size,
                                        unsigned char *sha1_ret);
 
-enum date_mode { DATE_NORMAL = 0, DATE_RELATIVE, DATE_SHORT, DATE_LOCAL };
+enum date_mode {
+       DATE_NORMAL = 0,
+       DATE_RELATIVE,
+       DATE_SHORT,
+       DATE_LOCAL,
+       DATE_ISO8601,
+       DATE_RFC2822
+};
+
 const char *show_date(unsigned long time, int timezone, enum date_mode mode);
-const char *show_rfc2822_date(unsigned long time, int timezone);
 int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
@@ -463,11 +499,10 @@ struct ref {
 #define REF_HEADS      (1u << 1)
 #define REF_TAGS       (1u << 2)
 
-extern pid_t git_connect(int fd[2], char *url, const char *prog);
+#define CONNECT_VERBOSE       (1u << 0)
+extern pid_t git_connect(int fd[2], char *url, const char *prog, int flags);
 extern int finish_connect(pid_t pid);
 extern int path_match(const char *path, int nr, char **match);
-extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
-                     int nr_refspec, char **refspec, int all);
 extern int get_ack(int fd, unsigned char *result_sha1);
 extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags);
 extern int server_supports(const char *feature);
@@ -480,14 +515,15 @@ extern void prepare_packed_git(void);
 extern void reprepare_packed_git(void);
 extern void install_packed_git(struct packed_git *pack);
 
-extern struct packed_git *find_sha1_pack(const unsigned char *sha1, 
+extern struct packed_git *find_sha1_pack(const unsigned char *sha1,
                                         struct packed_git *packs);
 
 extern void pack_report(void);
+extern int open_pack_index(struct packed_git *);
 extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *);
 extern void unuse_pack(struct pack_window **);
 extern struct packed_git *add_packed_git(const char *, int, int);
-extern const unsigned char *nth_packed_object_sha1(const struct packed_git *, uint32_t);
+extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t);
 extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *);
 extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *);
 extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
@@ -501,7 +537,10 @@ typedef int (*config_fn_t)(const char *, const char *);
 extern int git_default_config(const char *, const char *);
 extern int git_config_from_file(config_fn_t fn, const char *);
 extern int git_config(config_fn_t fn);
+extern int git_parse_long(const char *, long *);
+extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_config_int(const char *, const char *);
+extern unsigned long git_config_ulong(const char *, const char *);
 extern int git_config_bool(const char *, const char *);
 extern int git_config_set(const char *, const char *);
 extern int git_config_set_multivar(const char *, const char *, const char *, int);
@@ -515,6 +554,8 @@ extern char git_default_name[MAX_GITNAME];
 extern const char *git_commit_encoding;
 extern const char *git_log_output_encoding;
 
+/* IO helper functions */
+extern void maybe_flush_or_die(FILE *, const char *);
 extern int copy_fd(int ifd, int ofd);
 extern int read_in_full(int fd, void *buf, size_t count);
 extern int write_in_full(int fd, const void *buf, size_t count);
@@ -524,9 +565,12 @@ extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char
 
 /* pager.c */
 extern void setup_pager(void);
+extern char *pager_program;
 extern int pager_in_use;
 extern int pager_use_color;
 
+extern char *editor_program;
+
 /* base85 */
 int decode_85(char *dst, const char *line, int linelen);
 void encode_85(char *buf, const unsigned char *data, int bytes);
index ea3ca5f950561a92fdd3be1a4ee4bbd726656118..ef622340a52afb3b31b1cdf678ae0a83fb85c923 100644 (file)
@@ -215,8 +215,7 @@ static void combine_diff(const unsigned char *parent, mmfile_t *result_file,
        parent_file.ptr = grab_blob(parent, &sz);
        parent_file.size = sz;
        xpp.flags = XDF_NEED_MINIMAL;
-       xecfg.ctxlen = 0;
-       xecfg.flags = 0;
+       memset(&xecfg, 0, sizeof(xecfg));
        ecb.outf = xdiff_outf;
        ecb.priv = &state;
        memset(&state, 0, sizeof(state));
index 5632e32685478dcce34e479e8e916b0ccd3a3b13..dc5a0643f3d52797f29706595355fca824d9feda 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -27,7 +27,7 @@ struct sort_node
 
 const char *commit_type = "commit";
 
-struct cmt_fmt_map {
+static struct cmt_fmt_map {
        const char *n;
        size_t cmp_len;
        enum cmit_fmt v;
@@ -148,7 +148,7 @@ static int commit_graft_pos(const unsigned char *sha1)
 int register_commit_graft(struct commit_graft *graft, int ignore_dups)
 {
        int pos = commit_graft_pos(graft->sha1);
-       
+
        if (0 <= pos) {
                if (ignore_dups)
                        free(graft);
@@ -406,7 +406,7 @@ struct commit_list * insert_by_date(struct commit *item, struct commit_list **li
        return commit_list_insert(item, pp);
 }
 
-       
+
 void sort_by_date(struct commit_list **list)
 {
        struct commit_list *ret = NULL;
@@ -529,6 +529,14 @@ static int add_rfc2047(char *buf, const char *line, int len,
        return bp - buf;
 }
 
+static unsigned long bound_rfc2047(unsigned long len, const char *encoding)
+{
+       /* upper bound of q encoded string of length 'len' */
+       unsigned long elen = strlen(encoding);
+
+       return len * 3 + elen + 100;
+}
+
 static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
                         const char *line, enum date_mode dmode,
                         const char *encoding)
@@ -577,7 +585,7 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
                break;
        case CMIT_FMT_EMAIL:
                ret += sprintf(buf + ret, "Date: %s\n",
-                              show_rfc2822_date(time, tz));
+                              show_date(time, tz, DATE_RFC2822));
                break;
        case CMIT_FMT_FULLER:
                ret += sprintf(buf + ret, "%sDate: %s\n", what,
@@ -713,7 +721,10 @@ static char *logmsg_reencode(const struct commit *commit,
        encoding = get_header(commit, "encoding");
        use_encoding = encoding ? encoding : utf8;
        if (!strcmp(use_encoding, output_encoding))
-               out = xstrdup(commit->buffer);
+               if (encoding) /* we'll strip encoding header later */
+                       out = xstrdup(commit->buffer);
+               else
+                       return NULL; /* nothing to do */
        else
                out = reencode_string(commit->buffer,
                                      output_encoding, use_encoding);
@@ -770,13 +781,14 @@ static void fill_person(struct interp *table, const char *msg, int len)
                        tz = -tz;
        }
 
-       interp_set_entry(table, 2, show_date(date, tz, 0));
-       interp_set_entry(table, 3, show_rfc2822_date(date, tz));
-       interp_set_entry(table, 4, show_date(date, tz, 1));
+       interp_set_entry(table, 2, show_date(date, tz, DATE_NORMAL));
+       interp_set_entry(table, 3, show_date(date, tz, DATE_RFC2822));
+       interp_set_entry(table, 4, show_date(date, tz, DATE_RELATIVE));
+       interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
 }
 
 static long format_commit_message(const struct commit *commit,
-               const char *msg, char *buf, unsigned long space)
+               const char *msg, char **buf_p, unsigned long *space_p)
 {
        struct interp table[] = {
                { "%H" },       /* commit hash */
@@ -791,12 +803,14 @@ static long format_commit_message(const struct commit *commit,
                { "%aD" },      /* author date, RFC2822 style */
                { "%ar" },      /* author date, relative */
                { "%at" },      /* author date, UNIX timestamp */
+               { "%ai" },      /* author date, ISO 8601 */
                { "%cn" },      /* committer name */
                { "%ce" },      /* committer email */
                { "%cd" },      /* committer date */
                { "%cD" },      /* committer date, RFC2822 style */
                { "%cr" },      /* committer date, relative */
                { "%ct" },      /* committer date, UNIX timestamp */
+               { "%ci" },      /* committer date, ISO 8601 */
                { "%e" },       /* encoding */
                { "%s" },       /* subject */
                { "%b" },       /* body */
@@ -813,10 +827,11 @@ static long format_commit_message(const struct commit *commit,
                IPARENTS, IPARENTS_ABBREV,
                IAUTHOR_NAME, IAUTHOR_EMAIL,
                IAUTHOR_DATE, IAUTHOR_DATE_RFC2822, IAUTHOR_DATE_RELATIVE,
-               IAUTHOR_TIMESTAMP,
+               IAUTHOR_TIMESTAMP, IAUTHOR_ISO8601,
                ICOMMITTER_NAME, ICOMMITTER_EMAIL,
                ICOMMITTER_DATE, ICOMMITTER_DATE_RFC2822,
                ICOMMITTER_DATE_RELATIVE, ICOMMITTER_TIMESTAMP,
+               ICOMMITTER_ISO8601,
                IENCODING,
                ISUBJECT,
                IBODY,
@@ -905,31 +920,252 @@ static long format_commit_message(const struct commit *commit,
                if (!table[i].value)
                        interp_set_entry(table, i, "<unknown>");
 
-       interpolate(buf, space, user_format, table, ARRAY_SIZE(table));
+       do {
+               char *buf = *buf_p;
+               unsigned long space = *space_p;
+
+               space = interpolate(buf, space, user_format,
+                                   table, ARRAY_SIZE(table));
+               if (!space)
+                       break;
+               buf = xrealloc(buf, space);
+               *buf_p = buf;
+               *space_p = space;
+       } while (1);
        interp_clear_table(table, ARRAY_SIZE(table));
 
-       return strlen(buf);
+       return strlen(*buf_p);
+}
+
+static void pp_header(enum cmit_fmt fmt,
+                     int abbrev,
+                     enum date_mode dmode,
+                     const char *encoding,
+                     const struct commit *commit,
+                     const char **msg_p,
+                     unsigned long *len_p,
+                     unsigned long *ofs_p,
+                     char **buf_p,
+                     unsigned long *space_p)
+{
+       int parents_shown = 0;
+
+       for (;;) {
+               const char *line = *msg_p;
+               char *dst;
+               int linelen = get_one_line(*msg_p, *len_p);
+               unsigned long len;
+
+               if (!linelen)
+                       return;
+               *msg_p += linelen;
+               *len_p -= linelen;
+
+               if (linelen == 1)
+                       /* End of header */
+                       return;
+
+               ALLOC_GROW(*buf_p, linelen + *ofs_p + 20, *space_p);
+               dst = *buf_p + *ofs_p;
+
+               if (fmt == CMIT_FMT_RAW) {
+                       memcpy(dst, line, linelen);
+                       *ofs_p += linelen;
+                       continue;
+               }
+
+               if (!memcmp(line, "parent ", 7)) {
+                       if (linelen != 48)
+                               die("bad parent line in commit");
+                       continue;
+               }
+
+               if (!parents_shown) {
+                       struct commit_list *parent;
+                       int num;
+                       for (parent = commit->parents, num = 0;
+                            parent;
+                            parent = parent->next, num++)
+                               ;
+                       /* with enough slop */
+                       num = *ofs_p + num * 50 + 20;
+                       ALLOC_GROW(*buf_p, num, *space_p);
+                       dst = *buf_p + *ofs_p;
+                       *ofs_p += add_merge_info(fmt, dst, commit, abbrev);
+                       parents_shown = 1;
+               }
+
+               /*
+                * MEDIUM == DEFAULT shows only author with dates.
+                * FULL shows both authors but not dates.
+                * FULLER shows both authors and dates.
+                */
+               if (!memcmp(line, "author ", 7)) {
+                       len = linelen;
+                       if (fmt == CMIT_FMT_EMAIL)
+                               len = bound_rfc2047(linelen, encoding);
+                       ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
+                       dst = *buf_p + *ofs_p;
+                       *ofs_p += add_user_info("Author", fmt, dst,
+                                               line + 7, dmode, encoding);
+               }
+
+               if (!memcmp(line, "committer ", 10) &&
+                   (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
+                       len = linelen;
+                       if (fmt == CMIT_FMT_EMAIL)
+                               len = bound_rfc2047(linelen, encoding);
+                       ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
+                       dst = *buf_p + *ofs_p;
+                       *ofs_p += add_user_info("Commit", fmt, dst,
+                                               line + 10, dmode, encoding);
+               }
+       }
+}
+
+static void pp_title_line(enum cmit_fmt fmt,
+                         const char **msg_p,
+                         unsigned long *len_p,
+                         unsigned long *ofs_p,
+                         char **buf_p,
+                         unsigned long *space_p,
+                         int indent,
+                         const char *subject,
+                         const char *after_subject,
+                         const char *encoding,
+                         int plain_non_ascii)
+{
+       char *title;
+       unsigned long title_alloc, title_len;
+       unsigned long len;
+
+       title_len = 0;
+       title_alloc = 80;
+       title = xmalloc(title_alloc);
+       for (;;) {
+               const char *line = *msg_p;
+               int linelen = get_one_line(line, *len_p);
+               *msg_p += linelen;
+               *len_p -= linelen;
+
+               if (!linelen || is_empty_line(line, &linelen))
+                       break;
+
+               if (title_alloc <= title_len + linelen + 2) {
+                       title_alloc = title_len + linelen + 80;
+                       title = xrealloc(title, title_alloc);
+               }
+               len = 0;
+               if (title_len) {
+                       if (fmt == CMIT_FMT_EMAIL) {
+                               len++;
+                               title[title_len++] = '\n';
+                       }
+                       len++;
+                       title[title_len++] = ' ';
+               }
+               memcpy(title + title_len, line, linelen);
+               title_len += linelen;
+       }
+
+       /* Enough slop for the MIME header and rfc2047 */
+       len = bound_rfc2047(title_len, encoding)+ 1000;
+       if (subject)
+               len += strlen(subject);
+       if (after_subject)
+               len += strlen(after_subject);
+       if (encoding)
+               len += strlen(encoding);
+       ALLOC_GROW(*buf_p, title_len + *ofs_p + len, *space_p);
+
+       if (subject) {
+               len = strlen(subject);
+               memcpy(*buf_p + *ofs_p, subject, len);
+               *ofs_p += len;
+               *ofs_p += add_rfc2047(*buf_p + *ofs_p,
+                                     title, title_len, encoding);
+       } else {
+               memcpy(*buf_p + *ofs_p, title, title_len);
+               *ofs_p += title_len;
+       }
+       (*buf_p)[(*ofs_p)++] = '\n';
+       if (plain_non_ascii) {
+               const char *header_fmt =
+                       "MIME-Version: 1.0\n"
+                       "Content-Type: text/plain; charset=%s\n"
+                       "Content-Transfer-Encoding: 8bit\n";
+               *ofs_p += snprintf(*buf_p + *ofs_p,
+                                  *space_p - *ofs_p,
+                                  header_fmt, encoding);
+       }
+       if (after_subject) {
+               len = strlen(after_subject);
+               memcpy(*buf_p + *ofs_p, after_subject, len);
+               *ofs_p += len;
+       }
+       free(title);
+       if (fmt == CMIT_FMT_EMAIL) {
+               ALLOC_GROW(*buf_p, *ofs_p + 20, *space_p);
+               (*buf_p)[(*ofs_p)++] = '\n';
+       }
+}
+
+static void pp_remainder(enum cmit_fmt fmt,
+                        const char **msg_p,
+                        unsigned long *len_p,
+                        unsigned long *ofs_p,
+                        char **buf_p,
+                        unsigned long *space_p,
+                        int indent)
+{
+       int first = 1;
+       for (;;) {
+               const char *line = *msg_p;
+               int linelen = get_one_line(line, *len_p);
+               *msg_p += linelen;
+               *len_p -= linelen;
+
+               if (!linelen)
+                       break;
+
+               if (is_empty_line(line, &linelen)) {
+                       if (first)
+                               continue;
+                       if (fmt == CMIT_FMT_SHORT)
+                               break;
+               }
+               first = 0;
+
+               ALLOC_GROW(*buf_p, *ofs_p + linelen + indent + 20, *space_p);
+               if (indent) {
+                       memset(*buf_p + *ofs_p, ' ', indent);
+                       *ofs_p += indent;
+               }
+               memcpy(*buf_p + *ofs_p, line, linelen);
+               *ofs_p += linelen;
+               (*buf_p)[(*ofs_p)++] = '\n';
+       }
 }
 
 unsigned long pretty_print_commit(enum cmit_fmt fmt,
                                  const struct commit *commit,
                                  unsigned long len,
-                                 char *buf, unsigned long space,
+                                 char **buf_p, unsigned long *space_p,
                                  int abbrev, const char *subject,
                                  const char *after_subject,
                                  enum date_mode dmode)
 {
-       int hdr = 1, body = 0, seen_title = 0;
        unsigned long offset = 0;
+       unsigned long beginning_of_body;
        int indent = 4;
-       int parents_shown = 0;
        const char *msg = commit->buffer;
        int plain_non_ascii = 0;
        char *reencoded;
        const char *encoding;
+       char *buf;
 
        if (fmt == CMIT_FMT_USERFORMAT)
-               return format_commit_message(commit, msg, buf, space);
+               return format_commit_message(commit, msg, buf_p, space_p);
 
        encoding = (git_log_output_encoding
                    ? git_log_output_encoding
@@ -937,8 +1173,10 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
        if (!encoding)
                encoding = "utf-8";
        reencoded = logmsg_reencode(commit, encoding);
-       if (reencoded)
+       if (reencoded) {
                msg = reencoded;
+               len = strlen(reencoded);
+       }
 
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
                indent = 0;
@@ -969,137 +1207,57 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
                }
        }
 
+       pp_header(fmt, abbrev, dmode, encoding,
+                 commit, &msg, &len,
+                 &offset, buf_p, space_p);
+       if (fmt != CMIT_FMT_ONELINE && !subject) {
+               ALLOC_GROW(*buf_p, offset + 20, *space_p);
+               (*buf_p)[offset++] = '\n';
+       }
+
+       /* Skip excess blank lines at the beginning of body, if any... */
        for (;;) {
-               const char *line = msg;
                int linelen = get_one_line(msg, len);
-
+               int ll = linelen;
                if (!linelen)
                        break;
-
-               /*
-                * We want some slop for indentation and a possible
-                * final "...". Thus the "+ 20".
-                */
-               if (offset + linelen + 20 > space) {
-                       memcpy(buf + offset, "    ...\n", 8);
-                       offset += 8;
+               if (!is_empty_line(msg, &ll))
                        break;
-               }
-
                msg += linelen;
                len -= linelen;
-               if (hdr) {
-                       if (linelen == 1) {
-                               hdr = 0;
-                               if ((fmt != CMIT_FMT_ONELINE) && !subject)
-                                       buf[offset++] = '\n';
-                               continue;
-                       }
-                       if (fmt == CMIT_FMT_RAW) {
-                               memcpy(buf + offset, line, linelen);
-                               offset += linelen;
-                               continue;
-                       }
-                       if (!memcmp(line, "parent ", 7)) {
-                               if (linelen != 48)
-                                       die("bad parent line in commit");
-                               continue;
-                       }
-
-                       if (!parents_shown) {
-                               offset += add_merge_info(fmt, buf + offset,
-                                                        commit, abbrev);
-                               parents_shown = 1;
-                               continue;
-                       }
-                       /*
-                        * MEDIUM == DEFAULT shows only author with dates.
-                        * FULL shows both authors but not dates.
-                        * FULLER shows both authors and dates.
-                        */
-                       if (!memcmp(line, "author ", 7))
-                               offset += add_user_info("Author", fmt,
-                                                       buf + offset,
-                                                       line + 7,
-                                                       dmode,
-                                                       encoding);
-                       if (!memcmp(line, "committer ", 10) &&
-                           (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
-                               offset += add_user_info("Commit", fmt,
-                                                       buf + offset,
-                                                       line + 10,
-                                                       dmode,
-                                                       encoding);
-                       continue;
-               }
+       }
 
-               if (!subject)
-                       body = 1;
+       /* These formats treat the title line specially. */
+       if (fmt == CMIT_FMT_ONELINE
+           || fmt == CMIT_FMT_EMAIL)
+               pp_title_line(fmt, &msg, &len, &offset,
+                             buf_p, space_p, indent,
+                             subject, after_subject, encoding,
+                             plain_non_ascii);
 
-               if (is_empty_line(line, &linelen)) {
-                       if (!seen_title)
-                               continue;
-                       if (!body)
-                               continue;
-                       if (subject)
-                               continue;
-                       if (fmt == CMIT_FMT_SHORT)
-                               break;
-               }
+       beginning_of_body = offset;
+       if (fmt != CMIT_FMT_ONELINE)
+               pp_remainder(fmt, &msg, &len, &offset,
+                            buf_p, space_p, indent);
 
-               seen_title = 1;
-               if (subject) {
-                       int slen = strlen(subject);
-                       memcpy(buf + offset, subject, slen);
-                       offset += slen;
-                       offset += add_rfc2047(buf + offset, line, linelen,
-                                             encoding);
-               }
-               else {
-                       memset(buf + offset, ' ', indent);
-                       memcpy(buf + offset + indent, line, linelen);
-                       offset += linelen + indent;
-               }
-               buf[offset++] = '\n';
-               if (fmt == CMIT_FMT_ONELINE)
-                       break;
-               if (subject && plain_non_ascii) {
-                       int sz;
-                       char header[512];
-                       const char *header_fmt =
-                               "MIME-Version: 1.0\n"
-                               "Content-Type: text/plain; charset=%s\n"
-                               "Content-Transfer-Encoding: 8bit\n";
-                       sz = snprintf(header, sizeof(header), header_fmt,
-                                     encoding);
-                       if (sizeof(header) < sz)
-                               die("Encoding name %s too long", encoding);
-                       memcpy(buf + offset, header, sz);
-                       offset += sz;
-               }
-               if (after_subject) {
-                       int slen = strlen(after_subject);
-                       if (slen > space - offset - 1)
-                               slen = space - offset - 1;
-                       memcpy(buf + offset, after_subject, slen);
-                       offset += slen;
-                       after_subject = NULL;
-               }
-               subject = NULL;
-       }
-       while (offset && isspace(buf[offset-1]))
+       while (offset && isspace((*buf_p)[offset-1]))
                offset--;
+
+       ALLOC_GROW(*buf_p, offset + 20, *space_p);
+       buf = *buf_p;
+
        /* Make sure there is an EOLN for the non-oneline case */
        if (fmt != CMIT_FMT_ONELINE)
                buf[offset++] = '\n';
+
        /*
-        * make sure there is another EOLN to separate the headers from whatever
-        * body the caller appends if we haven't already written a body
+        * The caller may append additional body text in e-mail
+        * format.  Make sure we did not strip the blank line
+        * between the header and the body.
         */
-       if (fmt == CMIT_FMT_EMAIL && !body)
+       if (fmt == CMIT_FMT_EMAIL && offset <= beginning_of_body)
                buf[offset++] = '\n';
        buf[offset] = '\0';
-
        free(reencoded);
        return offset;
 }
@@ -1116,15 +1274,6 @@ struct commit *pop_commit(struct commit_list **stack)
        return item;
 }
 
-int count_parents(struct commit * commit)
-{
-        int count;
-        struct commit_list * parents = commit->parents;
-        for (count = 0; parents; parents = parents->next,count++)
-               ;
-        return count;
-}
-
 void topo_sort_default_setter(struct commit *c, void *data)
 {
        c->util = data;
@@ -1160,7 +1309,7 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
                next = next->next;
                count++;
        }
-       
+
        if (!count)
                return;
        /* allocate an array to help sort the list */
@@ -1188,11 +1337,11 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
                }
                next=next->next;
        }
-       /* 
+       /*
          * find the tips
          *
-         * tips are nodes not reachable from any other node in the list 
-         * 
+         * tips are nodes not reachable from any other node in the list
+         *
          * the tips serve as a starting set for the work queue.
          */
        next=*list;
@@ -1220,7 +1369,7 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
 
                        if (pn) {
                                /*
-                                * parents are only enqueued for emission 
+                                * parents are only enqueued for emission
                                  * when all their children have been emitted thereby
                                  * guaranteeing topological order.
                                  */
index 86e8dca0c9dbac4d990d959296145805319b477a..467872eecabf05ccedbb9bf8247b6de244416b8f 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -61,20 +61,18 @@ enum cmit_fmt {
 };
 
 extern enum cmit_fmt get_commit_format(const char *arg);
-extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char *buf, unsigned long space, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
+extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
 
 /** Removes the first commit from a list sorted by date, and adds all
  * of its parents.
  **/
-struct commit *pop_most_recent_commit(struct commit_list **list, 
+struct commit *pop_most_recent_commit(struct commit_list **list,
                                      unsigned int mark);
 
 struct commit *pop_commit(struct commit_list **stack);
 
 void clear_commit_marks(struct commit *commit, unsigned int mark);
 
-int count_parents(struct commit * commit);
-
 /*
  * Performs an in-place topological sort of list supplied.
  *
index 4cfaee31361d98d2c8f68d250609b09130335baa..c9d46d174259f42a3e2a2eb073475aba517044be 100644 (file)
@@ -40,4 +40,3 @@ int git_munmap(void *start, size_t length)
        free(start);
        return 0;
 }
-
index 7b655fdb78350bf9a8320075bef6cf4f1e87c50c..dc3148d4566205858869973804de1c2ddb19e981 100644 (file)
--- a/config.c
+++ b/config.c
@@ -12,6 +12,8 @@
 static FILE *config_file;
 static const char *config_file_name;
 static int config_linenr;
+static int zlib_compression_seen;
+
 static int get_next_char(void)
 {
        int c;
@@ -231,21 +233,55 @@ static int git_parse_file(config_fn_t fn)
        die("bad config file line %d in %s", config_linenr, config_file_name);
 }
 
-int git_config_int(const char *name, const char *value)
+static unsigned long get_unit_factor(const char *end)
+{
+       if (!*end)
+               return 1;
+       else if (!strcasecmp(end, "k"))
+               return 1024;
+       else if (!strcasecmp(end, "m"))
+               return 1024 * 1024;
+       else if (!strcasecmp(end, "g"))
+               return 1024 * 1024 * 1024;
+       die("unknown unit: '%s'", end);
+}
+
+int git_parse_long(const char *value, long *ret)
 {
        if (value && *value) {
                char *end;
-               int val = strtol(value, &end, 0);
-               if (!*end)
-                       return val;
-               if (!strcasecmp(end, "k"))
-                       return val * 1024;
-               if (!strcasecmp(end, "m"))
-                       return val * 1024 * 1024;
-               if (!strcasecmp(end, "g"))
-                       return val * 1024 * 1024 * 1024;
-       }
-       die("bad config value for '%s' in %s", name, config_file_name);
+               long val = strtol(value, &end, 0);
+               *ret = val * get_unit_factor(end);
+               return 1;
+       }
+       return 0;
+}
+
+int git_parse_ulong(const char *value, unsigned long *ret)
+{
+       if (value && *value) {
+               char *end;
+               unsigned long val = strtoul(value, &end, 0);
+               *ret = val * get_unit_factor(end);
+               return 1;
+       }
+       return 0;
+}
+
+int git_config_int(const char *name, const char *value)
+{
+       long ret;
+       if (!git_parse_long(value, &ret))
+               die("bad config value for '%s' in %s", name, config_file_name);
+       return ret;
+}
+
+unsigned long git_config_ulong(const char *name, const char *value)
+{
+       unsigned long ret;
+       if (!git_parse_ulong(value, &ret))
+               die("bad config value for '%s' in %s", name, config_file_name);
+       return ret;
 }
 
 int git_config_bool(const char *name, const char *value)
@@ -269,6 +305,11 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.quotepath")) {
+               quote_path_fully = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "core.symlinks")) {
                has_symlinks = git_config_bool(var, value);
                return 0;
@@ -299,8 +340,14 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
-       if (!strcmp(var, "core.legacyheaders")) {
-               use_legacy_headers = git_config_bool(var, value);
+       if (!strcmp(var, "core.loosecompression")) {
+               int level = git_config_int(var, value);
+               if (level == -1)
+                       level = Z_DEFAULT_COMPRESSION;
+               else if (level < 0 || level > Z_BEST_COMPRESSION)
+                       die("bad zlib compression level %d", level);
+               zlib_compression_level = level;
+               zlib_compression_seen = 1;
                return 0;
        }
 
@@ -310,7 +357,10 @@ int git_default_config(const char *var, const char *value)
                        level = Z_DEFAULT_COMPRESSION;
                else if (level < 0 || level > Z_BEST_COMPRESSION)
                        die("bad zlib compression level %d", level);
-               zlib_compression_level = level;
+               core_compression_level = level;
+               core_compression_seen = 1;
+               if (!zlib_compression_seen)
+                       zlib_compression_level = level;
                return 0;
        }
 
@@ -371,6 +421,16 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.pager")) {
+               pager_program = xstrdup(value);
+               return 0;
+       }
+
+       if (!strcmp(var, "core.editor")) {
+               editor_program = xstrdup(value);
+               return 0;
+       }
+
        /* Add other config variables here and to Documentation/config.txt. */
        return 0;
 }
@@ -512,7 +572,7 @@ static int store_aux(const char* key, const char* value)
        return 0;
 }
 
-static int write_error()
+static int write_error(void)
 {
        fprintf(stderr, "Failed to write new configuration file\n");
 
@@ -610,7 +670,7 @@ static ssize_t find_beginning_of_line(const char* contents, size_t size,
        size_t equal_offset = size, bracket_offset = size;
        ssize_t offset;
 
-       for (offset = offset_-2; offset > 0 
+       for (offset = offset_-2; offset > 0
                        && contents[offset] != '\n'; offset--)
                switch (contents[offset]) {
                        case '=': equal_offset = offset; break;
@@ -660,7 +720,7 @@ int git_config_set_multivar(const char* key, const char* value,
        int fd = -1, in_fd;
        int ret;
        char* config_filename;
-       char* lock_file;
+       struct lock_file *lock = NULL;
        const char* last_dot = strrchr(key, '.');
 
        config_filename = getenv(CONFIG_ENVIRONMENT);
@@ -670,7 +730,6 @@ int git_config_set_multivar(const char* key, const char* value,
                        config_filename  = git_path("config");
        }
        config_filename = xstrdup(config_filename);
-       lock_file = xstrdup(mkpath("%s.lock", config_filename));
 
        /*
         * Since "key" actually contains the section name and the real
@@ -715,11 +774,12 @@ int git_config_set_multivar(const char* key, const char* value,
        store.key[i] = 0;
 
        /*
-        * The lock_file serves a purpose in addition to locking: the new
+        * The lock serves a purpose in addition to locking: the new
         * contents of .git/config will be written into it.
         */
-       fd = open(lock_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
-       if (fd < 0 || adjust_shared_perm(lock_file)) {
+       lock = xcalloc(sizeof(struct lock_file), 1);
+       fd = hold_lock_file_for_update(lock, config_filename, 0);
+       if (fd < 0) {
                fprintf(stderr, "could not lock config file\n");
                free(store.key);
                ret = -1;
@@ -859,25 +919,31 @@ int git_config_set_multivar(const char* key, const char* value,
                                goto write_err_out;
 
                munmap(contents, contents_sz);
-               unlink(config_filename);
        }
 
-       if (rename(lock_file, config_filename) < 0) {
-               fprintf(stderr, "Could not rename the lock file?\n");
+       if (close(fd) || commit_lock_file(lock) < 0) {
+               fprintf(stderr, "Cannot commit config file!\n");
                ret = 4;
                goto out_free;
        }
 
+       /* fd is closed, so don't try to close it below. */
+       fd = -1;
+       /*
+        * lock is committed, so don't try to roll it back below.
+        * NOTE: Since lockfile.c keeps a linked list of all created
+        * lock_file structures, it isn't safe to free(lock).  It's
+        * better to just leave it hanging around.
+        */
+       lock = NULL;
        ret = 0;
 
 out_free:
        if (0 <= fd)
                close(fd);
+       if (lock)
+               rollback_lock_file(lock);
        free(config_filename);
-       if (lock_file) {
-               unlink(lock_file);
-               free(lock_file);
-       }
        return ret;
 
 write_err_out:
@@ -978,4 +1044,3 @@ int git_config_rename_section(const char *old_name, const char *new_name)
        free(config_filename);
        return ret;
 }
-
index eb9d7a55496492fa021e8262f052c7f46b60ea20..a3032e389f6bf63cd46a3aa23e59b6a6cd537132 100644 (file)
@@ -38,4 +38,3 @@ NO_STRCASESTR=@NO_STRCASESTR@
 NO_STRLCPY=@NO_STRLCPY@
 NO_SETENV=@NO_SETENV@
 NO_ICONV=@NO_ICONV@
-
index 50d2b85ace7d79ba1b8c576b54c6ef1f22ddf91f..84fd7f1e1f2fd670de38a43bfe65d3988fc4767e 100644 (file)
@@ -69,12 +69,26 @@ fi \
 ## Site configuration related to programs (before tests)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
 #
+# Set lib to alternative name of lib directory (e.g. lib64)
+AC_ARG_WITH([lib],
+ [AS_HELP_STRING([--with-lib=ARG],
+                 [ARG specifies alternative name for lib directory])],
+ [if test "$withval" = "no" -o "$withval" = "yes"; then \
+       AC_MSG_WARN([You should provide name for --with-lib=ARG]); \
+else \
+       GIT_CONF_APPEND_LINE(lib=$withval); \
+fi; \
+],[])
+#
 # Define SHELL_PATH to provide path to shell.
 GIT_ARG_SET_PATH(shell)
 #
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
+# Define ZLIB_PATH to provide path to zlib.
+GIT_ARG_SET_PATH(zlib)
+#
 # Declare the with-tcltk/without-tcltk options.
 AC_ARG_WITH(tcltk,
 AS_HELP_STRING([--with-tcltk],[use Tcl/Tk GUI (default is YES)])
index da89c9cfcf3469dfab789f2644f953ea46666e90..ae49c5a367f9f5b77b07228fcd241fd73a5711b0 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -4,6 +4,7 @@
 #include "quote.h"
 #include "refs.h"
 #include "run-command.h"
+#include "remote.h"
 
 static char *server_capabilities;
 
@@ -71,7 +72,7 @@ struct ref **get_remote_heads(int in, struct ref **list,
                        continue;
                if (nr_match && !path_match(name, nr_match, match))
                        continue;
-               ref = xcalloc(1, sizeof(*ref) + len - 40);
+               ref = alloc_ref(len - 40);
                hashcpy(ref->old_sha1, old_sha1);
                memcpy(ref->name, buffer + 41, len - 40);
                *list = ref;
@@ -128,245 +129,6 @@ int path_match(const char *path, int nr, char **match)
        return 0;
 }
 
-struct refspec {
-       char *src;
-       char *dst;
-       char force;
-};
-
-/*
- * A:B means fast forward remote B with local A.
- * +A:B means overwrite remote B with local A.
- * +A is a shorthand for +A:A.
- * A is a shorthand for A:A.
- * :B means delete remote B.
- */
-static struct refspec *parse_ref_spec(int nr_refspec, char **refspec)
-{
-       int i;
-       struct refspec *rs = xcalloc(sizeof(*rs), (nr_refspec + 1));
-       for (i = 0; i < nr_refspec; i++) {
-               char *sp, *dp, *ep;
-               sp = refspec[i];
-               if (*sp == '+') {
-                       rs[i].force = 1;
-                       sp++;
-               }
-               ep = strchr(sp, ':');
-               if (ep) {
-                       dp = ep + 1;
-                       *ep = 0;
-               }
-               else
-                       dp = sp;
-               rs[i].src = sp;
-               rs[i].dst = dp;
-       }
-       rs[nr_refspec].src = rs[nr_refspec].dst = NULL;
-       return rs;
-}
-
-static int count_refspec_match(const char *pattern,
-                              struct ref *refs,
-                              struct ref **matched_ref)
-{
-       int patlen = strlen(pattern);
-       struct ref *matched_weak = NULL;
-       struct ref *matched = NULL;
-       int weak_match = 0;
-       int match = 0;
-
-       for (weak_match = match = 0; refs; refs = refs->next) {
-               char *name = refs->name;
-               int namelen = strlen(name);
-               int weak_match;
-
-               if (namelen < patlen ||
-                   memcmp(name + namelen - patlen, pattern, patlen))
-                       continue;
-               if (namelen != patlen && name[namelen - patlen - 1] != '/')
-                       continue;
-
-               /* A match is "weak" if it is with refs outside
-                * heads or tags, and did not specify the pattern
-                * in full (e.g. "refs/remotes/origin/master") or at
-                * least from the toplevel (e.g. "remotes/origin/master");
-                * otherwise "git push $URL master" would result in
-                * ambiguity between remotes/origin/master and heads/master
-                * at the remote site.
-                */
-               if (namelen != patlen &&
-                   patlen != namelen - 5 &&
-                   prefixcmp(name, "refs/heads/") &&
-                   prefixcmp(name, "refs/tags/")) {
-                       /* We want to catch the case where only weak
-                        * matches are found and there are multiple
-                        * matches, and where more than one strong
-                        * matches are found, as ambiguous.  One
-                        * strong match with zero or more weak matches
-                        * are acceptable as a unique match.
-                        */
-                       matched_weak = refs;
-                       weak_match++;
-               }
-               else {
-                       matched = refs;
-                       match++;
-               }
-       }
-       if (!matched) {
-               *matched_ref = matched_weak;
-               return weak_match;
-       }
-       else {
-               *matched_ref = matched;
-               return match;
-       }
-}
-
-static void link_dst_tail(struct ref *ref, struct ref ***tail)
-{
-       **tail = ref;
-       *tail = &ref->next;
-       **tail = NULL;
-}
-
-static struct ref *try_explicit_object_name(const char *name)
-{
-       unsigned char sha1[20];
-       struct ref *ref;
-       int len;
-
-       if (!*name) {
-               ref = xcalloc(1, sizeof(*ref) + 20);
-               strcpy(ref->name, "(delete)");
-               hashclr(ref->new_sha1);
-               return ref;
-       }
-       if (get_sha1(name, sha1))
-               return NULL;
-       len = strlen(name) + 1;
-       ref = xcalloc(1, sizeof(*ref) + len);
-       memcpy(ref->name, name, len);
-       hashcpy(ref->new_sha1, sha1);
-       return ref;
-}
-
-static int match_explicit_refs(struct ref *src, struct ref *dst,
-                              struct ref ***dst_tail, struct refspec *rs)
-{
-       int i, errs;
-       for (i = errs = 0; rs[i].src; i++) {
-               struct ref *matched_src, *matched_dst;
-
-               matched_src = matched_dst = NULL;
-               switch (count_refspec_match(rs[i].src, src, &matched_src)) {
-               case 1:
-                       break;
-               case 0:
-                       /* The source could be in the get_sha1() format
-                        * not a reference name.  :refs/other is a
-                        * way to delete 'other' ref at the remote end.
-                        */
-                       matched_src = try_explicit_object_name(rs[i].src);
-                       if (matched_src)
-                               break;
-                       errs = 1;
-                       error("src refspec %s does not match any.",
-                             rs[i].src);
-                       break;
-               default:
-                       errs = 1;
-                       error("src refspec %s matches more than one.",
-                             rs[i].src);
-                       break;
-               }
-               switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) {
-               case 1:
-                       break;
-               case 0:
-                       if (!memcmp(rs[i].dst, "refs/", 5)) {
-                               int len = strlen(rs[i].dst) + 1;
-                               matched_dst = xcalloc(1, sizeof(*dst) + len);
-                               memcpy(matched_dst->name, rs[i].dst, len);
-                               link_dst_tail(matched_dst, dst_tail);
-                       }
-                       else if (!strcmp(rs[i].src, rs[i].dst) &&
-                                matched_src) {
-                               /* pushing "master:master" when
-                                * remote does not have master yet.
-                                */
-                               int len = strlen(matched_src->name) + 1;
-                               matched_dst = xcalloc(1, sizeof(*dst) + len);
-                               memcpy(matched_dst->name, matched_src->name,
-                                      len);
-                               link_dst_tail(matched_dst, dst_tail);
-                       }
-                       else {
-                               errs = 1;
-                               error("dst refspec %s does not match any "
-                                     "existing ref on the remote and does "
-                                     "not start with refs/.", rs[i].dst);
-                       }
-                       break;
-               default:
-                       errs = 1;
-                       error("dst refspec %s matches more than one.",
-                             rs[i].dst);
-                       break;
-               }
-               if (errs)
-                       continue;
-               if (matched_dst->peer_ref) {
-                       errs = 1;
-                       error("dst ref %s receives from more than one src.",
-                             matched_dst->name);
-               }
-               else {
-                       matched_dst->peer_ref = matched_src;
-                       matched_dst->force = rs[i].force;
-               }
-       }
-       return -errs;
-}
-
-static struct ref *find_ref_by_name(struct ref *list, const char *name)
-{
-       for ( ; list; list = list->next)
-               if (!strcmp(list->name, name))
-                       return list;
-       return NULL;
-}
-
-int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
-              int nr_refspec, char **refspec, int all)
-{
-       struct refspec *rs = parse_ref_spec(nr_refspec, refspec);
-
-       if (nr_refspec)
-               return match_explicit_refs(src, dst, dst_tail, rs);
-
-       /* pick the remainder */
-       for ( ; src; src = src->next) {
-               struct ref *dst_peer;
-               if (src->peer_ref)
-                       continue;
-               dst_peer = find_ref_by_name(dst, src->name);
-               if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all))
-                       continue;
-               if (!dst_peer) {
-                       /* Create a new one and link it */
-                       int len = strlen(src->name) + 1;
-                       dst_peer = xcalloc(1, sizeof(*dst_peer) + len);
-                       memcpy(dst_peer->name, src->name, len);
-                       hashcpy(dst_peer->new_sha1, src->new_sha1);
-                       link_dst_tail(dst_peer, dst_tail);
-               }
-               dst_peer->peer_ref = src;
-       }
-       return 0;
-}
-
 enum protocol {
        PROTO_LOCAL = 1,
        PROTO_SSH,
@@ -383,6 +145,8 @@ static enum protocol get_protocol(const char *name)
                return PROTO_SSH;
        if (!strcmp(name, "ssh+git"))
                return PROTO_SSH;
+       if (!strcmp(name, "file"))
+               return PROTO_LOCAL;
        die("I don't handle protocol '%s'", name);
 }
 
@@ -391,16 +155,34 @@ static enum protocol get_protocol(const char *name)
 
 #ifndef NO_IPV6
 
+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 {
+               strcpy(addr, "(unknown)");
+       }
+       return addr;
+}
+
 /*
  * Returns a connected socket() fd, or else die()s.
  */
-static int git_tcp_connect_sock(char *host)
+static int git_tcp_connect_sock(char *host, int flags)
 {
        int sockfd = -1, saved_errno = 0;
        char *colon, *end;
        const char *port = STR(DEFAULT_GIT_PORT);
        struct addrinfo hints, *ai0, *ai;
        int gai;
+       int cnt = 0;
 
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
@@ -425,10 +207,16 @@ static int git_tcp_connect_sock(char *host)
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
 
+       if (flags & CONNECT_VERBOSE)
+               fprintf(stderr, "Looking up %s ... ", host);
+
        gai = getaddrinfo(host, port, &hints, &ai);
        if (gai)
                die("Unable to look up %s (port %s) (%s)", host, port, gai_strerror(gai));
 
+       if (flags & CONNECT_VERBOSE)
+               fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
+
        for (ai0 = ai; ai; ai = ai->ai_next) {
                sockfd = socket(ai->ai_family,
                                ai->ai_socktype, ai->ai_protocol);
@@ -438,10 +226,17 @@ static int git_tcp_connect_sock(char *host)
                }
                if (connect(sockfd, ai->ai_addr, ai->ai_addrlen) < 0) {
                        saved_errno = errno;
+                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+                               host,
+                               cnt,
+                               ai_name(ai),
+                               strerror(saved_errno));
                        close(sockfd);
                        sockfd = -1;
                        continue;
                }
+               if (flags & CONNECT_VERBOSE)
+                       fprintf(stderr, "%s ", ai_name(ai));
                break;
        }
 
@@ -450,6 +245,9 @@ static int git_tcp_connect_sock(char *host)
        if (sockfd < 0)
                die("unable to connect a socket (%s)", strerror(saved_errno));
 
+       if (flags & CONNECT_VERBOSE)
+               fprintf(stderr, "done.\n");
+
        return sockfd;
 }
 
@@ -458,7 +256,7 @@ static int git_tcp_connect_sock(char *host)
 /*
  * Returns a connected socket() fd, or else die()s.
  */
-static int git_tcp_connect_sock(char *host)
+static int git_tcp_connect_sock(char *host, int flags)
 {
        int sockfd = -1, saved_errno = 0;
        char *colon, *end;
@@ -467,6 +265,7 @@ static int git_tcp_connect_sock(char *host)
        struct sockaddr_in sa;
        char **ap;
        unsigned int nport;
+       int cnt;
 
        if (host[0] == '[') {
                end = strchr(host + 1, ']');
@@ -485,6 +284,9 @@ static int git_tcp_connect_sock(char *host)
                port = colon + 1;
        }
 
+       if (flags & CONNECT_VERBOSE)
+               fprintf(stderr, "Looking up %s ... ", host);
+
        he = gethostbyname(host);
        if (!he)
                die("Unable to look up %s (%s)", host, hstrerror(h_errno));
@@ -497,7 +299,10 @@ static int git_tcp_connect_sock(char *host)
                nport = se->s_port;
        }
 
-       for (ap = he->h_addr_list; *ap; ap++) {
+       if (flags & CONNECT_VERBOSE)
+               fprintf(stderr, "done.\nConnecting to %s (port %s) ... ", host, port);
+
+       for (cnt = 0, ap = he->h_addr_list; *ap; ap++, cnt++) {
                sockfd = socket(he->h_addrtype, SOCK_STREAM, 0);
                if (sockfd < 0) {
                        saved_errno = errno;
@@ -511,25 +316,36 @@ static int git_tcp_connect_sock(char *host)
 
                if (connect(sockfd, (struct sockaddr *)&sa, sizeof sa) < 0) {
                        saved_errno = errno;
+                       fprintf(stderr, "%s[%d: %s]: errno=%s\n",
+                               host,
+                               cnt,
+                               inet_ntoa(*(struct in_addr *)&sa.sin_addr),
+                               strerror(saved_errno));
                        close(sockfd);
                        sockfd = -1;
                        continue;
                }
+               if (flags & CONNECT_VERBOSE)
+                       fprintf(stderr, "%s ",
+                               inet_ntoa(*(struct in_addr *)&sa.sin_addr));
                break;
        }
 
        if (sockfd < 0)
                die("unable to connect a socket (%s)", strerror(saved_errno));
 
+       if (flags & CONNECT_VERBOSE)
+               fprintf(stderr, "done.\n");
+
        return sockfd;
 }
 
 #endif /* NO_IPV6 */
 
 
-static void git_tcp_connect(int fd[2], char *host)
+static void git_tcp_connect(int fd[2], char *host, int flags)
 {
-       int sockfd = git_tcp_connect_sock(host);
+       int sockfd = git_tcp_connect_sock(host, flags);
 
        fd[0] = sockfd;
        fd[1] = dup(sockfd);
@@ -574,7 +390,7 @@ static int git_proxy_command_options(const char *var, const char *value)
                }
                if (0 <= matchlen) {
                        /* core.gitproxy = none for kernel.org */
-                       if (matchlen == 4 && 
+                       if (matchlen == 4 &&
                            !memcmp(value, "none", 4))
                                matchlen = 0;
                        git_proxy_command = xmalloc(matchlen + 1);
@@ -646,7 +462,7 @@ static void git_proxy_connect(int fd[2], char *host)
  *
  * Does not return a negative value on error; it just dies.
  */
-pid_t git_connect(int fd[2], char *url, const char *prog)
+pid_t git_connect(int fd[2], char *url, const char *prog, int flags)
 {
        char *host, *path = url;
        char *end;
@@ -684,13 +500,13 @@ pid_t git_connect(int fd[2], char *url, const char *prog)
                end = host;
 
        path = strchr(end, c);
-       if (c == ':') {
-               if (path) {
+       if (path) {
+               if (c == ':') {
                        protocol = PROTO_SSH;
                        *path++ = '\0';
-               } else
-                       path = host;
-       }
+               }
+       } else
+               path = end;
 
        if (!path || !*path)
                die("No path specified. See 'man git-pull' for valid url syntax");
@@ -719,7 +535,7 @@ pid_t git_connect(int fd[2], char *url, const char *prog)
                if (git_use_proxy(host))
                        git_proxy_connect(fd, host);
                else
-                       git_tcp_connect(fd, host);
+                       git_tcp_connect(fd, host, flags);
                /*
                 * Separate original protocol components prog and path
                 * from extended components with a NUL byte.
@@ -773,6 +589,7 @@ pid_t git_connect(int fd[2], char *url, const char *prog)
                        unsetenv(ALTERNATE_DB_ENVIRONMENT);
                        unsetenv(DB_ENVIRONMENT);
                        unsetenv(GIT_DIR_ENVIRONMENT);
+                       unsetenv(GIT_WORK_TREE_ENVIRONMENT);
                        unsetenv(GRAFT_ENVIRONMENT);
                        unsetenv(INDEX_ENVIRONMENT);
                        execlp("sh", "sh", "-c", command, NULL);
index e1c0a01ff30d3756d547781f783a37c96e9b7eb9..05f291c1f1d3d1018f390618816f94d0cd58951b 100644 (file)
@@ -41,4 +41,3 @@ submit a patch to create a subdirectory of contrib/ and put your
 stuff there.
 
 -jc
-
index 50a6f67fd6df5c37a0e244ff5c5997131f3f0f52..fada5ce909876168f68a85c8ca9a8bc269048acb 100644 (file)
@@ -7,4 +7,3 @@ To: Linus Torvalds <torvalds@linux-foundation.org>
 Cc: git@vger.kernel.org
 Date: Sat, 27 Jan 2007 18:52:38 -0500
 Message-ID: <20070127235238.GA28706@coredump.intra.peff.net>
-
index 9e72f0f7b1668ba144397b6a6a2094d4294f5724..82b9ed40d84dbb72c72e282392ca8eb247ab527e 100755 (executable)
@@ -682,8 +682,11 @@ _git_push ()
                        esac
                        __gitcomp "$(__git_refs "$remote")" "" "${cur#*:}"
                        ;;
+               +*)
+                       __gitcomp "$(__git_refs)" + "${cur#+}"
+                       ;;
                *)
-                       __gitcomp "$(__git_refs2)"
+                       __gitcomp "$(__git_refs)"
                        ;;
                esac
                ;;
@@ -969,6 +972,11 @@ _git_show ()
        __git_complete_file
 }
 
+_git_stash ()
+{
+       __gitcomp 'list show apply clear'
+}
+
 _git ()
 {
        local i c=1 command __git_dir
@@ -1025,6 +1033,7 @@ _git ()
        shortlog)    _git_shortlog ;;
        show)        _git_show ;;
        show-branch) _git_log ;;
+       stash)       _git_stash ;;
        whatchanged) _git_log ;;
        *)           COMPREPLY=() ;;
        esac
@@ -1070,6 +1079,7 @@ complete -o default -o nospace -F _git_remote git-remote
 complete -o default -o nospace -F _git_reset git-reset
 complete -o default -o nospace -F _git_shortlog git-shortlog
 complete -o default -o nospace -F _git_show git-show
+complete -o default -o nospace -F _git_stash git-stash
 complete -o default -o nospace -F _git_log git-show-branch
 complete -o default -o nospace -F _git_log git-whatchanged
 
index 98aa0aae9b2e9e4a758fa58e7dd1e1adc3214883..a48540a92b4aa5140a87469b36c1f9b3d8e46e7f 100644 (file)
@@ -7,14 +7,15 @@ INSTALL ?= install
 INSTALL_ELC = $(INSTALL) -m 644
 prefix ?= $(HOME)
 emacsdir = $(prefix)/share/emacs/site-lisp
+RM ?= rm -f
 
 all: $(ELC)
 
 install: all
        $(INSTALL) -d $(DESTDIR)$(emacsdir)
-       $(INSTALL_ELC) $(ELC) $(DESTDIR)$(emacsdir)
+       $(INSTALL_ELC) $(ELC:.elc=.el) $(ELC) $(DESTDIR)$(emacsdir)
 
 %.elc: %.el
        $(EMACS) -batch -f batch-byte-compile $<
 
-clean:; rm -f $(ELC)
+clean:; $(RM) $(ELC)
index f60017948f4eb45f6aa7fe0fc60ca1507cec98f2..be44e06c452818651d57f07ebb1288e62680794f 100644 (file)
@@ -314,8 +314,8 @@ and returns the process output as a string."
     (sort-lines nil (point-min) (point-max))
     (save-buffer))
   (when created
-    (git-run-command nil nil "update-index" "--info-only" "--add" "--" (file-relative-name ignore-name)))
-  (git-add-status-file (if created 'added 'modified) (file-relative-name ignore-name))))
+    (git-run-command nil nil "update-index" "--add" "--" (file-relative-name ignore-name)))
+  (git-update-status-files (list (file-relative-name ignore-name)) 'unknown)))
 
 ; propertize definition for XEmacs, stolen from erc-compat
 (eval-when-compile
@@ -523,23 +523,39 @@ and returns the process output as a string."
                   "  " (git-escape-file-name (git-fileinfo->name info))
                   (git-rename-as-string info))))
 
-(defun git-parse-status (status)
-  "Parse the output of git-diff-index in the current buffer."
-  (goto-char (point-min))
-  (while (re-search-forward
-          ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
-          nil t 1)
-    (let ((old-perm (string-to-number (match-string 1) 8))
-          (new-perm (string-to-number (match-string 2) 8))
-          (state (or (match-string 4) (match-string 6)))
-          (name (or (match-string 5) (match-string 7)))
-          (new-name (match-string 8)))
-      (if new-name  ; copy or rename
-          (if (eq ?C (string-to-char state))
-              (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name))
-            (ewoc-enter-last status (git-create-fileinfo 'deleted name 0 0 'rename new-name))
-            (ewoc-enter-last status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)))
-        (ewoc-enter-last status (git-create-fileinfo (git-state-code state) name old-perm new-perm))))))
+(defun git-insert-fileinfo (status info &optional refresh)
+  "Insert INFO in the status buffer, optionally refreshing an existing one."
+  (let ((node (and refresh
+                   (git-find-status-file status (git-fileinfo->name info)))))
+    (setf (git-fileinfo->needs-refresh info) t)
+    (when node   ;preserve the marked flag
+      (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))))
+    (if node (setf (ewoc-data node) info) (ewoc-enter-last status info))))
+
+(defun git-run-diff-index (status files)
+  "Run git-diff-index on FILES and parse the results into STATUS.
+Return the list of files that haven't been handled."
+  (let ((refresh files))
+    (with-temp-buffer
+      (apply #'git-run-command t nil "diff-index" "-z" "-M" "HEAD" "--" files)
+      (goto-char (point-min))
+      (while (re-search-forward
+              ":\\([0-7]\\{6\\}\\) \\([0-7]\\{6\\}\\) [0-9a-f]\\{40\\} [0-9a-f]\\{40\\} \\(\\([ADMU]\\)\0\\([^\0]+\\)\\|\\([CR]\\)[0-9]*\0\\([^\0]+\\)\0\\([^\0]+\\)\\)\0"
+              nil t 1)
+        (let ((old-perm (string-to-number (match-string 1) 8))
+              (new-perm (string-to-number (match-string 2) 8))
+              (state (or (match-string 4) (match-string 6)))
+              (name (or (match-string 5) (match-string 7)))
+              (new-name (match-string 8)))
+          (if new-name  ; copy or rename
+              (if (eq ?C (string-to-char state))
+                  (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) refresh)
+                (git-insert-fileinfo status (git-create-fileinfo 'deleted name 0 0 'rename new-name) refresh)
+                (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)) refresh)
+            (git-insert-fileinfo status (git-create-fileinfo (git-state-code state) name old-perm new-perm) refresh))
+          (setq files (delete name files))
+          (when new-name (setq files (delete new-name files)))))))
+  files)
 
 (defun git-find-status-file (status file)
   "Find a given file in the status ewoc and return its node."
@@ -548,32 +564,69 @@ and returns the process output as a string."
       (setq node (ewoc-next status node)))
     node))
 
-(defun git-parse-ls-files (status default-state &optional skip-existing)
-  "Parse the output of git-ls-files in the current buffer."
-  (goto-char (point-min))
-  (let (infolist)
-    (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
-      (let ((state (match-string 1))
-            (name (match-string 2)))
-        (unless (and skip-existing (git-find-status-file status name))
-          (push (git-create-fileinfo (or (git-state-code state) default-state) name) infolist))))
-    (dolist (info (nreverse infolist))
-      (ewoc-enter-last status info))))
-
-(defun git-parse-ls-unmerged (status)
-  "Parse the output of git-ls-files -u in the current buffer."
-  (goto-char (point-min))
-  (let (files)
-    (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
-      (let ((node (git-find-status-file status (match-string 1))))
-        (when node (push (ewoc-data node) files))))
-    (git-set-files-state files 'unmerged)))
-
-(defun git-add-status-file (state name)
-  "Add a new file to the status list (if not existing already) and return its node."
+(defun git-run-ls-files (status files default-state &rest options)
+  "Run git-ls-files on FILES and parse the results into STATUS.
+Return the list of files that haven't been handled."
+  (let ((refresh files))
+    (with-temp-buffer
+      (apply #'git-run-command t nil "ls-files" "-z" "-t" (append options (list "--") files))
+      (goto-char (point-min))
+      (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
+        (let ((state (match-string 1))
+              (name (match-string 2)))
+          (git-insert-fileinfo status (git-create-fileinfo (or (git-state-code state) default-state) name) refresh)
+          (setq files (delete name files))))))
+  files)
+
+(defun git-run-ls-unmerged (status files)
+  "Run git-ls-files -u on FILES and parse the results into STATUS."
+  (with-temp-buffer
+    (apply #'git-run-command t nil "ls-files" "-z" "-u" "--" files)
+    (goto-char (point-min))
+    (let (unmerged-files)
+      (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
+        (let ((node (git-find-status-file status (match-string 1))))
+          (when node (push (ewoc-data node) unmerged-files))))
+      (git-set-files-state unmerged-files 'unmerged))))
+
+(defun git-get-exclude-files ()
+  "Get the list of exclude files to pass to git-ls-files."
+  (let (files
+        (config (git-config "core.excludesfile")))
+    (when (file-readable-p ".git/info/exclude")
+      (push ".git/info/exclude" files))
+    (when (and config (file-readable-p config))
+      (push config files))
+    files))
+
+(defun git-update-status-files (files &optional default-state)
+  "Update the status of FILES from the index."
   (unless git-status (error "Not in git-status buffer."))
-  (or (git-find-status-file git-status name)
-      (ewoc-enter-last git-status (git-create-fileinfo state name))))
+  (let* ((status git-status)
+         (remaining-files
+          (if (git-empty-db-p) ; we need some special handling for an empty db
+              (git-run-ls-files status files 'added "-c")
+            (git-run-diff-index status files))))
+    (git-run-ls-unmerged status files)
+    (when (or (not files) remaining-files)
+      (let ((exclude-files (git-get-exclude-files)))
+        (setq remaining-files (apply #'git-run-ls-files status remaining-files 'unknown "-o"
+                                     (concat "--exclude-per-directory=" git-per-dir-ignore-file)
+                                     (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
+    ; mark remaining files with the default state (or remove them if nil)
+    (when remaining-files
+      (if default-state
+          (ewoc-map (lambda (info)
+                      (when (member (git-fileinfo->name info) remaining-files)
+                        (git-set-files-state (list info) default-state))
+                      nil)
+                    status)
+        (ewoc-filter status
+                     (lambda (info files)
+                       (not (member (git-fileinfo->name info) files)))
+                     remaining-files)))
+    (git-refresh-files)
+    (git-refresh-ewoc-hf status)))
 
 (defun git-marked-files ()
   "Return a list of all marked files, or if none a list containing just the file at cursor position."
@@ -681,8 +734,7 @@ and returns the process output as a string."
                             (condition-case nil (delete-file ".git/MERGE_MSG") (error nil))
                             (with-current-buffer buffer (erase-buffer))
                             (git-set-files-state files 'uptodate)
-                            (when (file-directory-p ".git/rr-cache")
-                              (git-run-command nil nil "rerere"))
+                            (git-run-command nil nil "rerere")
                             (git-refresh-files)
                             (git-refresh-ewoc-hf git-status)
                             (message "Committed %s." commit)
@@ -790,54 +842,34 @@ and returns the process output as a string."
 (defun git-add-file ()
   "Add marked file(s) to the index cache."
   (interactive)
-  (let ((files (git-marked-files-state 'unknown)))
+  (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
     (unless files
-      (push (ewoc-data
-             (git-add-status-file 'added (file-relative-name
-                                          (read-file-name "File to add: " nil nil t))))
-            files))
-    (apply #'git-run-command nil nil "update-index" "--info-only" "--add" "--" (git-get-filenames files))
-    (git-set-files-state files 'added)
-    (git-refresh-files)))
+      (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
+    (apply #'git-run-command nil nil "update-index" "--add" "--" files)
+    (git-update-status-files files 'uptodate)))
 
 (defun git-ignore-file ()
   "Add marked file(s) to the ignore list."
   (interactive)
-  (let ((files (git-marked-files-state 'unknown)))
+  (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
     (unless files
-      (push (ewoc-data
-             (git-add-status-file 'unknown (file-relative-name
-                                            (read-file-name "File to ignore: " nil nil t))))
-            files))
-    (dolist (info files) (git-append-to-ignore (git-fileinfo->name info)))
-    (git-set-files-state files 'ignored)
-    (git-refresh-files)))
+      (push (file-relative-name (read-file-name "File to ignore: " nil nil t)) files))
+    (dolist (f files) (git-append-to-ignore f))
+    (git-update-status-files files 'ignored)))
 
 (defun git-remove-file ()
   "Remove the marked file(s)."
   (interactive)
-  (let ((files (git-marked-files-state 'added 'modified 'unknown 'uptodate)))
+  (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate))))
     (unless files
-      (push (ewoc-data
-             (git-add-status-file 'unknown (file-relative-name
-                                            (read-file-name "File to remove: " nil nil t))))
-            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" "")))
         (progn
-          (dolist (info files)
-            (let ((name (git-fileinfo->name info)))
-              (when (file-exists-p name) (delete-file name))))
-          (apply #'git-run-command nil nil "update-index" "--info-only" "--remove" "--" (git-get-filenames files))
-          ; remove unknown files from the list, set the others to deleted
-          (ewoc-filter git-status
-                       (lambda (info files)
-                         (not (and (memq info files) (eq (git-fileinfo->state info) 'unknown))))
-                       files)
-          (git-set-files-state files 'deleted)
-          (git-refresh-files)
-          (unless (ewoc-nth git-status 0)  ; refresh header if list is empty
-            (git-refresh-ewoc-hf git-status)))
+          (dolist (name files)
+            (when (file-exists-p name) (delete-file name)))
+          (apply #'git-run-command nil nil "update-index" "--remove" "--" files)
+          (git-update-status-files files nil))
       (message "Aborting"))))
 
 (defun git-revert-file ()
@@ -850,26 +882,23 @@ and returns the process output as a string."
                 (format "Revert %d file%s? " (length files) (if (> (length files) 1) "s" ""))))
       (dolist (info files)
         (case (git-fileinfo->state info)
-          ('added (push info added))
-          ('deleted (push info modified))
-          ('unmerged (push info modified))
-          ('modified (push info modified))))
+          ('added (push (git-fileinfo->name info) added))
+          ('deleted (push (git-fileinfo->name info) modified))
+          ('unmerged (push (git-fileinfo->name info) modified))
+          ('modified (push (git-fileinfo->name info) modified))))
       (when added
-          (apply #'git-run-command nil nil "update-index" "--force-remove" "--" (git-get-filenames added))
-          (git-set-files-state added 'unknown))
+        (apply #'git-run-command nil nil "update-index" "--force-remove" "--" added))
       (when modified
-          (apply #'git-run-command nil nil "checkout" "HEAD" (git-get-filenames modified))
-          (git-set-files-state modified 'uptodate))
-      (git-refresh-files))))
+        (apply #'git-run-command nil nil "checkout" "HEAD" modified))
+      (git-update-status-files (append added modified) 'uptodate))))
 
 (defun git-resolve-file ()
   "Resolve conflicts in marked file(s)."
   (interactive)
-  (let ((files (git-marked-files-state 'unmerged)))
+  (let ((files (git-get-filenames (git-marked-files-state 'unmerged))))
     (when files
-      (apply #'git-run-command nil nil "update-index" "--" (git-get-filenames files))
-      (git-set-files-state files 'modified)
-      (git-refresh-files))))
+      (apply #'git-run-command nil nil "update-index" "--" files)
+      (git-update-status-files files 'uptodate))))
 
 (defun git-remove-handled ()
   "Remove handled files from the status list."
@@ -883,10 +912,12 @@ and returns the process output as a string."
 
 (defun git-setup-diff-buffer (buffer)
   "Setup a buffer for displaying a diff."
-  (with-current-buffer buffer
-    (diff-mode)
-    (goto-char (point-min))
-    (setq buffer-read-only t))
+  (let ((dir default-directory))
+    (with-current-buffer buffer
+      (diff-mode)
+      (goto-char (point-min))
+      (setq default-directory dir)
+      (setq buffer-read-only t)))
   (display-buffer buffer)
   (shrink-window-if-larger-than-buffer))
 
@@ -936,7 +967,13 @@ and returns the process output as a string."
 (defun git-diff-file-idiff ()
   "Perform an interactive diff on the current file."
   (interactive)
-  (error "Interactive diffs not implemented yet."))
+  (let ((files (git-marked-files-state 'added 'deleted 'modified)))
+    (unless (eq 1 (length files))
+      (error "Cannot perform an interactive diff on multiple files."))
+    (let* ((filename (car (git-get-filenames files)))
+           (buff1 (find-file-noselect filename))
+           (buff2 (git-run-command-buffer (concat filename ".~HEAD~") "cat-file" "blob" (concat "HEAD:" filename))))
+      (ediff-buffers buff1 buff2))))
 
 (defun git-log-file ()
   "Display a log of changes to the marked file(s)."
@@ -1039,7 +1076,7 @@ and returns the process output as a string."
   (let ((info (ewoc-data (ewoc-locate git-status))))
     (find-file (git-fileinfo->name info))
     (when (eq 'unmerged (git-fileinfo->state info))
-      (smerge-mode))))
+      (smerge-mode 1))))
 
 (defun git-find-file-other-window ()
   "Visit the current file in its own buffer in another window."
@@ -1072,27 +1109,9 @@ and returns the process output as a string."
          (pos (ewoc-locate status))
          (cur-name (and pos (git-fileinfo->name (ewoc-data pos)))))
     (unless status (error "Not in git-status buffer."))
+    (git-run-command nil nil "update-index" "--refresh")
     (git-clear-status status)
-    (git-run-command nil nil "update-index" "--info-only" "--refresh")
-    (if (git-empty-db-p)
-        ; we need some special handling for an empty db
-        (with-temp-buffer
-          (git-run-command t nil "ls-files" "-z" "-t" "-c")
-          (git-parse-ls-files status 'added))
-      (with-temp-buffer
-        (git-run-command t nil "diff-index" "-z" "-M" "HEAD")
-        (git-parse-status status)))
-      (with-temp-buffer
-        (git-run-command t nil "ls-files" "-z" "-u")
-        (git-parse-ls-unmerged status))
-      (when (file-readable-p ".git/info/exclude")
-        (with-temp-buffer
-          (git-run-command t nil "ls-files" "-z" "-t" "-o"
-                           "--exclude-from=.git/info/exclude"
-                           (concat "--exclude-per-directory=" git-per-dir-ignore-file))
-          (git-parse-ls-files status 'unknown)))
-    (git-refresh-files)
-    (git-refresh-ewoc-hf status)
+    (git-update-status-files nil)
     ; move point to the current file name if any
     (let ((node (and cur-name (git-find-status-file status cur-name))))
       (when node (ewoc-goto-node status node)))))
index e456ab9712e0574c712e5f79b0dc55cac167d9b1..b8f6be5c0af64dfbe7e136f984404f22aea68130 100644 (file)
         (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
index 436d7caff5c26f7c0ff8c0a410bdabccdec0a900..2ae235b081ea9829a51758bba5a0d0d64111d32c 100755 (executable)
@@ -30,8 +30,8 @@ notbare|"")
 esac
 
 test "true" != "$pack_refs" ||
-git-pack-refs --prune &&
-git-reflog expire --all &&
+git pack-refs --prune &&
+git reflog expire --all &&
 git-repack -a -d -l &&
-$no_prune git-prune &&
-git-rerere gc || exit
+$no_prune git prune &&
+git rerere gc || exit
index 36b90e38494eb79b4859cf59301b5a4e6ccccea1..0ee1bd898ecbb725d13385408b4ed4bcb413f503 100755 (executable)
@@ -17,8 +17,8 @@ dropheads() {
                "$GIT_DIR/LAST_MERGE" || exit 1
 }
 
-head=$(git-rev-parse --verify "$1"^0) &&
-merge=$(git-rev-parse --verify "$2"^0) &&
+head=$(git rev-parse --verify "$1"^0) &&
+merge=$(git rev-parse --verify "$2"^0) &&
 merge_name="$2" &&
 merge_msg="$3" || usage
 
@@ -34,7 +34,7 @@ dropheads
 echo $head > "$GIT_DIR"/ORIG_HEAD
 echo $merge > "$GIT_DIR"/LAST_MERGE
 
-common=$(git-merge-base $head $merge)
+common=$(git merge-base $head $merge)
 if [ -z "$common" ]; then
        die "Unable to find common commit between" $merge $head
 fi
@@ -46,11 +46,11 @@ case "$common" in
        exit 0
        ;;
 "$head")
-       echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)"
-       git-read-tree -u -m $head $merge || exit 1
-       git-update-ref -m "resolve $merge_name: Fast forward" \
+       echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $merge)"
+       git read-tree -u -m $head $merge || exit 1
+       git update-ref -m "resolve $merge_name: Fast forward" \
                HEAD "$merge" "$head"
-       git-diff-tree -p $head $merge | git-apply --stat
+       git diff-tree -p $head $merge | git apply --stat
        dropheads
        exit 0
        ;;
@@ -62,7 +62,7 @@ git var GIT_COMMITTER_IDENT >/dev/null || exit
 # Find an optimum merge base if there are more than one candidates.
 LF='
 '
-common=$(git-merge-base -a $head $merge)
+common=$(git merge-base -a $head $merge)
 case "$common" in
 ?*"$LF"?*)
        echo "Trying to find the optimum merge base."
@@ -72,10 +72,10 @@ case "$common" in
        for c in $common
        do
                rm -f $G
-               GIT_INDEX_FILE=$G git-read-tree -m $c $head $merge \
+               GIT_INDEX_FILE=$G git read-tree -m $c $head $merge \
                        2>/dev/null || continue
                # Count the paths that are unmerged.
-               cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+               cnt=`GIT_INDEX_FILE=$G git ls-files --unmerged | wc -l`
                if test $best_cnt -le 0 -o $cnt -le $best_cnt
                then
                        best=$c
@@ -92,9 +92,9 @@ case "$common" in
 esac
 
 echo "Trying to merge $merge into $head using $common."
-git-update-index --refresh 2>/dev/null
-git-read-tree -u -m $common $head $merge || exit 1
-result_tree=$(git-write-tree  2> /dev/null)
+git update-index --refresh 2>/dev/null
+git read-tree -u -m $common $head $merge || exit 1
+result_tree=$(git write-tree  2> /dev/null)
 if [ $? -ne 0 ]; then
        echo "Simple merge failed, trying Automatic merge"
        git-merge-index -o git-merge-one-file -a
@@ -102,11 +102,11 @@ if [ $? -ne 0 ]; then
                echo $merge > "$GIT_DIR"/MERGE_HEAD
                die "Automatic merge failed, fix up by hand"
        fi
-       result_tree=$(git-write-tree) || exit 1
+       result_tree=$(git write-tree) || exit 1
 fi
-result_commit=$(echo "$merge_msg" | git-commit-tree $result_tree -p $head -p $merge)
+result_commit=$(echo "$merge_msg" | git commit-tree $result_tree -p $head -p $merge)
 echo "Committed merge $result_commit"
-git-update-ref -m "resolve $merge_name: In-index merge" \
+git update-ref -m "resolve $merge_name: In-index merge" \
        HEAD "$result_commit" "$head"
-git-diff-tree -p $head $result_commit | git-apply --stat
+git diff-tree -p $head $result_commit | git apply --stat
 dropheads
diff --git a/contrib/examples/git-tag.sh b/contrib/examples/git-tag.sh
new file mode 100755 (executable)
index 0000000..5ee3f50
--- /dev/null
@@ -0,0 +1,205 @@
+#!/bin/sh
+# Copyright (c) 2005 Linus Torvalds
+
+USAGE='[-n [<num>]] -l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+message_given=
+annotate=
+signed=
+force=
+message=
+username=
+list=
+verify=
+LINES=0
+while case "$#" in 0) break ;; esac
+do
+    case "$1" in
+    -a)
+       annotate=1
+       shift
+       ;;
+    -s)
+       annotate=1
+       signed=1
+       shift
+       ;;
+    -f)
+       force=1
+       shift
+       ;;
+    -n)
+        case "$#,$2" in
+       1,* | *,-*)
+               LINES=1         # no argument
+               ;;
+       *)      shift
+               LINES=$(expr "$1" : '\([0-9]*\)')
+               [ -z "$LINES" ] && LINES=1 # 1 line is default when -n is used
+               ;;
+       esac
+       shift
+       ;;
+    -l)
+       list=1
+       shift
+       case $# in
+       0)      PATTERN=
+               ;;
+       *)
+               PATTERN="$1"    # select tags by shell pattern, not re
+               shift
+               ;;
+       esac
+       git rev-parse --symbolic --tags | sort |
+           while read TAG
+           do
+               case "$TAG" in
+               *$PATTERN*) ;;
+               *)          continue ;;
+               esac
+               [ "$LINES" -le 0 ] && { echo "$TAG"; continue ;}
+               OBJTYPE=$(git cat-file -t "$TAG")
+               case $OBJTYPE in
+               tag)
+                       ANNOTATION=$(git cat-file tag "$TAG" |
+                               sed -e '1,/^$/d' |
+                               sed -n -e "
+                                       /^-----BEGIN PGP SIGNATURE-----\$/q
+                                       2,\$s/^/    /
+                                       p
+                                       ${LINES}q
+                               ")
+                       printf "%-15s %s\n" "$TAG" "$ANNOTATION"
+                       ;;
+               *)      echo "$TAG"
+                       ;;
+               esac
+           done
+       ;;
+    -m)
+       annotate=1
+       shift
+       message="$1"
+       if test "$#" = "0"; then
+           die "error: option -m needs an argument"
+       else
+           message="$1"
+           message_given=1
+           shift
+       fi
+       ;;
+    -F)
+       annotate=1
+       shift
+       if test "$#" = "0"; then
+           die "error: option -F needs an argument"
+       else
+           message="$(cat "$1")"
+           message_given=1
+           shift
+       fi
+       ;;
+    -u)
+       annotate=1
+       signed=1
+       shift
+       if test "$#" = "0"; then
+           die "error: option -u needs an argument"
+       else
+           username="$1"
+           shift
+       fi
+       ;;
+    -d)
+       shift
+       had_error=0
+       for tag
+       do
+               cur=$(git show-ref --verify --hash -- "refs/tags/$tag") || {
+                       echo >&2 "Seriously, what tag are you talking about?"
+                       had_error=1
+                       continue
+               }
+               git update-ref -m 'tag: delete' -d "refs/tags/$tag" "$cur" || {
+                       had_error=1
+                       continue
+               }
+               echo "Deleted tag $tag."
+       done
+       exit $had_error
+       ;;
+    -v)
+       shift
+       tag_name="$1"
+       tag=$(git show-ref --verify --hash -- "refs/tags/$tag_name") ||
+               die "Seriously, what tag are you talking about?"
+       git-verify-tag -v "$tag"
+       exit $?
+       ;;
+    -*)
+        usage
+       ;;
+    *)
+       break
+       ;;
+    esac
+done
+
+[ -n "$list" ] && exit 0
+
+name="$1"
+[ "$name" ] || usage
+prev=0000000000000000000000000000000000000000
+if git show-ref --verify --quiet -- "refs/tags/$name"
+then
+    test -n "$force" || die "tag '$name' already exists"
+    prev=`git rev-parse "refs/tags/$name"`
+fi
+shift
+git check-ref-format "tags/$name" ||
+       die "we do not like '$name' as a tag name."
+
+object=$(git rev-parse --verify --default HEAD "$@") || exit 1
+type=$(git cat-file -t $object) || exit 1
+tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
+
+test -n "$username" ||
+       username=$(git repo-config user.signingkey) ||
+       username=$(expr "z$tagger" : 'z\(.*>\)')
+
+trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
+
+if [ "$annotate" ]; then
+    if [ -z "$message_given" ]; then
+        ( echo "#"
+          echo "# Write a tag message"
+          echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
+        git_editor "$GIT_DIR"/TAG_EDITMSG || exit
+    else
+        printf '%s\n' "$message" >"$GIT_DIR"/TAG_EDITMSG
+    fi
+
+    grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
+    git stripspace >"$GIT_DIR"/TAG_FINALMSG
+
+    [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
+       echo >&2 "No tag message?"
+       exit 1
+    }
+
+    ( printf 'object %s\ntype %s\ntag %s\ntagger %s\n\n' \
+       "$object" "$type" "$name" "$tagger";
+      cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
+    rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
+    if [ "$signed" ]; then
+       gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
+       cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
+       die "failed to sign the tag with GPG."
+    fi
+    object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
+fi
+
+git update-ref "refs/tags/$name" "$object" "$prev"
diff --git a/contrib/examples/git-verify-tag.sh b/contrib/examples/git-verify-tag.sh
new file mode 100755 (executable)
index 0000000..37b0023
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/sh
+
+USAGE='<tag>'
+SUBDIRECTORY_OK='Yes'
+. git-sh-setup
+
+verbose=
+while case $# in 0) break;; esac
+do
+       case "$1" in
+       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+               verbose=t ;;
+       *)
+               break ;;
+       esac
+       shift
+done
+
+if [ "$#" != "1" ]
+then
+       usage
+fi
+
+type="$(git cat-file -t "$1" 2>/dev/null)" ||
+       die "$1: no such object."
+
+test "$type" = tag ||
+       die "$1: cannot verify a non-tag object of type $type."
+
+case "$verbose" in
+t)
+       git cat-file -p "$1" |
+       sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
+       ;;
+esac
+
+trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0
+
+git cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
+sed -n -e '
+       /^-----BEGIN PGP SIGNATURE-----$/q
+       p
+' <"$GIT_DIR/.tmp-vtag" |
+gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1
+rm -f "$GIT_DIR/.tmp-vtag"
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
new file mode 100755 (executable)
index 0000000..6d01062
--- /dev/null
@@ -0,0 +1,1621 @@
+#!/usr/bin/env python
+#
+# git-p4.py -- A tool for bidirectional operation between a Perforce depot and git.
+#
+# Author: Simon Hausmann <simon@lst.de>
+# Copyright: 2007 Simon Hausmann <simon@lst.de>
+#            2007 Trolltech ASA
+# License: MIT <http://www.opensource.org/licenses/mit-license.php>
+#
+
+import optparse, sys, os, marshal, popen2, subprocess, shelve
+import tempfile, getopt, sha, os.path, time, platform
+import re
+
+from sets import Set;
+
+verbose = False
+
+def die(msg):
+    if verbose:
+        raise Exception(msg)
+    else:
+        sys.stderr.write(msg + "\n")
+        sys.exit(1)
+
+def write_pipe(c, str):
+    if verbose:
+        sys.stderr.write('Writing pipe: %s\n' % c)
+
+    pipe = os.popen(c, 'w')
+    val = pipe.write(str)
+    if pipe.close():
+        die('Command failed: %s' % c)
+
+    return val
+
+def read_pipe(c, ignore_error=False):
+    if verbose:
+        sys.stderr.write('Reading pipe: %s\n' % c)
+
+    pipe = os.popen(c, 'rb')
+    val = pipe.read()
+    if pipe.close() and not ignore_error:
+        die('Command failed: %s' % c)
+
+    return val
+
+
+def read_pipe_lines(c):
+    if verbose:
+        sys.stderr.write('Reading pipe: %s\n' % c)
+    ## todo: check return status
+    pipe = os.popen(c, 'rb')
+    val = pipe.readlines()
+    if pipe.close():
+        die('Command failed: %s' % c)
+
+    return val
+
+def system(cmd):
+    if verbose:
+        sys.stderr.write("executing %s\n" % cmd)
+    if os.system(cmd) != 0:
+        die("command failed: %s" % cmd)
+
+def p4CmdList(cmd, stdin=None, stdin_mode='w+b'):
+    cmd = "p4 -G %s" % cmd
+    if verbose:
+        sys.stderr.write("Opening pipe: %s\n" % cmd)
+
+    # Use a temporary file to avoid deadlocks without
+    # subprocess.communicate(), which would put another copy
+    # of stdout into memory.
+    stdin_file = None
+    if stdin is not None:
+        stdin_file = tempfile.TemporaryFile(prefix='p4-stdin', mode=stdin_mode)
+        stdin_file.write(stdin)
+        stdin_file.flush()
+        stdin_file.seek(0)
+
+    p4 = subprocess.Popen(cmd, shell=True,
+                          stdin=stdin_file,
+                          stdout=subprocess.PIPE)
+
+    result = []
+    try:
+        while True:
+            entry = marshal.load(p4.stdout)
+            result.append(entry)
+    except EOFError:
+        pass
+    exitCode = p4.wait()
+    if exitCode != 0:
+        entry = {}
+        entry["p4ExitCode"] = exitCode
+        result.append(entry)
+
+    return result
+
+def p4Cmd(cmd):
+    list = p4CmdList(cmd)
+    result = {}
+    for entry in list:
+        result.update(entry)
+    return result;
+
+def p4Where(depotPath):
+    if not depotPath.endswith("/"):
+        depotPath += "/"
+    output = p4Cmd("where %s..." % depotPath)
+    if output["code"] == "error":
+        return ""
+    clientPath = ""
+    if "path" in output:
+        clientPath = output.get("path")
+    elif "data" in output:
+        data = output.get("data")
+        lastSpace = data.rfind(" ")
+        clientPath = data[lastSpace + 1:]
+
+    if clientPath.endswith("..."):
+        clientPath = clientPath[:-3]
+    return clientPath
+
+def currentGitBranch():
+    return read_pipe("git name-rev HEAD").split(" ")[1].strip()
+
+def isValidGitDir(path):
+    if (os.path.exists(path + "/HEAD")
+        and os.path.exists(path + "/refs") and os.path.exists(path + "/objects")):
+        return True;
+    return False
+
+def parseRevision(ref):
+    return read_pipe("git rev-parse %s" % ref).strip()
+
+def extractLogMessageFromGitCommit(commit):
+    logMessage = ""
+
+    ## fixme: title is first line of commit, not 1st paragraph.
+    foundTitle = False
+    for log in read_pipe_lines("git cat-file commit %s" % commit):
+       if not foundTitle:
+           if len(log) == 1:
+               foundTitle = True
+           continue
+
+       logMessage += log
+    return logMessage
+
+def extractSettingsGitLog(log):
+    values = {}
+    for line in log.split("\n"):
+        line = line.strip()
+        m = re.search (r"^ *\[git-p4: (.*)\]$", line)
+        if not m:
+            continue
+
+        assignments = m.group(1).split (':')
+        for a in assignments:
+            vals = a.split ('=')
+            key = vals[0].strip()
+            val = ('='.join (vals[1:])).strip()
+            if val.endswith ('\"') and val.startswith('"'):
+                val = val[1:-1]
+
+            values[key] = val
+
+    paths = values.get("depot-paths")
+    if not paths:
+        paths = values.get("depot-path")
+    if paths:
+        values['depot-paths'] = paths.split(',')
+    return values
+
+def gitBranchExists(branch):
+    proc = subprocess.Popen(["git", "rev-parse", branch],
+                            stderr=subprocess.PIPE, stdout=subprocess.PIPE);
+    return proc.wait() == 0;
+
+def gitConfig(key):
+    return read_pipe("git config %s" % key, ignore_error=True).strip()
+
+def p4BranchesInGit(branchesAreInRemotes = True):
+    branches = {}
+
+    cmdline = "git rev-parse --symbolic "
+    if branchesAreInRemotes:
+        cmdline += " --remotes"
+    else:
+        cmdline += " --branches"
+
+    for line in read_pipe_lines(cmdline):
+        line = line.strip()
+
+        ## only import to p4/
+        if not line.startswith('p4/') or line == "p4/HEAD":
+            continue
+        branch = line
+
+        # strip off p4
+        branch = re.sub ("^p4/", "", line)
+
+        branches[branch] = parseRevision(line)
+    return branches
+
+def findUpstreamBranchPoint(head = "HEAD"):
+    branches = p4BranchesInGit()
+    # map from depot-path to branch name
+    branchByDepotPath = {}
+    for branch in branches.keys():
+        tip = branches[branch]
+        log = extractLogMessageFromGitCommit(tip)
+        settings = extractSettingsGitLog(log)
+        if settings.has_key("depot-paths"):
+            paths = ",".join(settings["depot-paths"])
+            branchByDepotPath[paths] = "remotes/p4/" + branch
+
+    settings = None
+    parent = 0
+    while parent < 65535:
+        commit = head + "~%s" % parent
+        log = extractLogMessageFromGitCommit(commit)
+        settings = extractSettingsGitLog(log)
+        if settings.has_key("depot-paths"):
+            paths = ",".join(settings["depot-paths"])
+            if branchByDepotPath.has_key(paths):
+                return [branchByDepotPath[paths], settings]
+
+        parent = parent + 1
+
+    return ["", settings]
+
+class Command:
+    def __init__(self):
+        self.usage = "usage: %prog [options]"
+        self.needsGit = True
+
+class P4Debug(Command):
+    def __init__(self):
+        Command.__init__(self)
+        self.options = [
+            optparse.make_option("--verbose", dest="verbose", action="store_true",
+                                 default=False),
+            ]
+        self.description = "A tool to debug the output of p4 -G."
+        self.needsGit = False
+        self.verbose = False
+
+    def run(self, args):
+        j = 0
+        for output in p4CmdList(" ".join(args)):
+            print 'Element: %d' % j
+            j += 1
+            print output
+        return True
+
+class P4RollBack(Command):
+    def __init__(self):
+        Command.__init__(self)
+        self.options = [
+            optparse.make_option("--verbose", dest="verbose", action="store_true"),
+            optparse.make_option("--local", dest="rollbackLocalBranches", action="store_true")
+        ]
+        self.description = "A tool to debug the multi-branch import. Don't use :)"
+        self.verbose = False
+        self.rollbackLocalBranches = False
+
+    def run(self, args):
+        if len(args) != 1:
+            return False
+        maxChange = int(args[0])
+
+        if "p4ExitCode" in p4Cmd("changes -m 1"):
+            die("Problems executing p4");
+
+        if self.rollbackLocalBranches:
+            refPrefix = "refs/heads/"
+            lines = read_pipe_lines("git rev-parse --symbolic --branches")
+        else:
+            refPrefix = "refs/remotes/"
+            lines = read_pipe_lines("git rev-parse --symbolic --remotes")
+
+        for line in lines:
+            if self.rollbackLocalBranches or (line.startswith("p4/") and line != "p4/HEAD\n"):
+                line = line.strip()
+                ref = refPrefix + line
+                log = extractLogMessageFromGitCommit(ref)
+                settings = extractSettingsGitLog(log)
+
+                depotPaths = settings['depot-paths']
+                change = settings['change']
+
+                changed = False
+
+                if len(p4Cmd("changes -m 1 "  + ' '.join (['%s...@%s' % (p, maxChange)
+                                                           for p in depotPaths]))) == 0:
+                    print "Branch %s did not exist at change %s, deleting." % (ref, maxChange)
+                    system("git update-ref -d %s `git rev-parse %s`" % (ref, ref))
+                    continue
+
+                while change and int(change) > maxChange:
+                    changed = True
+                    if self.verbose:
+                        print "%s is at %s ; rewinding towards %s" % (ref, change, maxChange)
+                    system("git update-ref %s \"%s^\"" % (ref, ref))
+                    log = extractLogMessageFromGitCommit(ref)
+                    settings =  extractSettingsGitLog(log)
+
+
+                    depotPaths = settings['depot-paths']
+                    change = settings['change']
+
+                if changed:
+                    print "%s rewound to %s" % (ref, change)
+
+        return True
+
+class P4Submit(Command):
+    def __init__(self):
+        Command.__init__(self)
+        self.options = [
+                optparse.make_option("--continue", action="store_false", dest="firstTime"),
+                optparse.make_option("--verbose", dest="verbose", action="store_true"),
+                optparse.make_option("--origin", dest="origin"),
+                optparse.make_option("--reset", action="store_true", dest="reset"),
+                optparse.make_option("--log-substitutions", dest="substFile"),
+                optparse.make_option("--dry-run", action="store_true"),
+                optparse.make_option("--direct", dest="directSubmit", action="store_true"),
+                optparse.make_option("--trust-me-like-a-fool", dest="trustMeLikeAFool", action="store_true"),
+        ]
+        self.description = "Submit changes from git to the perforce depot."
+        self.usage += " [name of git branch to submit into perforce depot]"
+        self.firstTime = True
+        self.reset = False
+        self.interactive = True
+        self.dryRun = False
+        self.substFile = ""
+        self.firstTime = True
+        self.origin = ""
+        self.directSubmit = False
+        self.trustMeLikeAFool = False
+        self.verbose = False
+        self.isWindows = (platform.system() == "Windows")
+
+        self.logSubstitutions = {}
+        self.logSubstitutions["<enter description here>"] = "%log%"
+        self.logSubstitutions["\tDetails:"] = "\tDetails:  %log%"
+
+    def check(self):
+        if len(p4CmdList("opened ...")) > 0:
+            die("You have files opened with perforce! Close them before starting the sync.")
+
+    def start(self):
+        if len(self.config) > 0 and not self.reset:
+            die("Cannot start sync. Previous sync config found at %s\n"
+                "If you want to start submitting again from scratch "
+                "maybe you want to call git-p4 submit --reset" % self.configFile)
+
+        commits = []
+        if self.directSubmit:
+            commits.append("0")
+        else:
+            for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
+                commits.append(line.strip())
+            commits.reverse()
+
+        self.config["commits"] = commits
+
+    def prepareLogMessage(self, template, message):
+        result = ""
+
+        for line in template.split("\n"):
+            if line.startswith("#"):
+                result += line + "\n"
+                continue
+
+            substituted = False
+            for key in self.logSubstitutions.keys():
+                if line.find(key) != -1:
+                    value = self.logSubstitutions[key]
+                    value = value.replace("%log%", message)
+                    if value != "@remove@":
+                        result += line.replace(key, value) + "\n"
+                    substituted = True
+                    break
+
+            if not substituted:
+                result += line + "\n"
+
+        return result
+
+    def prepareSubmitTemplate(self):
+        # remove lines in the Files section that show changes to files outside the depot path we're committing into
+        template = ""
+        inFilesSection = False
+        for line in read_pipe_lines("p4 change -o"):
+            if inFilesSection:
+                if line.startswith("\t"):
+                    # path starts and ends with a tab
+                    path = line[1:]
+                    lastTab = path.rfind("\t")
+                    if lastTab != -1:
+                        path = path[:lastTab]
+                        if not path.startswith(self.depotPath):
+                            continue
+                else:
+                    inFilesSection = False
+            else:
+                if line.startswith("Files:"):
+                    inFilesSection = True
+
+            template += line
+
+        return template
+
+    def applyCommit(self, id):
+        if self.directSubmit:
+            print "Applying local change in working directory/index"
+            diff = self.diffStatus
+        else:
+            print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+            diff = read_pipe_lines("git diff-tree -r --name-status \"%s^\" \"%s\"" % (id, id))
+        filesToAdd = set()
+        filesToDelete = set()
+        editedFiles = set()
+        for line in diff:
+            modifier = line[0]
+            path = line[1:].strip()
+            if modifier == "M":
+                system("p4 edit \"%s\"" % path)
+                editedFiles.add(path)
+            elif modifier == "A":
+                filesToAdd.add(path)
+                if path in filesToDelete:
+                    filesToDelete.remove(path)
+            elif modifier == "D":
+                filesToDelete.add(path)
+                if path in filesToAdd:
+                    filesToAdd.remove(path)
+            else:
+                die("unknown modifier %s for %s" % (modifier, path))
+
+        if self.directSubmit:
+            diffcmd = "cat \"%s\"" % self.diffFile
+        else:
+            diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
+        patchcmd = diffcmd + " | git apply "
+        tryPatchCmd = patchcmd + "--check -"
+        applyPatchCmd = patchcmd + "--check --apply -"
+
+        if os.system(tryPatchCmd) != 0:
+            print "Unfortunately applying the change failed!"
+            print "What do you want to do?"
+            response = "x"
+            while response != "s" and response != "a" and response != "w":
+                response = raw_input("[s]kip this patch / [a]pply the patch forcibly "
+                                     "and with .rej files / [w]rite the patch to a file (patch.txt) ")
+            if response == "s":
+                print "Skipping! Good luck with the next patches..."
+                return
+            elif response == "a":
+                os.system(applyPatchCmd)
+                if len(filesToAdd) > 0:
+                    print "You may also want to call p4 add on the following files:"
+                    print " ".join(filesToAdd)
+                if len(filesToDelete):
+                    print "The following files should be scheduled for deletion with p4 delete:"
+                    print " ".join(filesToDelete)
+                die("Please resolve and submit the conflict manually and "
+                    + "continue afterwards with git-p4 submit --continue")
+            elif response == "w":
+                system(diffcmd + " > patch.txt")
+                print "Patch saved to patch.txt in %s !" % self.clientPath
+                die("Please resolve and submit the conflict manually and "
+                    "continue afterwards with git-p4 submit --continue")
+
+        system(applyPatchCmd)
+
+        for f in filesToAdd:
+            system("p4 add \"%s\"" % f)
+        for f in filesToDelete:
+            system("p4 revert \"%s\"" % f)
+            system("p4 delete \"%s\"" % f)
+
+        logMessage = ""
+        if not self.directSubmit:
+            logMessage = extractLogMessageFromGitCommit(id)
+            logMessage = logMessage.replace("\n", "\n\t")
+            if self.isWindows:
+                logMessage = logMessage.replace("\n", "\r\n")
+            logMessage = logMessage.strip()
+
+        template = self.prepareSubmitTemplate()
+
+        if self.interactive:
+            submitTemplate = self.prepareLogMessage(template, logMessage)
+            diff = read_pipe("p4 diff -du ...")
+
+            for newFile in filesToAdd:
+                diff += "==== new file ====\n"
+                diff += "--- /dev/null\n"
+                diff += "+++ %s\n" % newFile
+                f = open(newFile, "r")
+                for line in f.readlines():
+                    diff += "+" + line
+                f.close()
+
+            separatorLine = "######## everything below this line is just the diff #######"
+            if platform.system() == "Windows":
+                separatorLine += "\r"
+            separatorLine += "\n"
+
+            response = "e"
+            if self.trustMeLikeAFool:
+                response = "y"
+
+            firstIteration = True
+            while response == "e":
+                if not firstIteration:
+                    response = raw_input("Do you want to submit this change? [y]es/[e]dit/[n]o/[s]kip ")
+                firstIteration = False
+                if response == "e":
+                    [handle, fileName] = tempfile.mkstemp()
+                    tmpFile = os.fdopen(handle, "w+")
+                    tmpFile.write(submitTemplate + separatorLine + diff)
+                    tmpFile.close()
+                    defaultEditor = "vi"
+                    if platform.system() == "Windows":
+                        defaultEditor = "notepad"
+                    editor = os.environ.get("EDITOR", defaultEditor);
+                    system(editor + " " + fileName)
+                    tmpFile = open(fileName, "rb")
+                    message = tmpFile.read()
+                    tmpFile.close()
+                    os.remove(fileName)
+                    submitTemplate = message[:message.index(separatorLine)]
+                    if self.isWindows:
+                        submitTemplate = submitTemplate.replace("\r\n", "\n")
+
+            if response == "y" or response == "yes":
+               if self.dryRun:
+                   print submitTemplate
+                   raw_input("Press return to continue...")
+               else:
+                   if self.directSubmit:
+                       print "Submitting to git first"
+                       os.chdir(self.oldWorkingDirectory)
+                       write_pipe("git commit -a -F -", submitTemplate)
+                       os.chdir(self.clientPath)
+
+                   write_pipe("p4 submit -i", submitTemplate)
+            elif response == "s":
+                for f in editedFiles:
+                    system("p4 revert \"%s\"" % f);
+                for f in filesToAdd:
+                    system("p4 revert \"%s\"" % f);
+                    system("rm %s" %f)
+                for f in filesToDelete:
+                    system("p4 delete \"%s\"" % f);
+                return
+            else:
+                print "Not submitting!"
+                self.interactive = False
+        else:
+            fileName = "submit.txt"
+            file = open(fileName, "w+")
+            file.write(self.prepareLogMessage(template, logMessage))
+            file.close()
+            print ("Perforce submit template written as %s. "
+                   + "Please review/edit and then use p4 submit -i < %s to submit directly!"
+                   % (fileName, fileName))
+
+    def run(self, args):
+        if len(args) == 0:
+            self.master = currentGitBranch()
+            if len(self.master) == 0 or not gitBranchExists("refs/heads/%s" % self.master):
+                die("Detecting current git branch failed!")
+        elif len(args) == 1:
+            self.master = args[0]
+        else:
+            return False
+
+        [upstream, settings] = findUpstreamBranchPoint()
+        self.depotPath = settings['depot-paths'][0]
+        if len(self.origin) == 0:
+            self.origin = upstream
+
+        if self.verbose:
+            print "Origin branch is " + self.origin
+
+        if len(self.depotPath) == 0:
+            print "Internal error: cannot locate perforce depot path from existing branches"
+            sys.exit(128)
+
+        self.clientPath = p4Where(self.depotPath)
+
+        if len(self.clientPath) == 0:
+            print "Error: Cannot locate perforce checkout of %s in client view" % self.depotPath
+            sys.exit(128)
+
+        print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
+        self.oldWorkingDirectory = os.getcwd()
+
+        if self.directSubmit:
+            self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
+            if len(self.diffStatus) == 0:
+                print "No changes in working directory to submit."
+                return True
+            patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
+            self.diffFile = self.gitdir + "/p4-git-diff"
+            f = open(self.diffFile, "wb")
+            f.write(patch)
+            f.close();
+
+        os.chdir(self.clientPath)
+        response = raw_input("Do you want to sync %s with p4 sync? [y]es/[n]o " % self.clientPath)
+        if response == "y" or response == "yes":
+            system("p4 sync ...")
+
+        if self.reset:
+            self.firstTime = True
+
+        if len(self.substFile) > 0:
+            for line in open(self.substFile, "r").readlines():
+                tokens = line.strip().split("=")
+                self.logSubstitutions[tokens[0]] = tokens[1]
+
+        self.check()
+        self.configFile = self.gitdir + "/p4-git-sync.cfg"
+        self.config = shelve.open(self.configFile, writeback=True)
+
+        if self.firstTime:
+            self.start()
+
+        commits = self.config.get("commits", [])
+
+        while len(commits) > 0:
+            self.firstTime = False
+            commit = commits[0]
+            commits = commits[1:]
+            self.config["commits"] = commits
+            self.applyCommit(commit)
+            if not self.interactive:
+                break
+
+        self.config.close()
+
+        if self.directSubmit:
+            os.remove(self.diffFile)
+
+        if len(commits) == 0:
+            if self.firstTime:
+                print "No changes found to apply between %s and current HEAD" % self.origin
+            else:
+                print "All changes applied!"
+                os.chdir(self.oldWorkingDirectory)
+                response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ")
+                if response == "y" or response == "yes":
+                    rebase = P4Rebase()
+                    rebase.run([])
+            os.remove(self.configFile)
+
+        return True
+
+class P4Sync(Command):
+    def __init__(self):
+        Command.__init__(self)
+        self.options = [
+                optparse.make_option("--branch", dest="branch"),
+                optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
+                optparse.make_option("--changesfile", dest="changesFile"),
+                optparse.make_option("--silent", dest="silent", action="store_true"),
+                optparse.make_option("--detect-labels", dest="detectLabels", action="store_true"),
+                optparse.make_option("--verbose", dest="verbose", action="store_true"),
+                optparse.make_option("--import-local", dest="importIntoRemotes", action="store_false",
+                                     help="Import into refs/heads/ , not refs/remotes"),
+                optparse.make_option("--max-changes", dest="maxChanges"),
+                optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
+                                     help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
+        ]
+        self.description = """Imports from Perforce into a git repository.\n
+    example:
+    //depot/my/project/ -- to import the current head
+    //depot/my/project/@all -- to import everything
+    //depot/my/project/@1,6 -- to import only from revision 1 to 6
+
+    (a ... is not needed in the path p4 specification, it's added implicitly)"""
+
+        self.usage += " //depot/path[@revRange]"
+        self.silent = False
+        self.createdBranches = Set()
+        self.committedChanges = Set()
+        self.branch = ""
+        self.detectBranches = False
+        self.detectLabels = False
+        self.changesFile = ""
+        self.syncWithOrigin = True
+        self.verbose = False
+        self.importIntoRemotes = True
+        self.maxChanges = ""
+        self.isWindows = (platform.system() == "Windows")
+        self.keepRepoPath = False
+        self.depotPaths = None
+        self.p4BranchesInGit = []
+
+        if gitConfig("git-p4.syncFromOrigin") == "false":
+            self.syncWithOrigin = False
+
+    def extractFilesFromCommit(self, commit):
+        files = []
+        fnum = 0
+        while commit.has_key("depotFile%s" % fnum):
+            path =  commit["depotFile%s" % fnum]
+
+            found = [p for p in self.depotPaths
+                     if path.startswith (p)]
+            if not found:
+                fnum = fnum + 1
+                continue
+
+            file = {}
+            file["path"] = path
+            file["rev"] = commit["rev%s" % fnum]
+            file["action"] = commit["action%s" % fnum]
+            file["type"] = commit["type%s" % fnum]
+            files.append(file)
+            fnum = fnum + 1
+        return files
+
+    def stripRepoPath(self, path, prefixes):
+        if self.keepRepoPath:
+            prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
+
+        for p in prefixes:
+            if path.startswith(p):
+                path = path[len(p):]
+
+        return path
+
+    def splitFilesIntoBranches(self, commit):
+        branches = {}
+        fnum = 0
+        while commit.has_key("depotFile%s" % fnum):
+            path =  commit["depotFile%s" % fnum]
+            found = [p for p in self.depotPaths
+                     if path.startswith (p)]
+            if not found:
+                fnum = fnum + 1
+                continue
+
+            file = {}
+            file["path"] = path
+            file["rev"] = commit["rev%s" % fnum]
+            file["action"] = commit["action%s" % fnum]
+            file["type"] = commit["type%s" % fnum]
+            fnum = fnum + 1
+
+            relPath = self.stripRepoPath(path, self.depotPaths)
+
+            for branch in self.knownBranches.keys():
+
+                # add a trailing slash so that a commit into qt/4.2foo doesn't end up in qt/4.2
+                if relPath.startswith(branch + "/"):
+                    if branch not in branches:
+                        branches[branch] = []
+                    branches[branch].append(file)
+                    break
+
+        return branches
+
+    ## Should move this out, doesn't use SELF.
+    def readP4Files(self, files):
+        files = [f for f in files
+                 if f['action'] != 'delete']
+
+        if not files:
+            return
+
+        filedata = p4CmdList('-x - print',
+                             stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
+                                              for f in files]),
+                             stdin_mode='w+')
+        if "p4ExitCode" in filedata[0]:
+            die("Problems executing p4. Error: [%d]."
+                % (filedata[0]['p4ExitCode']));
+
+        j = 0;
+        contents = {}
+        while j < len(filedata):
+            stat = filedata[j]
+            j += 1
+            text = ''
+            while j < len(filedata) and filedata[j]['code'] in ('text',
+                                                                'binary'):
+                text += filedata[j]['data']
+                j += 1
+
+
+            if not stat.has_key('depotFile'):
+                sys.stderr.write("p4 print fails with: %s\n" % repr(stat))
+                continue
+
+            contents[stat['depotFile']] = text
+
+        for f in files:
+            assert not f.has_key('data')
+            f['data'] = contents[f['path']]
+
+    def commit(self, details, files, branch, branchPrefixes, parent = ""):
+        epoch = details["time"]
+        author = details["user"]
+
+        if self.verbose:
+            print "commit into %s" % branch
+
+        # start with reading files; if that fails, we should not
+        # create a commit.
+        new_files = []
+        for f in files:
+            if [p for p in branchPrefixes if f['path'].startswith(p)]:
+                new_files.append (f)
+            else:
+                sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
+        files = new_files
+        self.readP4Files(files)
+
+
+
+
+        self.gitStream.write("commit %s\n" % branch)
+#        gitStream.write("mark :%s\n" % details["change"])
+        self.committedChanges.add(int(details["change"]))
+        committer = ""
+        if author not in self.users:
+            self.getUserMapFromPerforceServer()
+        if author in self.users:
+            committer = "%s %s %s" % (self.users[author], epoch, self.tz)
+        else:
+            committer = "%s <a@b> %s %s" % (author, epoch, self.tz)
+
+        self.gitStream.write("committer %s\n" % committer)
+
+        self.gitStream.write("data <<EOT\n")
+        self.gitStream.write(details["desc"])
+        self.gitStream.write("\n[git-p4: depot-paths = \"%s\": change = %s"
+                             % (','.join (branchPrefixes), details["change"]))
+        if len(details['options']) > 0:
+            self.gitStream.write(": options = %s" % details['options'])
+        self.gitStream.write("]\nEOT\n\n")
+
+        if len(parent) > 0:
+            if self.verbose:
+                print "parent %s" % parent
+            self.gitStream.write("from %s\n" % parent)
+
+        for file in files:
+            if file["type"] == "apple":
+                print "\nfile %s is a strange apple file that forks. Ignoring!" % file['path']
+                continue
+
+            relPath = self.stripRepoPath(file['path'], branchPrefixes)
+            if file["action"] == "delete":
+                self.gitStream.write("D %s\n" % relPath)
+            else:
+                data = file['data']
+
+                mode = "644"
+                if file["type"].startswith("x"):
+                    mode = "755"
+                elif file["type"] == "symlink":
+                    mode = "120000"
+                    # p4 print on a symlink contains "target\n", so strip it off
+                    data = data[:-1]
+
+                if self.isWindows and file["type"].endswith("text"):
+                    data = data.replace("\r\n", "\n")
+
+                self.gitStream.write("M %s inline %s\n" % (mode, relPath))
+                self.gitStream.write("data %s\n" % len(data))
+                self.gitStream.write(data)
+                self.gitStream.write("\n")
+
+        self.gitStream.write("\n")
+
+        change = int(details["change"])
+
+        if self.labels.has_key(change):
+            label = self.labels[change]
+            labelDetails = label[0]
+            labelRevisions = label[1]
+            if self.verbose:
+                print "Change %s is labelled %s" % (change, labelDetails)
+
+            files = p4CmdList("files " + ' '.join (["%s...@%s" % (p, change)
+                                                    for p in branchPrefixes]))
+
+            if len(files) == len(labelRevisions):
+
+                cleanedFiles = {}
+                for info in files:
+                    if info["action"] == "delete":
+                        continue
+                    cleanedFiles[info["depotFile"]] = info["rev"]
+
+                if cleanedFiles == labelRevisions:
+                    self.gitStream.write("tag tag_%s\n" % labelDetails["label"])
+                    self.gitStream.write("from %s\n" % branch)
+
+                    owner = labelDetails["Owner"]
+                    tagger = ""
+                    if author in self.users:
+                        tagger = "%s %s %s" % (self.users[owner], epoch, self.tz)
+                    else:
+                        tagger = "%s <a@b> %s %s" % (owner, epoch, self.tz)
+                    self.gitStream.write("tagger %s\n" % tagger)
+                    self.gitStream.write("data <<EOT\n")
+                    self.gitStream.write(labelDetails["Description"])
+                    self.gitStream.write("EOT\n\n")
+
+                else:
+                    if not self.silent:
+                        print ("Tag %s does not match with change %s: files do not match."
+                               % (labelDetails["label"], change))
+
+            else:
+                if not self.silent:
+                    print ("Tag %s does not match with change %s: file count is different."
+                           % (labelDetails["label"], change))
+
+    def getUserCacheFilename(self):
+        home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
+        return home + "/.gitp4-usercache.txt"
+
+    def getUserMapFromPerforceServer(self):
+        if self.userMapFromPerforceServer:
+            return
+        self.users = {}
+
+        for output in p4CmdList("users"):
+            if not output.has_key("User"):
+                continue
+            self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
+
+
+        s = ''
+        for (key, val) in self.users.items():
+            s += "%s\t%s\n" % (key, val)
+
+        open(self.getUserCacheFilename(), "wb").write(s)
+        self.userMapFromPerforceServer = True
+
+    def loadUserMapFromCache(self):
+        self.users = {}
+        self.userMapFromPerforceServer = False
+        try:
+            cache = open(self.getUserCacheFilename(), "rb")
+            lines = cache.readlines()
+            cache.close()
+            for line in lines:
+                entry = line.strip().split("\t")
+                self.users[entry[0]] = entry[1]
+        except IOError:
+            self.getUserMapFromPerforceServer()
+
+    def getLabels(self):
+        self.labels = {}
+
+        l = p4CmdList("labels %s..." % ' '.join (self.depotPaths))
+        if len(l) > 0 and not self.silent:
+            print "Finding files belonging to labels in %s" % `self.depotPath`
+
+        for output in l:
+            label = output["label"]
+            revisions = {}
+            newestChange = 0
+            if self.verbose:
+                print "Querying files for label %s" % label
+            for file in p4CmdList("files "
+                                  +  ' '.join (["%s...@%s" % (p, label)
+                                                for p in self.depotPaths])):
+                revisions[file["depotFile"]] = file["rev"]
+                change = int(file["change"])
+                if change > newestChange:
+                    newestChange = change
+
+            self.labels[newestChange] = [output, revisions]
+
+        if self.verbose:
+            print "Label changes: %s" % self.labels.keys()
+
+    def guessProjectName(self):
+        for p in self.depotPaths:
+            if p.endswith("/"):
+                p = p[:-1]
+            p = p[p.strip().rfind("/") + 1:]
+            if not p.endswith("/"):
+               p += "/"
+            return p
+
+    def getBranchMapping(self):
+        lostAndFoundBranches = set()
+
+        for info in p4CmdList("branches"):
+            details = p4Cmd("branch -o %s" % info["branch"])
+            viewIdx = 0
+            while details.has_key("View%s" % viewIdx):
+                paths = details["View%s" % viewIdx].split(" ")
+                viewIdx = viewIdx + 1
+                # require standard //depot/foo/... //depot/bar/... mapping
+                if len(paths) != 2 or not paths[0].endswith("/...") or not paths[1].endswith("/..."):
+                    continue
+                source = paths[0]
+                destination = paths[1]
+                ## HACK
+                if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
+                    source = source[len(self.depotPaths[0]):-4]
+                    destination = destination[len(self.depotPaths[0]):-4]
+
+                    if destination in self.knownBranches:
+                        if not self.silent:
+                            print "p4 branch %s defines a mapping from %s to %s" % (info["branch"], source, destination)
+                            print "but there exists another mapping from %s to %s already!" % (self.knownBranches[destination], destination)
+                        continue
+
+                    self.knownBranches[destination] = source
+
+                    lostAndFoundBranches.discard(destination)
+
+                    if source not in self.knownBranches:
+                        lostAndFoundBranches.add(source)
+
+
+        for branch in lostAndFoundBranches:
+            self.knownBranches[branch] = branch
+
+    def listExistingP4GitBranches(self):
+        # branches holds mapping from name to commit
+        branches = p4BranchesInGit(self.importIntoRemotes)
+        self.p4BranchesInGit = branches.keys()
+        for branch in branches.keys():
+            self.initialParents[self.refPrefix + branch] = branches[branch]
+
+    def createOrUpdateBranchesFromOrigin(self):
+        if not self.silent:
+            print ("Creating/updating branch(es) in %s based on origin branch(es)"
+                   % self.refPrefix)
+
+        originPrefix = "origin/p4/"
+
+        for line in read_pipe_lines("git rev-parse --symbolic --remotes"):
+            line = line.strip()
+            if (not line.startswith(originPrefix)) or line.endswith("HEAD"):
+                continue
+
+            headName = line[len(originPrefix):]
+            remoteHead = self.refPrefix + headName
+            originHead = line
+
+            original = extractSettingsGitLog(extractLogMessageFromGitCommit(originHead))
+            if (not original.has_key('depot-paths')
+                or not original.has_key('change')):
+                continue
+
+            update = False
+            if not gitBranchExists(remoteHead):
+                if self.verbose:
+                    print "creating %s" % remoteHead
+                update = True
+            else:
+                settings = extractSettingsGitLog(extractLogMessageFromGitCommit(remoteHead))
+                if settings.has_key('change') > 0:
+                    if settings['depot-paths'] == original['depot-paths']:
+                        originP4Change = int(original['change'])
+                        p4Change = int(settings['change'])
+                        if originP4Change > p4Change:
+                            print ("%s (%s) is newer than %s (%s). "
+                                   "Updating p4 branch from origin."
+                                   % (originHead, originP4Change,
+                                      remoteHead, p4Change))
+                            update = True
+                    else:
+                        print ("Ignoring: %s was imported from %s while "
+                               "%s was imported from %s"
+                               % (originHead, ','.join(original['depot-paths']),
+                                  remoteHead, ','.join(settings['depot-paths'])))
+
+            if update:
+                system("git update-ref %s %s" % (remoteHead, originHead))
+
+    def updateOptionDict(self, d):
+        option_keys = {}
+        if self.keepRepoPath:
+            option_keys['keepRepoPath'] = 1
+
+        d["options"] = ' '.join(sorted(option_keys.keys()))
+
+    def readOptions(self, d):
+        self.keepRepoPath = (d.has_key('options')
+                             and ('keepRepoPath' in d['options']))
+
+    def run(self, args):
+        self.depotPaths = []
+        self.changeRange = ""
+        self.initialParent = ""
+        self.previousDepotPaths = []
+
+        # map from branch depot path to parent branch
+        self.knownBranches = {}
+        self.initialParents = {}
+        self.hasOrigin = gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
+        if not self.syncWithOrigin:
+            self.hasOrigin = False
+
+        if self.importIntoRemotes:
+            self.refPrefix = "refs/remotes/p4/"
+        else:
+            self.refPrefix = "refs/heads/p4/"
+
+        if self.syncWithOrigin and self.hasOrigin:
+            if not self.silent:
+                print "Syncing with origin first by calling git fetch origin"
+            system("git fetch origin")
+
+        if len(self.branch) == 0:
+            self.branch = self.refPrefix + "master"
+            if gitBranchExists("refs/heads/p4") and self.importIntoRemotes:
+                system("git update-ref %s refs/heads/p4" % self.branch)
+                system("git branch -D p4");
+            # create it /after/ importing, when master exists
+            if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes:
+                system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
+
+        # TODO: should always look at previous commits,
+        # merge with previous imports, if possible.
+        if args == []:
+            if self.hasOrigin:
+                self.createOrUpdateBranchesFromOrigin()
+            self.listExistingP4GitBranches()
+
+            if len(self.p4BranchesInGit) > 1:
+                if not self.silent:
+                    print "Importing from/into multiple branches"
+                self.detectBranches = True
+
+            if self.verbose:
+                print "branches: %s" % self.p4BranchesInGit
+
+            p4Change = 0
+            for branch in self.p4BranchesInGit:
+                logMsg =  extractLogMessageFromGitCommit(self.refPrefix + branch)
+
+                settings = extractSettingsGitLog(logMsg)
+
+                self.readOptions(settings)
+                if (settings.has_key('depot-paths')
+                    and settings.has_key ('change')):
+                    change = int(settings['change']) + 1
+                    p4Change = max(p4Change, change)
+
+                    depotPaths = sorted(settings['depot-paths'])
+                    if self.previousDepotPaths == []:
+                        self.previousDepotPaths = depotPaths
+                    else:
+                        paths = []
+                        for (prev, cur) in zip(self.previousDepotPaths, depotPaths):
+                            for i in range(0, min(len(cur), len(prev))):
+                                if cur[i] <> prev[i]:
+                                    i = i - 1
+                                    break
+
+                            paths.append (cur[:i + 1])
+
+                        self.previousDepotPaths = paths
+
+            if p4Change > 0:
+                self.depotPaths = sorted(self.previousDepotPaths)
+                self.changeRange = "@%s,#head" % p4Change
+                if not self.detectBranches:
+                    self.initialParent = parseRevision(self.branch)
+                if not self.silent and not self.detectBranches:
+                    print "Performing incremental import into %s git branch" % self.branch
+
+        if not self.branch.startswith("refs/"):
+            self.branch = "refs/heads/" + self.branch
+
+        if len(args) == 0 and self.depotPaths:
+            if not self.silent:
+                print "Depot paths: %s" % ' '.join(self.depotPaths)
+        else:
+            if self.depotPaths and self.depotPaths != args:
+                print ("previous import used depot path %s and now %s was specified. "
+                       "This doesn't work!" % (' '.join (self.depotPaths),
+                                               ' '.join (args)))
+                sys.exit(1)
+
+            self.depotPaths = sorted(args)
+
+        self.revision = ""
+        self.users = {}
+
+        newPaths = []
+        for p in self.depotPaths:
+            if p.find("@") != -1:
+                atIdx = p.index("@")
+                self.changeRange = p[atIdx:]
+                if self.changeRange == "@all":
+                    self.changeRange = ""
+                elif ',' not in self.changeRange:
+                    self.revision = self.changeRange
+                    self.changeRange = ""
+                p = p[:atIdx]
+            elif p.find("#") != -1:
+                hashIdx = p.index("#")
+                self.revision = p[hashIdx:]
+                p = p[:hashIdx]
+            elif self.previousDepotPaths == []:
+                self.revision = "#head"
+
+            p = re.sub ("\.\.\.$", "", p)
+            if not p.endswith("/"):
+                p += "/"
+
+            newPaths.append(p)
+
+        self.depotPaths = newPaths
+
+
+        self.loadUserMapFromCache()
+        self.labels = {}
+        if self.detectLabels:
+            self.getLabels();
+
+        if self.detectBranches:
+            ## FIXME - what's a P4 projectName ?
+            self.projectName = self.guessProjectName()
+
+            if not self.hasOrigin:
+                self.getBranchMapping();
+            if self.verbose:
+                print "p4-git branches: %s" % self.p4BranchesInGit
+                print "initial parents: %s" % self.initialParents
+            for b in self.p4BranchesInGit:
+                if b != "master":
+
+                    ## FIXME
+                    b = b[len(self.projectName):]
+                self.createdBranches.add(b)
+
+        self.tz = "%+03d%02d" % (- time.timezone / 3600, ((- time.timezone % 3600) / 60))
+
+        importProcess = subprocess.Popen(["git", "fast-import"],
+                                         stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                                         stderr=subprocess.PIPE);
+        self.gitOutput = importProcess.stdout
+        self.gitStream = importProcess.stdin
+        self.gitError = importProcess.stderr
+
+        if self.revision:
+            print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), self.revision, self.branch)
+
+            details = { "user" : "git perforce import user", "time" : int(time.time()) }
+            details["desc"] = ("Initial import of %s from the state at revision %s"
+                               % (' '.join(self.depotPaths), self.revision))
+            details["change"] = self.revision
+            newestRevision = 0
+
+            fileCnt = 0
+            for info in p4CmdList("files "
+                                  +  ' '.join(["%s...%s"
+                                               % (p, self.revision)
+                                               for p in self.depotPaths])):
+
+                if info['code'] == 'error':
+                    sys.stderr.write("p4 returned an error: %s\n"
+                                     % info['data'])
+                    sys.exit(1)
+
+
+                change = int(info["change"])
+                if change > newestRevision:
+                    newestRevision = change
+
+                if info["action"] == "delete":
+                    # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
+                    #fileCnt = fileCnt + 1
+                    continue
+
+                for prop in ["depotFile", "rev", "action", "type" ]:
+                    details["%s%s" % (prop, fileCnt)] = info[prop]
+
+                fileCnt = fileCnt + 1
+
+            details["change"] = newestRevision
+            self.updateOptionDict(details)
+            try:
+                self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
+            except IOError:
+                print "IO error with git fast-import. Is your git version recent enough?"
+                print self.gitError.read()
+
+        else:
+            changes = []
+
+            if len(self.changesFile) > 0:
+                output = open(self.changesFile).readlines()
+                changeSet = Set()
+                for line in output:
+                    changeSet.add(int(line))
+
+                for change in changeSet:
+                    changes.append(change)
+
+                changes.sort()
+            else:
+                if self.verbose:
+                    print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
+                                                              self.changeRange)
+                assert self.depotPaths
+                output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, self.changeRange)
+                                                                    for p in self.depotPaths]))
+
+                for line in output:
+                    changeNum = line.split(" ")[1]
+                    changes.append(int(changeNum))
+
+                changes.sort()
+
+                if len(self.maxChanges) > 0:
+                    changes = changes[:min(int(self.maxChanges), len(changes))]
+
+            if len(changes) == 0:
+                if not self.silent:
+                    print "No changes to import!"
+                return True
+
+            if not self.silent and not self.detectBranches:
+                print "Import destination: %s" % self.branch
+
+            self.updatedBranches = set()
+
+            cnt = 1
+            for change in changes:
+                description = p4Cmd("describe %s" % change)
+                self.updateOptionDict(description)
+
+                if not self.silent:
+                    sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
+                    sys.stdout.flush()
+                cnt = cnt + 1
+
+                try:
+                    if self.detectBranches:
+                        branches = self.splitFilesIntoBranches(description)
+                        for branch in branches.keys():
+                            ## HACK  --hwn
+                            branchPrefix = self.depotPaths[0] + branch + "/"
+
+                            parent = ""
+
+                            filesForCommit = branches[branch]
+
+                            if self.verbose:
+                                print "branch is %s" % branch
+
+                            self.updatedBranches.add(branch)
+
+                            if branch not in self.createdBranches:
+                                self.createdBranches.add(branch)
+                                parent = self.knownBranches[branch]
+                                if parent == branch:
+                                    parent = ""
+                                elif self.verbose:
+                                    print "parent determined through known branches: %s" % parent
+
+                            # main branch? use master
+                            if branch == "main":
+                                branch = "master"
+                            else:
+
+                                ## FIXME
+                                branch = self.projectName + branch
+
+                            if parent == "main":
+                                parent = "master"
+                            elif len(parent) > 0:
+                                ## FIXME
+                                parent = self.projectName + parent
+
+                            branch = self.refPrefix + branch
+                            if len(parent) > 0:
+                                parent = self.refPrefix + parent
+
+                            if self.verbose:
+                                print "looking for initial parent for %s; current parent is %s" % (branch, parent)
+
+                            if len(parent) == 0 and branch in self.initialParents:
+                                parent = self.initialParents[branch]
+                                del self.initialParents[branch]
+
+                            self.commit(description, filesForCommit, branch, [branchPrefix], parent)
+                    else:
+                        files = self.extractFilesFromCommit(description)
+                        self.commit(description, files, self.branch, self.depotPaths,
+                                    self.initialParent)
+                        self.initialParent = ""
+                except IOError:
+                    print self.gitError.read()
+                    sys.exit(1)
+
+            if not self.silent:
+                print ""
+                if len(self.updatedBranches) > 0:
+                    sys.stdout.write("Updated branches: ")
+                    for b in self.updatedBranches:
+                        sys.stdout.write("%s " % b)
+                    sys.stdout.write("\n")
+
+
+        self.gitStream.close()
+        if importProcess.wait() != 0:
+            die("fast-import failed: %s" % self.gitError.read())
+        self.gitOutput.close()
+        self.gitError.close()
+
+        return True
+
+class P4Rebase(Command):
+    def __init__(self):
+        Command.__init__(self)
+        self.options = [ ]
+        self.description = ("Fetches the latest revision from perforce and "
+                            + "rebases the current work (branch) against it")
+        self.verbose = False
+
+    def run(self, args):
+        sync = P4Sync()
+        sync.run([])
+
+        [upstream, settings] = findUpstreamBranchPoint()
+        if len(upstream) == 0:
+            die("Cannot find upstream branchpoint for rebase")
+
+        # the branchpoint may be p4/foo~3, so strip off the parent
+        upstream = re.sub("~[0-9]+$", "", upstream)
+
+        print "Rebasing the current branch onto %s" % upstream
+        oldHead = read_pipe("git rev-parse HEAD").strip()
+        system("git rebase %s" % upstream)
+        system("git diff-tree --stat --summary -M %s HEAD" % oldHead)
+        return True
+
+class P4Clone(P4Sync):
+    def __init__(self):
+        P4Sync.__init__(self)
+        self.description = "Creates a new git repository and imports from Perforce into it"
+        self.usage = "usage: %prog [options] //depot/path[@revRange]"
+        self.options.append(
+            optparse.make_option("--destination", dest="cloneDestination",
+                                 action='store', default=None,
+                                 help="where to leave result of the clone"))
+        self.cloneDestination = None
+        self.needsGit = False
+
+    def defaultDestination(self, args):
+        ## TODO: use common prefix of args?
+        depotPath = args[0]
+        depotDir = re.sub("(@[^@]*)$", "", depotPath)
+        depotDir = re.sub("(#[^#]*)$", "", depotDir)
+        depotDir = re.sub(r"\.\.\.$,", "", depotDir)
+        depotDir = re.sub(r"/$", "", depotDir)
+        return os.path.split(depotDir)[1]
+
+    def run(self, args):
+        if len(args) < 1:
+            return False
+
+        if self.keepRepoPath and not self.cloneDestination:
+            sys.stderr.write("Must specify destination for --keep-path\n")
+            sys.exit(1)
+
+        depotPaths = args
+
+        if not self.cloneDestination and len(depotPaths) > 1:
+            self.cloneDestination = depotPaths[-1]
+            depotPaths = depotPaths[:-1]
+
+        for p in depotPaths:
+            if not p.startswith("//"):
+                return False
+
+        if not self.cloneDestination:
+            self.cloneDestination = self.defaultDestination(args)
+
+        print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
+        if not os.path.exists(self.cloneDestination):
+            os.makedirs(self.cloneDestination)
+        os.chdir(self.cloneDestination)
+        system("git init")
+        self.gitdir = os.getcwd() + "/.git"
+        if not P4Sync.run(self, depotPaths):
+            return False
+        if self.branch != "master":
+            if gitBranchExists("refs/remotes/p4/master"):
+                system("git branch master refs/remotes/p4/master")
+                system("git checkout -f")
+            else:
+                print "Could not detect main branch. No checkout/master branch created."
+
+        return True
+
+class P4Branches(Command):
+    def __init__(self):
+        Command.__init__(self)
+        self.options = [ ]
+        self.description = ("Shows the git branches that hold imports and their "
+                            + "corresponding perforce depot paths")
+        self.verbose = False
+
+    def run(self, args):
+        cmdline = "git rev-parse --symbolic "
+        cmdline += " --remotes"
+
+        for line in read_pipe_lines(cmdline):
+            line = line.strip()
+
+            if not line.startswith('p4/') or line == "p4/HEAD":
+                continue
+            branch = line
+
+            log = extractLogMessageFromGitCommit("refs/remotes/%s" % branch)
+            settings = extractSettingsGitLog(log)
+
+            print "%s <= %s (%s)" % (branch, ",".join(settings["depot-paths"]), settings["change"])
+        return True
+
+class HelpFormatter(optparse.IndentedHelpFormatter):
+    def __init__(self):
+        optparse.IndentedHelpFormatter.__init__(self)
+
+    def format_description(self, description):
+        if description:
+            return description + "\n"
+        else:
+            return ""
+
+def printUsage(commands):
+    print "usage: %s <command> [options]" % sys.argv[0]
+    print ""
+    print "valid commands: %s" % ", ".join(commands)
+    print ""
+    print "Try %s <command> --help for command specific help." % sys.argv[0]
+    print ""
+
+commands = {
+    "debug" : P4Debug,
+    "submit" : P4Submit,
+    "sync" : P4Sync,
+    "rebase" : P4Rebase,
+    "clone" : P4Clone,
+    "rollback" : P4RollBack,
+    "branches" : P4Branches
+}
+
+
+def main():
+    if len(sys.argv[1:]) == 0:
+        printUsage(commands.keys())
+        sys.exit(2)
+
+    cmd = ""
+    cmdName = sys.argv[1]
+    try:
+        klass = commands[cmdName]
+        cmd = klass()
+    except KeyError:
+        print "unknown command %s" % cmdName
+        print ""
+        printUsage(commands.keys())
+        sys.exit(2)
+
+    options = cmd.options
+    cmd.gitdir = os.environ.get("GIT_DIR", None)
+
+    args = sys.argv[2:]
+
+    if len(options) > 0:
+        options.append(optparse.make_option("--git-dir", dest="gitdir"))
+
+        parser = optparse.OptionParser(cmd.usage.replace("%prog", "%prog " + cmdName),
+                                       options,
+                                       description = cmd.description,
+                                       formatter = HelpFormatter())
+
+        (cmd, args) = parser.parse_args(sys.argv[2:], cmd);
+    global verbose
+    verbose = cmd.verbose
+    if cmd.needsGit:
+        if cmd.gitdir == None:
+            cmd.gitdir = os.path.abspath(".git")
+            if not isValidGitDir(cmd.gitdir):
+                cmd.gitdir = read_pipe("git rev-parse --git-dir").strip()
+                if os.path.exists(cmd.gitdir):
+                    cdup = read_pipe("git rev-parse --show-cdup").strip()
+                    if len(cdup) > 0:
+                        os.chdir(cdup);
+
+        if not isValidGitDir(cmd.gitdir):
+            if isValidGitDir(cmd.gitdir + "/.git"):
+                cmd.gitdir += "/.git"
+            else:
+                die("fatal: cannot locate git repository at %s" % cmd.gitdir)
+
+        os.environ["GIT_DIR"] = cmd.gitdir
+
+    if not cmd.run(args):
+        parser.print_help()
+
+
+if __name__ == '__main__':
+    main()
diff --git a/contrib/fast-import/git-p4.bat b/contrib/fast-import/git-p4.bat
new file mode 100644 (file)
index 0000000..9f97e88
--- /dev/null
@@ -0,0 +1 @@
+@python "%~d0%~p0git-p4" %*
diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
new file mode 100644 (file)
index 0000000..b16a838
--- /dev/null
@@ -0,0 +1,159 @@
+git-p4 - Perforce <-> Git converter using git-fast-import
+
+Usage
+=====
+
+git-p4 supports two main modes: Importing from Perforce to a Git repository is
+done using "git-p4 sync" or "git-p4 rebase". Submitting changes from Git back
+to Perforce is done using "git-p4 submit".
+
+Importing
+=========
+
+You can simply start with
+
+  git-p4 clone //depot/path/project
+
+or
+
+  git-p4 clone //depot/path/project myproject
+
+This will create an empty git repository in a subdirectory called "project" (or
+"myproject" with the second command), import the head revision from the
+specified perforce path into a git "p4" branch (remotes/p4 actually), create a
+master branch off it and check it out. If you want the entire history (not just
+the head revision) then you can simply append a "@all" to the depot path:
+
+  git-p4 clone //depot/project/main@all myproject
+
+
+
+If you want more control you can also use the git-p4 sync command directly:
+
+  mkdir repo-git
+  cd repo-git
+  git init
+  git-p4 sync //path/in/your/perforce/depot
+
+This will import the current head revision of the specified depot path into a
+"remotes/p4/master" branch of your git repository. You can use the
+--branch=mybranch option to use a different branch.
+
+If you want to import the entire history of a given depot path just use
+
+  git-p4 sync //path/in/depot@all
+
+To achieve optimal compression you may want to run 'git repack -a -d -f' after
+a big import. This may take a while.
+
+Support for Perforce integrations is still work in progress. Don't bother
+trying it unless you want to hack on it :)
+
+Incremental Imports
+===================
+
+After an initial import you can easily synchronize your git repository with
+newer changes from the Perforce depot by just calling
+
+  git-p4 sync
+
+in your git repository. By default the "remotes/p4/master" branch is updated.
+
+It is recommended to run 'git repack -a -d -f' from time to time when using
+incremental imports to optimally combine the individual git packs that each
+incremental import creates through the use of git-fast-import.
+
+
+A useful setup may be that you have a periodically updated git repository
+somewhere that contains a complete import of a Perforce project. That git
+repository can be used to clone the working repository from and one would
+import from Perforce directly after cloning using git-p4. If the connection to
+the Perforce server is slow and the working repository hasn't been synced for a
+while it may be desirable to fetch changes from the origin git repository using
+the efficient git protocol. git-p4 supports this setup by calling "git fetch origin"
+by default if there is an origin branch. You can disable this using
+
+  git config git-p4.syncFromOrigin false
+
+Updating
+========
+
+A common working pattern is to fetch the latest changes from the Perforce depot
+and merge them with local uncommitted changes. The recommended way is to use
+git's rebase mechanism to preserve linear history. git-p4 provides a convenient
+
+  git-p4 rebase
+
+command that calls git-p4 sync followed by git rebase to rebase the current
+working branch.
+
+Submitting
+==========
+
+git-p4 has support for submitting changes from a git repository back to the
+Perforce depot. This requires a Perforce checkout separate to your git
+repository. To submit all changes that are in the current git branch but not in
+the "p4" branch (or "origin" if "p4" doesn't exist) simply call
+
+    git-p4 submit
+
+in your git repository. If you want to submit changes in a specific branch that
+is not your current git branch you can also pass that as an argument:
+
+    git-p4 submit mytopicbranch
+
+You can override the reference branch with the --origin=mysourcebranch option.
+
+If a submit fails you may have to "p4 resolve" and submit manually. You can
+continue importing the remaining changes with
+
+  git-p4 submit --continue
+
+After submitting you should sync your perforce import branch ("p4" or "origin")
+from Perforce using git-p4's sync command.
+
+If you have changes in your working directory that you haven't committed into
+git yet but that you want to commit to Perforce directly ("quick fixes") then
+you do not have to go through the intermediate step of creating a git commit
+first but you can just call
+
+  git-p4 submit --direct
+
+
+Example
+=======
+
+# Clone a repository
+  git-p4 clone //depot/path/project
+# Enter the newly cloned directory
+  cd project
+# Do some work...
+  vi foo.h
+# ... and commit locally to gi
+  git commit foo.h
+# In the meantime somebody submitted changes to the Perforce depot. Rebase your latest
+# changes against the latest changes in Perforce:
+  git-p4 rebase
+# Submit your locally committed changes back to Perforce
+  git-p4 submit
+# ... and synchronize with Perforce
+  git-p4 rebase
+
+
+Implementation Details...
+=========================
+
+* Changesets from Perforce are imported using git fast-import.
+* The import does not require anything from the Perforce client view as it just uses
+  "p4 print //depot/path/file#revision" to get the actual file contents.
+* Every imported changeset has a special [git-p4...] line at the
+  end of the log message that gives information about the corresponding
+  Perforce change number and is also used by git-p4 itself to find out
+  where to continue importing when doing incremental imports.
+  Basically when syncing it extracts the perforce change number of the
+  latest commit in the "p4" branch and uses "p4 changes //depot/path/...@changenum,#head"
+  to find out which changes need to be imported.
+* git-p4 submit uses "git rev-list" to pick the commits between the "p4" branch
+  and the current branch.
+  The commits themselves are applied using git diff/format-patch ... | git apply
+
index 2d80e2bad2e6f322d7ff7e9f03a6897a11f74231..593176662050f0c84897759ab7d80f31aabfae53 100755 (executable)
@@ -259,10 +259,13 @@ class CellRendererGraph(gtk.GenericCellRenderer):
                                self.set_colour(ctx, colour, 0.0, 0.5)
                        ctx.show_text(name)
 
-class Commit:
+class Commit(object):
        """ This represent a commit object obtained after parsing the git-rev-list
        output """
 
+       __slots__ = ['children_sha1', 'message', 'author', 'date', 'committer',
+                                'commit_date', 'commit_sha1', 'parent_sha1']
+
        children_sha1 = {}
 
        def __init__(self, commit_lines):
@@ -339,7 +342,7 @@ class Commit:
                fp.close()
                return diff
 
-class AnnotateWindow:
+class AnnotateWindow(object):
        """Annotate window.
        This object represents and manages a single window containing the
        annotate information of the file
@@ -349,6 +352,7 @@ class AnnotateWindow:
                self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
                self.window.set_border_width(0)
                self.window.set_title("Git repository browser annotation window")
+               self.prev_read = ""
 
                # Use two thirds of the screen by default
                screen = self.window.get_screen()
@@ -398,7 +402,10 @@ class AnnotateWindow:
        def data_ready(self, source, condition):
                while (1):
                        try :
-                               buffer = source.read(8192)
+                               # A simple readline doesn't work
+                               # a readline bug ??
+                               buffer = source.read(100)
+
                        except:
                                # resource temporary not available
                                return True
@@ -408,6 +415,19 @@ class AnnotateWindow:
                                source.close()
                                return False
 
+                       if (self.prev_read != ""):
+                               buffer = self.prev_read + buffer
+                               self.prev_read = ""
+
+                       if (buffer[len(buffer) -1] != '\n'):
+                               try:
+                                       newline_index = buffer.rindex("\n")
+                               except ValueError:
+                                       newline_index = 0
+
+                               self.prev_read = buffer[newline_index:(len(buffer))]
+                               buffer = buffer[0:newline_index]
+
                        for buff in buffer.split("\n"):
                                annotate_line = re.compile('^([0-9a-f]{40}) (.+) (.+) (.+)$')
                                m = annotate_line.match(buff)
@@ -513,13 +533,13 @@ class AnnotateWindow:
 
                self.add_file_data(filename, commit_sha1, line_num)
 
-               fp = os.popen("git blame --incremental -- " + filename + " " + commit_sha1)
+               fp = os.popen("git blame --incremental -C -C -- " + filename + " " + commit_sha1)
                flags = fcntl.fcntl(fp.fileno(), fcntl.F_GETFL)
                fcntl.fcntl(fp.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
                self.io_watch_tag = gobject.io_add_watch(fp, gobject.IO_IN, self.data_ready)
 
 
-class DiffWindow:
+class DiffWindow(object):
        """Diff window.
        This object represents and manages a single window containing the
        differences between two revisions on a branch.
@@ -674,7 +694,7 @@ class DiffWindow:
                        fp.close()
                dialog.destroy()
 
-class GitView:
+class GitView(object):
        """ This is the main class
        """
        version = "0.9"
@@ -1277,5 +1297,3 @@ if __name__ == "__main__":
 
        view = GitView( without_diff != 1)
        view.run(sys.argv[without_diff:])
-
-
index d1bef9125bca0997e0e714b3f89e9c2df307d598..c589a39a0c81818150575c74866a57619e1adf2a 100644 (file)
@@ -199,7 +199,7 @@ generate_email_footer()
 
 
        hooks/post-receive
-       -- 
+       --
        $projectdesc
        EOF
 }
index 5ee1835c801fc2fea8284aa253c966bd65be0549..068fa3708331bbb7d451040715c8bba9623f8c7e 100644 (file)
@@ -102,6 +102,8 @@ my ($this_user) = getpwuid $<; # REAL_USER_ID
 my $repository_name;
 my %user_committer;
 my @allow_rules;
+my @path_rules;
+my %diff_cache;
 
 sub deny ($) {
        print STDERR "-Deny-    $_[0]\n" if $debug;
@@ -118,22 +120,36 @@ sub info ($) {
        print STDERR "-Info-    $_[0]\n" if $debug;
 }
 
-sub parse_config ($$) {
-       my ($data, $fn) = @_;
-       info "Loading $fn";
-       open(I,'-|','git',"--git-dir=$acl_git",'cat-file','blob',$fn);
+sub git_value (@) {
+       open(T,'-|','git',@_); local $_ = <T>; chop; close T; $_;
+}
+
+sub match_string ($$) {
+       my ($acl_n, $ref) = @_;
+          ($acl_n eq $ref)
+       || ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
+       || ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:);
+}
+
+sub parse_config ($$$$) {
+       my $data = shift;
+       local $ENV{GIT_DIR} = shift;
+       my $br = shift;
+       my $fn = shift;
+       info "Loading $br:$fn";
+       open(I,'-|','git','cat-file','blob',"$br:$fn");
        my $section = '';
        while (<I>) {
                chomp;
                if (/^\s*$/ || /^\s*#/) {
                } elsif (/^\[([a-z]+)\]$/i) {
-                       $section = $1;
+                       $section = lc $1;
                } elsif (/^\[([a-z]+)\s+"(.*)"\]$/i) {
-                       $section = "$1.$2";
+                       $section = join('.',lc $1,$2);
                } elsif (/^\s*([a-z][a-z0-9]+)\s*=\s*(.*?)\s*$/i) {
-                       push @{$data->{"$section.$1"}}, $2;
+                       push @{$data->{join('.',$section,lc $1)}}, $2;
                } else {
-                       deny "bad config file line $. in $fn";
+                       deny "bad config file line $. in $br:$fn";
                }
        }
        close I;
@@ -202,9 +218,40 @@ sub check_committers (@) {
        }
 }
 
-sub git_value (@) {
-       open(T,'-|','git',@_); local $_ = <T>; chop; close T;
-       $_;
+sub load_diff ($) {
+       my $base = shift;
+       my $d = $diff_cache{$base};
+       unless ($d) {
+               local $/ = "\0";
+               my %this_diff;
+               if ($base =~ /^0{40}$/) {
+                       open(T,'-|','git','ls-tree',
+                               '-r','--name-only','-z',
+                               $new) or return undef;
+                       while (<T>) {
+                               chop;
+                               $this_diff{$_} = 'A';
+                       }
+                       close T or return undef;
+               } else {
+                       open(T,'-|','git','diff-tree',
+                               '-r','--name-status','-z',
+                               $base,$new) or return undef;
+                       while (<T>) {
+                               my $op = $_;
+                               chop $op;
+
+                               my $path = <T>;
+                               chop $path;
+
+                               $this_diff{$path} = $op;
+                       }
+                       close T or return undef;
+               }
+               $d = \%this_diff;
+               $diff_cache{$base} = $d;
+       }
+       return $d;
 }
 
 deny "No GIT_DIR inherited from caller" unless $git_dir;
@@ -231,14 +278,52 @@ $op = 'U' if ($op eq 'R'
        && $ref =~ m,^heads/,
        && $old eq git_value('merge-base',$old,$new));
 
-# Load the user's ACL file.
+# Load the user's ACL file. Expand groups (user.memberof) one level.
 {
        my %data = ('user.committer' => []);
-       parse_config(\%data, "$acl_branch:users/$this_user.acl");
+       parse_config(\%data,$acl_git,$acl_branch,"external/$repository_name.acl");
+
+       %data = (
+               'user.committer' => $data{'user.committer'},
+               'user.memberof' => [],
+       );
+       parse_config(\%data,$acl_git,$acl_branch,"users/$this_user.acl");
+
        %user_committer = map {$_ => $_} @{$data{'user.committer'}};
-       my $rules = $data{"repository.$repository_name.allow"} || [];
+       my $rule_key = "repository.$repository_name.allow";
+       my $rules = $data{$rule_key} || [];
+
+       foreach my $group (@{$data{'user.memberof'}}) {
+               my %g;
+               parse_config(\%g,$acl_git,$acl_branch,"groups/$group.acl");
+               my $group_rules = $g{$rule_key};
+               push @$rules, @$group_rules if $group_rules;
+       }
+
+RULE:
        foreach (@$rules) {
-               if (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
+               while (/\${user\.([a-z][a-zA-Z0-9]+)}/) {
+                       my $k = lc $1;
+                       my $v = $data{"user.$k"};
+                       next RULE unless defined $v;
+                       next RULE if @$v != 1;
+                       next RULE unless defined $v->[0];
+                       s/\${user\.$k}/$v->[0]/g;
+               }
+
+               if (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)\s+diff\s+([^\s]+)$/) {
+                       my ($ops, $pth, $ref, $bst) = ($1, $2, $3, $4);
+                       $ops =~ s/ //g;
+                       $pth =~ s/\\\\/\\/g;
+                       $ref =~ s/\\\\/\\/g;
+                       push @path_rules, [$ops, $pth, $ref, $bst];
+               } elsif (/^([AMD ]+)\s+of\s+([^\s]+)\s+for\s+([^\s]+)$/) {
+                       my ($ops, $pth, $ref) = ($1, $2, $3);
+                       $ops =~ s/ //g;
+                       $pth =~ s/\\\\/\\/g;
+                       $ref =~ s/\\\\/\\/g;
+                       push @path_rules, [$ops, $pth, $ref, $old];
+               } elsif (/^([CDRU ]+)\s+for\s+([^\s]+)$/) {
                        my $ops = $1;
                        my $ref = $2;
                        $ops =~ s/ //g;
@@ -272,13 +357,65 @@ foreach my $acl_entry (@allow_rules) {
        next unless $acl_ops =~ /^[CDRU]+$/; # Uhh.... shouldn't happen.
        next unless $acl_n;
        next unless $op =~ /^[$acl_ops]$/;
+       next unless match_string $acl_n, $ref;
+
+       # Don't test path rules on branch deletes.
+       #
+       grant "Allowed by: $acl_ops for $acl_n" if $op eq 'D';
+
+       # Aggregate matching path rules; allow if there aren't
+       # any matching this ref.
+       #
+       my %pr;
+       foreach my $p_entry (@path_rules) {
+               my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
+               next unless $p_ref;
+               push @{$pr{$p_bst}}, $p_entry if match_string $p_ref, $ref;
+       }
+       grant "Allowed by: $acl_ops for $acl_n" unless %pr;
 
-       grant "Allowed by: $acl_ops for $acl_n"
-       if (
-          ($acl_n eq $ref)
-       || ($acl_n =~ m,/$, && substr($ref,0,length $acl_n) eq $acl_n)
-       || ($acl_n =~ m,^\^, && $ref =~ m:$acl_n:)
-       );
+       # Allow only if all changes against a single base are
+       # allowed by file path rules.
+       #
+       my @bad;
+       foreach my $p_bst (keys %pr) {
+               my $diff_ref = load_diff $p_bst;
+               deny "Cannot difference trees." unless ref $diff_ref;
+
+               my %fd = %$diff_ref;
+               foreach my $p_entry (@{$pr{$p_bst}}) {
+                       my ($p_ops, $p_n, $p_ref, $p_bst) = @$p_entry;
+                       next unless $p_ops =~ /^[AMD]+$/;
+                       next unless $p_n;
+
+                       foreach my $f_n (keys %fd) {
+                               my $f_op = $fd{$f_n};
+                               next unless $f_op;
+                               next unless $f_op =~ /^[$p_ops]$/;
+                               delete $fd{$f_n} if match_string $p_n, $f_n;
+                       }
+                       last unless %fd;
+               }
+
+               if (%fd) {
+                       push @bad, [$p_bst, \%fd];
+               } else {
+                       # All changes relative to $p_bst were allowed.
+                       #
+                       grant "Allowed by: $acl_ops for $acl_n diff $p_bst";
+               }
+       }
+
+       foreach my $bad_ref (@bad) {
+               my ($p_bst, $fd) = @$bad_ref;
+               print STDERR "\n";
+               print STDERR "Not allowed to make the following changes:\n";
+               print STDERR "(base: $p_bst)\n";
+               foreach my $f_n (sort keys %$fd) {
+                       print STDERR "  $fd->{$f_n} $f_n\n";
+               }
+       }
+       deny "You are not permitted to $op $ref";
 }
 close A;
 deny "You are not permitted to $op $ref";
diff --git a/contrib/p4import/README b/contrib/p4import/README
new file mode 100644 (file)
index 0000000..b9892b6
--- /dev/null
@@ -0,0 +1 @@
+Please see contrib/fast-import/git-p4 for a better Perforce importer.
diff --git a/contrib/p4import/git-p4import.py b/contrib/p4import/git-p4import.py
new file mode 100644 (file)
index 0000000..0f3d97b
--- /dev/null
@@ -0,0 +1,360 @@
+#!/usr/bin/python
+#
+# This tool is copyright (c) 2006, Sean Estabrooks.
+# It is released under the Gnu Public License, version 2.
+#
+# Import Perforce branches into Git repositories.
+# Checking out the files is done by calling the standard p4
+# client which you must have properly configured yourself
+#
+
+import marshal
+import os
+import sys
+import time
+import getopt
+
+from signal import signal, \
+   SIGPIPE, SIGINT, SIG_DFL, \
+   default_int_handler
+
+signal(SIGPIPE, SIG_DFL)
+s = signal(SIGINT, SIG_DFL)
+if s != default_int_handler:
+   signal(SIGINT, s)
+
+def die(msg, *args):
+    for a in args:
+        msg = "%s %s" % (msg, a)
+    print "git-p4import fatal error:", msg
+    sys.exit(1)
+
+def usage():
+    print "USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]"
+    sys.exit(1)
+
+verbosity = 1
+logfile = "/dev/null"
+ignore_warnings = False
+stitch = 0
+tagall = True
+
+def report(level, msg, *args):
+    global verbosity
+    global logfile
+    for a in args:
+        msg = "%s %s" % (msg, a)
+    fd = open(logfile, "a")
+    fd.writelines(msg)
+    fd.close()
+    if level <= verbosity:
+        print msg
+
+class p4_command:
+    def __init__(self, _repopath):
+        try:
+            global logfile
+            self.userlist = {}
+            if _repopath[-1] == '/':
+                self.repopath = _repopath[:-1]
+            else:
+                self.repopath = _repopath
+            if self.repopath[-4:] != "/...":
+                self.repopath= "%s/..." % self.repopath
+            f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
+            a = f.readlines()
+            if f.close():
+                raise
+        except:
+                die("Could not find the \"p4\" command")
+
+    def p4(self, cmd, *args):
+        global logfile
+        cmd = "%s %s" % (cmd, ' '.join(args))
+        report(2, "P4:", cmd)
+        f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
+        list = []
+        while 1:
+           try:
+                list.append(marshal.load(f))
+           except EOFError:
+                break
+        self.ret = f.close()
+        return list
+
+    def sync(self, id, force=False, trick=False, test=False):
+        if force:
+            ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
+        elif trick:
+            ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
+        elif test:
+            ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
+        else:
+            ret = self.p4("sync    %s@%s"%(self.repopath, id))[0]
+        if ret['code'] == "error":
+             data = ret['data'].upper()
+             if data.find('VIEW') > 0:
+                 die("Perforce reports %s is not in client view"% self.repopath)
+             elif data.find('UP-TO-DATE') < 0:
+                 die("Could not sync files from perforce", self.repopath)
+
+    def changes(self, since=0):
+        try:
+            list = []
+            for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
+                list.append(rec['change'])
+            list.reverse()
+            return list
+        except:
+            return []
+
+    def authors(self, filename):
+        f=open(filename)
+        for l in f.readlines():
+            self.userlist[l[:l.find('=')].rstrip()] = \
+                    (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
+        f.close()
+        for f,e in self.userlist.items():
+                report(2, f, ":", e[0], "  <", e[1], ">")
+
+    def _get_user(self, id):
+        if not self.userlist.has_key(id):
+            try:
+                user = self.p4("users", id)[0]
+                self.userlist[id] = (user['FullName'], user['Email'])
+            except:
+                self.userlist[id] = (id, "")
+        return self.userlist[id]
+
+    def _format_date(self, ticks):
+        symbol='+'
+        name = time.tzname[0]
+        offset = time.timezone
+        if ticks[8]:
+            name = time.tzname[1]
+            offset = time.altzone
+        if offset < 0:
+            offset *= -1
+            symbol = '-'
+        localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
+        tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
+        return "%s %s" % (tickso, localo)
+
+    def where(self):
+        try:
+            return self.p4("where %s" % self.repopath)[-1]['path']
+        except:
+            return ""
+
+    def describe(self, num):
+        desc = self.p4("describe -s", num)[0]
+        self.msg = desc['desc']
+        self.author, self.email = self._get_user(desc['user'])
+        self.date = self._format_date(time.localtime(long(desc['time'])))
+        return self
+
+class git_command:
+    def __init__(self):
+        try:
+            self.version = self.git("--version")[0][12:].rstrip()
+        except:
+            die("Could not find the \"git\" command")
+        try:
+            self.gitdir = self.get_single("rev-parse --git-dir")
+            report(2, "gdir:", self.gitdir)
+        except:
+            die("Not a git repository... did you forget to \"git init\" ?")
+        try:
+            self.cdup = self.get_single("rev-parse --show-cdup")
+            if self.cdup != "":
+                os.chdir(self.cdup)
+            self.topdir = os.getcwd()
+            report(2, "topdir:", self.topdir)
+        except:
+            die("Could not find top git directory")
+
+    def git(self, cmd):
+        global logfile
+        report(2, "GIT:", cmd)
+        f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
+        r=f.readlines()
+        self.ret = f.close()
+        return r
+
+    def get_single(self, cmd):
+        return self.git(cmd)[0].rstrip()
+
+    def current_branch(self):
+        try:
+            testit = self.git("rev-parse --verify HEAD")[0]
+            return self.git("symbolic-ref HEAD")[0][11:].rstrip()
+        except:
+            return None
+
+    def get_config(self, variable):
+        try:
+            return self.git("config --get %s" % variable)[0].rstrip()
+        except:
+            return None
+
+    def set_config(self, variable, value):
+        try:
+            self.git("config %s %s"%(variable, value) )
+        except:
+            die("Could not set %s to " % variable, value)
+
+    def make_tag(self, name, head):
+        self.git("tag -f %s %s"%(name,head))
+
+    def top_change(self, branch):
+        try:
+            a=self.get_single("name-rev --tags refs/heads/%s" % branch)
+            loc = a.find(' tags/') + 6
+            if a[loc:loc+3] != "p4/":
+                raise
+            return int(a[loc+3:][:-2])
+        except:
+            return 0
+
+    def update_index(self):
+        self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
+
+    def checkout(self, branch):
+        self.git("checkout %s" % branch)
+
+    def repoint_head(self, branch):
+        self.git("symbolic-ref HEAD refs/heads/%s" % branch)
+
+    def remove_files(self):
+        self.git("ls-files | xargs rm")
+
+    def clean_directories(self):
+        self.git("clean -d")
+
+    def fresh_branch(self, branch):
+        report(1, "Creating new branch", branch)
+        self.git("ls-files | xargs rm")
+        os.remove(".git/index")
+        self.repoint_head(branch)
+        self.git("clean -d")
+
+    def basedir(self):
+        return self.topdir
+
+    def commit(self, author, email, date, msg, id):
+        self.update_index()
+        fd=open(".msg", "w")
+        fd.writelines(msg)
+        fd.close()
+        try:
+                current = self.get_single("rev-parse --verify HEAD")
+                head = "-p HEAD"
+        except:
+                current = ""
+                head = ""
+        tree = self.get_single("write-tree")
+        for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
+            os.environ['GIT_AUTHOR_%s'%r] = l
+            os.environ['GIT_COMMITTER_%s'%r] = l
+        commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
+        os.remove(".msg")
+        self.make_tag("p4/%s"%id, commit)
+        self.git("update-ref HEAD %s %s" % (commit, current) )
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
+            ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
+except getopt.GetoptError:
+    usage()
+
+for o, a in opts:
+    if o == "-q":
+        verbosity = 0
+    if o == "-v":
+        verbosity += 1
+    if o in ("--log"):
+        logfile = a
+    if o in ("--notags"):
+        tagall = False
+    if o in ("-h", "--help"):
+        usage()
+    if o in ("--ignore"):
+        ignore_warnings = True
+
+git = git_command()
+branch=git.current_branch()
+
+for o, a in opts:
+    if o in ("-t", "--timezone"):
+        git.set_config("perforce.timezone", a)
+    if o in ("--stitch"):
+        git.set_config("perforce.%s.path" % branch, a)
+        stitch = 1
+
+if len(args) == 2:
+    branch = args[1]
+    git.checkout(branch)
+    if branch == git.current_branch():
+        die("Branch %s already exists!" % branch)
+    report(1, "Setting perforce to ", args[0])
+    git.set_config("perforce.%s.path" % branch, args[0])
+elif len(args) != 0:
+    die("You must specify the perforce //depot/path and git branch")
+
+p4path = git.get_config("perforce.%s.path" % branch)
+if p4path == None:
+    die("Do not know Perforce //depot/path for git branch", branch)
+
+p4 = p4_command(p4path)
+
+for o, a in opts:
+    if o in ("-a", "--authors"):
+        p4.authors(a)
+
+localdir = git.basedir()
+if p4.where()[:len(localdir)] != localdir:
+    report(1, "**WARNING** Appears p4 client is misconfigured")
+    report(1, "   for sync from %s to %s" % (p4.repopath, localdir))
+    if ignore_warnings != True:
+        die("Reconfigure or use \"--ignore\" on command line")
+
+if stitch == 0:
+    top = git.top_change(branch)
+else:
+    top = 0
+changes = p4.changes(top)
+count = len(changes)
+if count == 0:
+    report(1, "Already up to date...")
+    sys.exit(0)
+
+ptz = git.get_config("perforce.timezone")
+if ptz:
+    report(1, "Setting timezone to", ptz)
+    os.environ['TZ'] = ptz
+    time.tzset()
+
+if stitch == 1:
+    git.remove_files()
+    git.clean_directories()
+    p4.sync(changes[0], force=True)
+elif top == 0 and branch != git.current_branch():
+    p4.sync(changes[0], test=True)
+    report(1, "Creating new initial commit");
+    git.fresh_branch(branch)
+    p4.sync(changes[0], force=True)
+else:
+    p4.sync(changes[0], trick=True)
+
+report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
+for id in changes:
+    report(1, "Importing changeset", id)
+    change = p4.describe(id)
+    p4.sync(id)
+    if tagall :
+            git.commit(change.author, change.email, change.date, change.msg, id)
+    else:
+            git.commit(change.author, change.email, change.date, change.msg, "import")
+    if stitch == 1:
+        git.clean_directories()
+        stitch = 0
diff --git a/contrib/p4import/git-p4import.txt b/contrib/p4import/git-p4import.txt
new file mode 100644 (file)
index 0000000..9967587
--- /dev/null
@@ -0,0 +1,167 @@
+git-p4import(1)
+===============
+
+NAME
+----
+git-p4import - Import a Perforce repository into git
+
+
+SYNOPSIS
+--------
+[verse]
+`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
+               <//p4repo/path> <branch>
+`git-p4import` --stitch <//p4repo/path>
+`git-p4import`
+
+
+DESCRIPTION
+-----------
+Import a Perforce repository into an existing git repository.  When
+a <//p4repo/path> and <branch> are specified a new branch with the
+given name will be created and the initial import will begin.
+
+Once the initial import is complete you can do an incremental import
+of new commits from the Perforce repository.  You do this by checking
+out the appropriate git branch and then running `git-p4import` without
+any options.
+
+The standard p4 client is used to communicate with the Perforce
+repository; it must be configured correctly in order for `git-p4import`
+to operate (see below).
+
+
+OPTIONS
+-------
+-q::
+       Do not display any progress information.
+
+-v::
+        Give extra progress information.
+
+\--authors::
+       Specify an authors file containing a mapping of Perforce user
+       ids to full names and email addresses (see Notes below).
+
+\--notags::
+       Do not create a tag for each imported commit.
+
+\--stitch::
+       Import the contents of the given perforce branch into the
+       currently checked out git branch.
+
+\--log::
+       Store debugging information in the specified file.
+
+-t::
+       Specify that the remote repository is in the specified timezone.
+       Timezone must be in the format "US/Pacific" or "Europe/London"
+       etc.  You only need to specify this once, it will be saved in
+       the git config file for the repository.
+
+<//p4repo/path>::
+       The Perforce path that will be imported into the specified branch.
+
+<branch>::
+       The new branch that will be created to hold the Perforce imports.
+
+
+P4 Client
+---------
+You must make the `p4` client command available in your $PATH and
+configure it to communicate with the target Perforce repository.
+Typically this means you must set the "$P4PORT" and "$P4CLIENT"
+environment variables.
+
+You must also configure a `p4` client "view" which maps the Perforce
+branch into the top level of your git repository, for example:
+
+------------
+Client: myhost
+
+Root:   /home/sean/import
+
+Options:   noallwrite clobber nocompress unlocked modtime rmdir
+
+View:
+        //public/jam/... //myhost/jam/...
+------------
+
+With the above `p4` client setup, you could import the "jam"
+perforce branch into a branch named "jammy", like so:
+
+------------
+$ mkdir -p /home/sean/import/jam
+$ cd /home/sean/import/jam
+$ git init
+$ git p4import //public/jam jammy
+------------
+
+
+Multiple Branches
+-----------------
+Note that by creating multiple "views" you can use `git-p4import`
+to import additional branches into the same git repository.
+However, the `p4` client has a limitation in that it silently
+ignores all but the last "view" that maps into the same local
+directory.  So the following will *not* work:
+
+------------
+View:
+        //public/jam/... //myhost/jam/...
+        //public/other/... //myhost/jam/...
+        //public/guest/... //myhost/jam/...
+------------
+
+If you want more than one Perforce branch to be imported into the
+same directory you must employ a workaround.  A simple option is
+to adjust your `p4` client before each import to only include a
+single view.
+
+Another option is to create multiple symlinks locally which all
+point to the same directory in your git repository and then use
+one per "view" instead of listing the actual directory.
+
+
+Tags
+----
+A git tag of the form p4/xx is created for every change imported from
+the Perforce repository where xx is the Perforce changeset number.
+Therefore after the import you can use git to access any commit by its
+Perforce number, e.g. git show p4/327.
+
+The tag associated with the HEAD commit is also how `git-p4import`
+determines if there are new changes to incrementally import from the
+Perforce repository.
+
+If you import from a repository with many thousands of changes
+you will have an equal number of p4/xxxx git tags.  Git tags can
+be expensive in terms of disk space and repository operations.
+If you don't need to perform further incremental imports, you
+may delete the tags.
+
+
+Notes
+-----
+You can interrupt the import (e.g. ctrl-c) at any time and restart it
+without worry.
+
+Author information is automatically determined by querying the
+Perforce "users" table using the id associated with each change.
+However, if you want to manually supply these mappings you can do
+so with the "--authors" option.  It accepts a file containing a list
+of mappings with each line containing one mapping in the format:
+
+------------
+    perforce_id = Full Name <email@address.com>
+------------
+
+
+Author
+------
+Written by Sean Estabrooks <seanlkml@sympatico.ca>
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
diff --git a/contrib/patches/docbook-xsl-manpages-charmap.patch b/contrib/patches/docbook-xsl-manpages-charmap.patch
new file mode 100644 (file)
index 0000000..f2b08b4
--- /dev/null
@@ -0,0 +1,21 @@
+From: Ismail Dönmez <ismail@pardus.org.tr>
+
+Trying to build the documentation with docbook-xsl 1.73 may result in
+the following error.  This patch fixes it.
+
+$ xmlto -m callouts.xsl man git-add.xml
+runtime error: file
+file:///usr/share/sgml/docbook/xsl-stylesheets-1.73.0/manpages/other.xsl line
+129 element call-template
+The called template 'read-character-map' was not found.
+
+--- docbook-xsl-1.73.0/manpages/docbook.xsl.manpages-charmap   2007-07-23 16:24:23.000000000 +0100
++++ docbook-xsl-1.73.0/manpages/docbook.xsl    2007-07-23 16:25:16.000000000 +0100
+@@ -37,6 +37,7 @@
+   <xsl:include href="lists.xsl"/>
+   <xsl:include href="endnotes.xsl"/>
+   <xsl:include href="table.xsl"/>
++  <xsl:include href="../common/charmap.xsl"/>
+
+   <!-- * we rename the following just to avoid using params with "man" -->
+   <!-- * prefixes in the table.xsl stylesheet (because that stylesheet -->
index dc09eae972711496aa390cb99b3017a936ce1afc..5838b3ab05fb1d9a1cf6a565513b3c7409adc73a 100644 (file)
@@ -26,10 +26,8 @@ if [ -d "$GIT_DIR"/remotes ]; then
                                mv "$GIT_DIR"/remotes "$GIT_DIR"/remotes.old
                        fi ;;
                *)
-                       echo "git-config $key "$value" $regex"
-                       git-config $key "$value" $regex || error=1 ;;
+                       echo "git config $key "$value" $regex"
+                       git config $key "$value" $regex || error=1 ;;
                esac
        done
 fi
-
-
diff --git a/contrib/stats/git-common-hash b/contrib/stats/git-common-hash
new file mode 100755 (executable)
index 0000000..e27fd08
--- /dev/null
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+# This script displays the distribution of longest common hash prefixes.
+# This can be used to determine the minimum prefix length to use
+# for object names to be unique.
+
+git rev-list --objects --all | sort | perl -lne '
+  substr($_, 40) = "";
+  # uncomment next line for a distribution of bits instead of hex chars
+  # $_ = unpack("B*",pack("H*",$_));
+  if (defined $p) {
+    ($p ^ $_) =~ /^(\0*)/;
+    $common = length $1;
+    if (defined $pcommon) {
+      $count[$pcommon > $common ? $pcommon : $common]++;
+    } else {
+      $count[$common]++; # first item
+    }
+  }
+  $p = $_;
+  $pcommon = $common;
+  END {
+    $count[$common]++; # last item
+    print "$_: $count[$_]" for 0..$#count;
+  }
+'
diff --git a/contrib/stats/mailmap.pl b/contrib/stats/mailmap.pl
new file mode 100755 (executable)
index 0000000..4b852e2
--- /dev/null
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -w
+my %mailmap = ();
+open I, "<", ".mailmap";
+while (<I>) {
+       chomp;
+       next if /^#/;
+       if (my ($author, $mail) = /^(.*?)\s+<(.+)>$/) {
+               $mailmap{$mail} = $author;
+       }
+}
+close I;
+
+my %mail2author = ();
+open I, "git log --pretty='format:%ae  %an' |";
+while (<I>) {
+       chomp;
+       my ($mail, $author) = split(/\t/, $_);
+       next if exists $mailmap{$mail};
+       $mail2author{$mail} ||= {};
+       $mail2author{$mail}{$author} ||= 0;
+       $mail2author{$mail}{$author}++;
+}
+close I;
+
+while (my ($mail, $authorcount) = each %mail2author) {
+       # %$authorcount is ($author => $count);
+       # sort and show the names from the most frequent ones.
+       my @names = (map { $_->[0] }
+               sort { $b->[1] <=> $a->[1] }
+               map { [$_, $authorcount->{$_}] }
+               keys %$authorcount);
+       if (1 < @names) {
+               for (@names) {
+                       print "$_ <$mail>\n";
+               }
+       }
+}
+
diff --git a/contrib/stats/packinfo.pl b/contrib/stats/packinfo.pl
new file mode 100755 (executable)
index 0000000..aab501e
--- /dev/null
@@ -0,0 +1,212 @@
+#!/usr/bin/perl
+#
+# This tool will print vaguely pretty information about a pack.  It
+# expects the output of "git-verify-pack -v" as input on stdin.
+#
+# $ git-verify-pack -v | packinfo.pl
+#
+# This prints some full-pack statistics; currently "all sizes", "all
+# path sizes", "tree sizes", "tree path sizes", and "depths".
+#
+# * "all sizes" stats are across every object size in the file;
+#   full sizes for base objects, and delta size for deltas.
+# * "all path sizes" stats are across all object's "path sizes".
+#   A path size is the sum of the size of the delta chain, including the
+#   base object.  In other words, it's how many bytes need be read to
+#   reassemble the file from deltas.
+# * "tree sizes" are object sizes grouped into delta trees.
+# * "tree path sizes" are path sizes grouped into delta trees.
+# * "depths" should be obvious.
+#
+# When run as:
+#
+# $ git-verify-pack -v | packinfo.pl -tree
+#
+# the trees of objects are output along with the stats.  This looks
+# like:
+#
+#   0 commit 031321c6...      803      803
+#
+#   0   blob 03156f21...     1767     1767
+#   1    blob f52a9d7f...       10     1777
+#   2     blob a8cc5739...       51     1828
+#   3      blob 660e90b1...       15     1843
+#   4       blob 0cb8e3bb...       33     1876
+#   2     blob e48607f0...      311     2088
+#      size: count 6 total 2187 min 10 max 1767 mean 364.50 median 51 std_dev 635.85
+# path size: count 6 total 11179 min 1767 max 2088 mean 1863.17 median 1843 std_dev 107.26
+#
+# The first number after the sha1 is the object size, the second
+# number is the path size.  The statistics are across all objects in
+# the previous delta tree.  Obviously they are omitted for trees of
+# one object.
+#
+# When run as:
+#
+# $ git-verify-pack -v | packinfo.pl -tree -filenames
+#
+# it adds filenames to the tree.  Getting this information is slow:
+#
+#   0   blob 03156f21...     1767     1767 Documentation/git-lost-found.txt @ tags/v1.2.0~142
+#   1    blob f52a9d7f...       10     1777 Documentation/git-lost-found.txt @ tags/v1.5.0-rc1~74
+#   2     blob a8cc5739...       51     1828 Documentation/git-lost+found.txt @ tags/v0.99.9h^0
+#   3      blob 660e90b1...       15     1843 Documentation/git-lost+found.txt @ master~3222^2~2
+#   4       blob 0cb8e3bb...       33     1876 Documentation/git-lost+found.txt @ master~3222^2~3
+#   2     blob e48607f0...      311     2088 Documentation/git-lost-found.txt @ tags/v1.5.2-rc3~4
+#      size: count 6 total 2187 min 10 max 1767 mean 364.50 median 51 std_dev 635.85
+# path size: count 6 total 11179 min 1767 max 2088 mean 1863.17 median 1843 std_dev 107.26
+#
+# When run as:
+#
+# $ git-verify-pack -v | packinfo.pl -dump
+#
+# it prints out "sha1 size pathsize depth" for each sha1 in lexical
+# order.
+#
+# 000079a2eaef17b7eae70e1f0f635557ea67b644 30 472 7
+# 00013cafe6980411aa6fdd940784917b5ff50f0a 44 1542 4
+# 000182eacf99cde27d5916aa415921924b82972c 499 499 0
+# ...
+#
+# This is handy for comparing two packs.  Adding "-filenames" will add
+# filenames, as per "-tree -filenames" above.
+
+use strict;
+use Getopt::Long;
+
+my $filenames = 0;
+my $tree = 0;
+my $dump = 0;
+GetOptions("tree" => \$tree,
+           "filenames" => \$filenames,
+           "dump" => \$dump);
+
+my %parents;
+my %children;
+my %sizes;
+my @roots;
+my %paths;
+my %types;
+my @commits;
+my %names;
+my %depths;
+my @depths;
+
+while (<STDIN>) {
+    my ($sha1, $type, $size, $offset, $depth, $parent) = split(/\s+/, $_);
+    next unless ($sha1 =~ /^[0-9a-f]{40}$/);
+    $depths{$sha1} = $depth || 0;
+    push(@depths, $depth || 0);
+    push(@commits, $sha1) if ($type eq 'commit');
+    push(@roots, $sha1) unless $parent;
+    $parents{$sha1} = $parent;
+    $types{$sha1} = $type;
+    push(@{$children{$parent}}, $sha1);
+    $sizes{$sha1} = $size;
+}
+
+if ($filenames && ($tree || $dump)) {
+    open(NAMES, "git-name-rev --all|");
+    while (<NAMES>) {
+        if (/^(\S+)\s+(.*)$/) {
+            my ($sha1, $name) = ($1, $2);
+            $names{$sha1} = $name;
+        }
+    }
+    close NAMES;
+
+    for my $commit (@commits) {
+        my $name = $names{$commit};
+        open(TREE, "git-ls-tree -t -r $commit|");
+        print STDERR "Plumbing tree $name\n";
+        while (<TREE>) {
+            if (/^(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
+                my ($mode, $type, $sha1, $path) = ($1, $2, $3, $4);
+                $paths{$sha1} = "$path @ $name";
+            }
+        }
+        close TREE;
+    }
+}
+
+sub stats {
+    my @data = sort {$a <=> $b} @_;
+    my $min = $data[0];
+    my $max = $data[$#data];
+    my $total = 0;
+    my $count = scalar @data;
+    for my $datum (@data) {
+        $total += $datum;
+    }
+    my $mean = $total / $count;
+    my $median = $data[int(@data / 2)];
+    my $diff_sum = 0;
+    for my $datum (@data) {
+        $diff_sum += ($datum - $mean)**2;
+    }
+    my $std_dev = sqrt($diff_sum / $count);
+    return ($count, $total, $min, $max, $mean, $median, $std_dev);
+}
+
+sub print_stats {
+    my $name = shift;
+    my ($count, $total, $min, $max, $mean, $median, $std_dev) = stats(@_);
+    printf("%s: count %s total %s min %s max %s mean %.2f median %s std_dev %.2f\n",
+           $name, $count, $total, $min, $max, $mean, $median, $std_dev);
+}
+
+my @sizes;
+my @path_sizes;
+my @all_sizes;
+my @all_path_sizes;
+my %path_sizes;
+
+sub dig {
+    my ($sha1, $depth, $path_size) = @_;
+    $path_size += $sizes{$sha1};
+    push(@sizes, $sizes{$sha1});
+    push(@all_sizes, $sizes{$sha1});
+    push(@path_sizes, $path_size);
+    push(@all_path_sizes, $path_size);
+    $path_sizes{$sha1} = $path_size;
+    if ($tree) {
+        printf("%3d%s %6s %s %8d %8d %s\n",
+               $depth, (" " x $depth), $types{$sha1},
+               $sha1, $sizes{$sha1}, $path_size, $paths{$sha1});
+    }
+    for my $child (@{$children{$sha1}}) {
+        dig($child, $depth + 1, $path_size);
+    }
+}
+
+my @tree_sizes;
+my @tree_path_sizes;
+
+for my $root (@roots) {
+    undef @sizes;
+    undef @path_sizes;
+    dig($root, 0, 0);
+    my ($aa, $sz_total) = stats(@sizes);
+    my ($bb, $psz_total) = stats(@path_sizes);
+    push(@tree_sizes, $sz_total);
+    push(@tree_path_sizes, $psz_total);
+    if ($tree) {
+        if (@sizes > 1) {
+            print_stats("     size", @sizes);
+            print_stats("path size", @path_sizes);
+        }
+        print "\n";
+    }
+}
+
+if ($dump) {
+    for my $sha1 (sort keys %sizes) {
+        print "$sha1 $sizes{$sha1} $path_sizes{$sha1} $depths{$sha1} $paths{$sha1}\n";
+    }
+} else {
+    print_stats("      all sizes", @all_sizes);
+    print_stats(" all path sizes", @all_path_sizes);
+    print_stats("     tree sizes", @tree_sizes);
+    print_stats("tree path sizes", @tree_path_sizes);
+    print_stats("         depths", @depths);
+}
index 9877b98508497c9377876bd08e8f130660b9ba05..3ff6bd166ab7db5612a419ffcdc93b9b3d7eb2f8 100755 (executable)
@@ -20,17 +20,24 @@ new_workdir=$2
 branch=$3
 
 # want to make sure that what is pointed to has a .git directory ...
-test -d "$orig_git/.git" || die "\"$orig_git\" is not a git repository!"
+git_dir=$(cd "$orig_git" 2>/dev/null &&
+  git rev-parse --git-dir 2>/dev/null) ||
+  die "\"$orig_git\" is not a git repository!"
+
+if test "$git_dir" = ".git"
+then
+       git_dir="$orig_git/.git"
+fi
 
 # don't link to a workdir
-if test -L "$orig_git/.git/config"
+if test -L "$git_dir/config"
 then
        die "\"$orig_git\" is a working directory only, please specify" \
                "a complete repository."
 fi
 
 # make sure the the links use full paths
-orig_git=$(cd "$orig_git"; pwd)
+git_dir=$(cd "$git_dir"; pwd)
 
 # create the workdir
 mkdir -p "$new_workdir/.git" || die "unable to create \"$new_workdir\"!"
@@ -45,13 +52,13 @@ do
                mkdir -p "$(dirname "$new_workdir/.git/$x")"
                ;;
        esac
-       ln -s "$orig_git/.git/$x" "$new_workdir/.git/$x"
+       ln -s "$git_dir/$x" "$new_workdir/.git/$x"
 done
 
 # now setup the workdir
 cd "$new_workdir"
 # copy the HEAD from the original repository as a default branch
-cp "$orig_git/.git/HEAD" .git/HEAD
+cp "$git_dir/HEAD" .git/HEAD
 # checkout the branch (either the same as HEAD from the original repository, or
 # the one that was asked for)
 git checkout -f $branch
index cefbcebdcaa0a085745c81b2ed1103e9575cb635..90e7900e6d7aff2fadf9ba04f8d982733493411c 100644 (file)
@@ -194,7 +194,7 @@ static unsigned long parse_oldstyle_date(const char *buf)
                fmt++;
        } while (*buf && *fmt);
        printf("left: %s\n", buf);
-       return mktime(&tm);                             
+       return mktime(&tm);
 }
 
 static int convert_date_line(char *dst, void **buf, unsigned long *sp)
diff --git a/copy.c b/copy.c
index d340bb253ec35af379c29e71f384e15d6822fb9a..c225d1b0ff0a67e637f7200ab5c2a917b550af4f 100644 (file)
--- a/copy.c
+++ b/copy.c
@@ -34,4 +34,3 @@ int copy_fd(int ifd, int ofd)
        close(ifd);
        return 0;
 }
-
index 7c806ada48d0fd58c091f9415fc8bb2f61bdd2e6..9ab997120d04c5be0aa9d3ff3ba090ba12b7bec8 100644 (file)
@@ -29,18 +29,20 @@ static void sha1flush(struct sha1file *f, unsigned int count)
        }
 }
 
-int sha1close(struct sha1file *f, unsigned char *result, int update)
+int sha1close(struct sha1file *f, unsigned char *result, int final)
 {
        unsigned offset = f->offset;
        if (offset) {
                SHA1_Update(&f->ctx, f->buffer, offset);
                sha1flush(f, offset);
+               f->offset = 0;
        }
+       if (!final)
+               return 0;       /* only want to flush (no checksum write, no close) */
        SHA1_Final(f->buffer, &f->ctx);
        if (result)
                hashcpy(result, f->buffer);
-       if (update)
-               sha1flush(f, 20);
+       sha1flush(f, 20);
        if (close(f->fd))
                die("%s: sha1 file error on close (%s)", f->name, strerror(errno));
        free(f);
@@ -71,33 +73,6 @@ int sha1write(struct sha1file *f, void *buf, unsigned int count)
        return 0;
 }
 
-struct sha1file *sha1create(const char *fmt, ...)
-{
-       struct sha1file *f;
-       unsigned len;
-       va_list arg;
-       int fd;
-
-       f = xmalloc(sizeof(*f));
-
-       va_start(arg, fmt);
-       len = vsnprintf(f->name, sizeof(f->name), fmt, arg);
-       va_end(arg);
-       if (len >= PATH_MAX)
-               die("you wascally wabbit, you");
-       f->namelen = len;
-
-       fd = open(f->name, O_CREAT | O_EXCL | O_WRONLY, 0666);
-       if (fd < 0)
-               die("unable to open %s (%s)", f->name, strerror(errno));
-       f->fd = fd;
-       f->error = 0;
-       f->offset = 0;
-       f->do_crc = 0;
-       SHA1_Init(&f->ctx);
-       return f;
-}
-
 struct sha1file *sha1fd(int fd, const char *name)
 {
        struct sha1file *f;
@@ -119,34 +94,6 @@ struct sha1file *sha1fd(int fd, const char *name)
        return f;
 }
 
-int sha1write_compressed(struct sha1file *f, void *in, unsigned int size)
-{
-       z_stream stream;
-       unsigned long maxsize;
-       void *out;
-
-       memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
-       maxsize = deflateBound(&stream, size);
-       out = xmalloc(maxsize);
-
-       /* Compress it */
-       stream.next_in = in;
-       stream.avail_in = size;
-
-       stream.next_out = out;
-       stream.avail_out = maxsize;
-
-       while (deflate(&stream, Z_FINISH) == Z_OK)
-               /* nothing */;
-       deflateEnd(&stream);
-
-       size = stream.total_out;
-       sha1write(f, out, size);
-       free(out);
-       return size;
-}
-
 void crc32_begin(struct sha1file *f)
 {
        f->crc32 = crc32(0, Z_NULL, 0);
index 7e1339189dcdc6e271fad5df7ad427954642ae9e..c3c792f1b56026b6a4d9d10b6ba63947c7f383cf 100644 (file)
@@ -13,10 +13,8 @@ struct sha1file {
 };
 
 extern struct sha1file *sha1fd(int fd, const char *name);
-extern struct sha1file *sha1create(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
 extern int sha1close(struct sha1file *, unsigned char *, int);
 extern int sha1write(struct sha1file *, void *, unsigned int);
-extern int sha1write_compressed(struct sha1file *, void *, unsigned int);
 extern void crc32_begin(struct sha1file *);
 extern uint32_t crc32_end(struct sha1file *);
 
diff --git a/ctype.c b/ctype.c
index 56bdffa636e6b4465ab8451de0351ff394182885..ee06eb7f48f1d3e818b3037369b4e056fe7e5be7 100644 (file)
--- a/ctype.c
+++ b/ctype.c
@@ -20,4 +20,3 @@ unsigned char sane_ctype[256] = {
        AA, AA, AA, AA, AA, AA, AA, AA, AA, AA, AA,  0,  0,  0,  0,  0,         /* 112-15 */
        /* Nothing in the 128.. range */
 };
-
index 674e30dca3d05cabc5a72e7bb0a40e64eaa4b2eb..9cf22fef417cee29e550a35fef1bac1050482afa 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -16,7 +16,8 @@ static int reuseaddr;
 static const char daemon_usage[] =
 "git-daemon [--verbose] [--syslog] [--export-all]\n"
 "           [--timeout=n] [--init-timeout=n] [--strict-paths]\n"
-"           [--base-path=path] [--user-path | --user-path=path]\n"
+"           [--base-path=path] [--base-path-relaxed]\n"
+"           [--user-path | --user-path=path]\n"
 "           [--interpolated-path=path]\n"
 "           [--reuseaddr] [--detach] [--pid-file=file]\n"
 "           [--[enable|disable|allow-override|forbid-override]=service]\n"
@@ -34,6 +35,7 @@ static int export_all_trees;
 /* Take all paths relative to this one if non-NULL */
 static char *base_path;
 static char *interpolated_path;
+static int base_path_relaxed;
 
 /* Flag indicating client sent extra args. */
 static int saw_extended_args;
@@ -133,7 +135,7 @@ static int avoid_alias(char *p)
 {
        int sl, ndot;
 
-       /* 
+       /*
         * This resurrects the belts and suspenders paranoia check by HPA
         * done in <435560F7.4080006@zytor.com> thread, now enter_repo()
         * does not do getcwd() based path canonicalizations.
@@ -180,6 +182,7 @@ static char *path_ok(struct interp *itable)
 {
        static char rpath[PATH_MAX];
        static char interp_path[PATH_MAX];
+       int retried_path = 0;
        char *path;
        char *dir;
 
@@ -235,7 +238,22 @@ static char *path_ok(struct interp *itable)
                dir = rpath;
        }
 
-       path = enter_repo(dir, strict_paths);
+       do {
+               path = enter_repo(dir, strict_paths);
+               if (path)
+                       break;
+
+               /*
+                * if we fail and base_path_relaxed is enabled, try without
+                * prefixing the base path
+                */
+               if (base_path && base_path_relaxed && !retried_path) {
+                       dir = itable[INTERP_SLOT_DIR].value;
+                       retried_path = 1;
+                       continue;
+               }
+               break;
+       } while (1);
 
        if (!path) {
                logerror("'%s': unable to chdir or not a git archive", dir);
@@ -247,7 +265,7 @@ static char *path_ok(struct interp *itable)
                int pathlen = strlen(path);
 
                /* The validation is done on the paths after enter_repo
-                * appends optional {.git,.git/.git} and friends, but 
+                * appends optional {.git,.git/.git} and friends, but
                 * it does not use getcwd().  So if your /pub is
                 * a symlink to /mnt/pub, you can whitelist /pub and
                 * do not have to say /mnt/pub.
@@ -439,7 +457,7 @@ static void parse_extra_args(struct interp *table, char *extra_args, int buflen)
        }
 }
 
-void fill_in_extra_table_entries(struct interp *itable)
+static void fill_in_extra_table_entries(struct interp *itable)
 {
        char *hp;
 
@@ -1061,6 +1079,10 @@ int main(int argc, char **argv)
                        base_path = arg+12;
                        continue;
                }
+               if (!strcmp(arg, "--base-path-relaxed")) {
+                       base_path_relaxed = 1;
+                       continue;
+               }
                if (!prefixcmp(arg, "--interpolated-path=")) {
                        interpolated_path = arg+20;
                        continue;
diff --git a/date.c b/date.c
index 4690371e5559f72b7755b3cc92ae85843098d285..93bef6efbe38cb8983fdda14b75ce772f90e1b6a 100644 (file)
--- a/date.c
+++ b/date.c
@@ -137,6 +137,18 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        if (mode == DATE_SHORT)
                sprintf(timebuf, "%04d-%02d-%02d", tm->tm_year + 1900,
                                tm->tm_mon + 1, tm->tm_mday);
+       else if (mode == DATE_ISO8601)
+               sprintf(timebuf, "%04d-%02d-%02d %02d:%02d:%02d %+05d",
+                               tm->tm_year + 1900,
+                               tm->tm_mon + 1,
+                               tm->tm_mday,
+                               tm->tm_hour, tm->tm_min, tm->tm_sec,
+                               tz);
+       else if (mode == DATE_RFC2822)
+               sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
+                       weekday_names[tm->tm_wday], tm->tm_mday,
+                       month_names[tm->tm_mon], tm->tm_year + 1900,
+                       tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
        else
                sprintf(timebuf, "%.3s %.3s %d %02d:%02d:%02d %d%c%+05d",
                                weekday_names[tm->tm_wday],
@@ -149,21 +161,6 @@ const char *show_date(unsigned long time, int tz, enum date_mode mode)
        return timebuf;
 }
 
-const char *show_rfc2822_date(unsigned long time, int tz)
-{
-       struct tm *tm;
-       static char timebuf[200];
-
-       tm = time_to_tm(time, tz);
-       if (!tm)
-               return NULL;
-       sprintf(timebuf, "%.3s, %d %.3s %d %02d:%02d:%02d %+05d",
-               weekday_names[tm->tm_wday], tm->tm_mday,
-               month_names[tm->tm_mon], tm->tm_year + 1900,
-               tm->tm_hour, tm->tm_min, tm->tm_sec, tz);
-       return timebuf;
-}
-
 /*
  * Check these. And note how it doesn't do the summer-time conversion.
  *
@@ -403,7 +400,7 @@ static int match_multi_number(unsigned long num, char c, const char *date, char
 }
 
 /*
- * We've seen a digit. Time? Year? Date? 
+ * We've seen a digit. Time? Year? Date?
  */
 static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt)
 {
@@ -495,7 +492,7 @@ static int match_digit(const char *date, struct tm *tm, int *offset, int *tm_gmt
        } else if (num > 0 && num < 13) {
                tm->tm_mon = num-1;
        }
-               
+
        return n;
 }
 
@@ -569,13 +566,13 @@ int parse_date(const char *date, char *result, int maxlen)
                if (!match) {
                        /* BAD CRAP */
                        match = 1;
-               }       
+               }
 
                date += match;
        }
 
        /* mktime uses local timezone */
-       then = my_mktime(&tm); 
+       then = my_mktime(&tm);
        if (offset == -1)
                offset = (then - mktime(&tm)) / 60;
 
@@ -663,6 +660,14 @@ static void date_am(struct tm *tm, int *num)
        tm->tm_hour = (hour % 12);
 }
 
+static void date_never(struct tm *tm, int *num)
+{
+       tm->tm_mon = tm->tm_wday = tm->tm_yday
+               = tm->tm_hour = tm->tm_min = tm->tm_sec = 0;
+       tm->tm_year = 70;
+       tm->tm_mday = 1;
+}
+
 static const struct special {
        const char *name;
        void (*fn)(struct tm *, int *);
@@ -673,6 +678,7 @@ static const struct special {
        { "tea", date_tea },
        { "PM", date_pm },
        { "AM", date_am },
+       { "never", date_never },
        { NULL }
 };
 
@@ -691,7 +697,7 @@ static const struct typelen {
        { "days", 24*60*60 },
        { "weeks", 7*24*60*60 },
        { NULL }
-};     
+};
 
 static const char *approxidate_alpha(const char *date, struct tm *tm, int *num)
 {
diff --git a/delta.h b/delta.h
index 7b3f86d85f71e47d11c40a4abbcd9d9f499a6636..40ccf5a1e95f62d840a006274f7024fa43208b1c 100644 (file)
--- a/delta.h
+++ b/delta.h
@@ -23,6 +23,13 @@ create_delta_index(const void *buf, unsigned long bufsize);
  */
 extern void free_delta_index(struct delta_index *index);
 
+/*
+ * sizeof_delta_index: returns memory usage of delta index
+ *
+ * Given pointer must be what create_delta_index() returned, or NULL.
+ */
+extern unsigned long sizeof_delta_index(struct delta_index *index);
+
 /*
  * create_delta: create a delta from given index for the given buffer
  *
index 9f998d0a73e0127d3a68a7caecb3727569149871..3af583536f2ee9f2d1f2fd699148ff16166dc932 100644 (file)
@@ -1,21 +1,14 @@
 /*
  * diff-delta.c: generate a delta between two buffers
  *
- *  Many parts of this file have been lifted from LibXDiff version 0.10.
- *  http://www.xmailserver.org/xdiff-lib.html
+ * This code was greatly inspired by parts of LibXDiff from Davide Libenzi
+ * http://www.xmailserver.org/xdiff-lib.html
  *
- *  LibXDiff was written by Davide Libenzi <davidel@xmailserver.org>
- *  Copyright (C) 2003 Davide Libenzi
+ * Rewritten for GIT by Nicolas Pitre <nico@cam.org>, (C) 2005-2007
  *
- *  Many mods for GIT usage by Nicolas Pitre <nico@cam.org>, (C) 2005.
- *
- *  This file 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.
- *
- *  Use of this within git automatically means that the LGPL
- *  licensing gets turned into GPLv2 within this project.
+ * This code is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
  */
 
 #include "git-compat-util.h"
@@ -126,6 +119,7 @@ struct index_entry {
 };
 
 struct delta_index {
+       unsigned long memsize;
        const void *src_buf;
        unsigned long src_size;
        unsigned int hash_mask;
@@ -166,6 +160,7 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
        mem = hash + hsize;
        entry = mem;
 
+       index->memsize = memsize;
        index->src_buf = buf;
        index->src_size = bufsize;
        index->hash_mask = hmask;
@@ -235,6 +230,14 @@ void free_delta_index(struct delta_index *index)
        free(index);
 }
 
+unsigned long sizeof_delta_index(struct delta_index *index)
+{
+       if (index)
+               return index->memsize;
+       else
+               return 0;
+}
+
 /*
  * The maximum size for any opcode sequence, including the initial header
  * plus Rabin window plus biggest copy.
@@ -246,7 +249,7 @@ create_delta(const struct delta_index *index,
             const void *trg_buf, unsigned long trg_size,
             unsigned long *delta_size, unsigned long max_size)
 {
-       unsigned int i, outpos, outsize, val;
+       unsigned int i, outpos, outsize, moff, msize, val;
        int inscnt;
        const unsigned char *ref_data, *ref_top, *data, *top;
        unsigned char *out;
@@ -291,30 +294,33 @@ create_delta(const struct delta_index *index,
        }
        inscnt = i;
 
+       moff = 0;
+       msize = 0;
        while (data < top) {
-               unsigned int moff = 0, msize = 0;
-               struct index_entry *entry;
-               val ^= U[data[-RABIN_WINDOW]];
-               val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
-               i = val & index->hash_mask;
-               for (entry = index->hash[i]; entry; entry = entry->next) {
-                       const unsigned char *ref = entry->ptr;
-                       const unsigned char *src = data;
-                       unsigned int ref_size = ref_top - ref;
-                       if (entry->val != val)
-                               continue;
-                       if (ref_size > top - src)
-                               ref_size = top - src;
-                       if (ref_size > 0x10000)
-                               ref_size = 0x10000;
-                       if (ref_size <= msize)
-                               break;
-                       while (ref_size-- && *src++ == *ref)
-                               ref++;
-                       if (msize < ref - entry->ptr) {
-                               /* this is our best match so far */
-                               msize = ref - entry->ptr;
-                               moff = entry->ptr - ref_data;
+               if (msize < 4096) {
+                       struct index_entry *entry;
+                       val ^= U[data[-RABIN_WINDOW]];
+                       val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
+                       i = val & index->hash_mask;
+                       for (entry = index->hash[i]; entry; entry = entry->next) {
+                               const unsigned char *ref = entry->ptr;
+                               const unsigned char *src = data;
+                               unsigned int ref_size = ref_top - ref;
+                               if (entry->val != val)
+                                       continue;
+                               if (ref_size > top - src)
+                                       ref_size = top - src;
+                               if (ref_size <= msize)
+                                       break;
+                               while (ref_size-- && *src++ == *ref)
+                                       ref++;
+                               if (msize < ref - entry->ptr) {
+                                       /* this is our best match so far */
+                                       msize = ref - entry->ptr;
+                                       moff = entry->ptr - ref_data;
+                                       if (msize >= 4096) /* good enough */
+                                               break;
+                               }
                        }
                }
 
@@ -327,27 +333,13 @@ create_delta(const struct delta_index *index,
                                out[outpos - inscnt - 1] = inscnt;
                                inscnt = 0;
                        }
+                       msize = 0;
                } else {
+                       unsigned int left;
                        unsigned char *op;
 
-                       if (msize >= RABIN_WINDOW) {
-                               const unsigned char *sk;
-                               sk = data + msize - RABIN_WINDOW;
-                               val = 0;
-                               for (i = 0; i < RABIN_WINDOW; i++)
-                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
-                       } else {
-                               const unsigned char *sk = data + 1;
-                               for (i = 1; i < msize; i++) {
-                                       val ^= U[sk[-RABIN_WINDOW]];
-                                       val = ((val << 8) | *sk++) ^ T[val >> RABIN_SHIFT];
-                               }
-                       }
-
                        if (inscnt) {
                                while (moff && ref_data[moff-1] == data[-1]) {
-                                       if (msize == 0x10000)
-                                               break;
                                        /* we can match one byte back */
                                        msize++;
                                        moff--;
@@ -363,23 +355,40 @@ create_delta(const struct delta_index *index,
                                inscnt = 0;
                        }
 
-                       data += msize;
+                       /* A copy op is currently limited to 64KB (pack v2) */
+                       left = (msize < 0x10000) ? 0 : (msize - 0x10000);
+                       msize -= left;
+
                        op = out + outpos++;
                        i = 0x80;
 
-                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
-                       moff >>= 8;
-                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x02; }
-                       moff >>= 8;
-                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x04; }
-                       moff >>= 8;
-                       if (moff & 0xff) { out[outpos++] = moff; i |= 0x08; }
+                       if (moff & 0x000000ff)
+                               out[outpos++] = moff >> 0,  i |= 0x01;
+                       if (moff & 0x0000ff00)
+                               out[outpos++] = moff >> 8,  i |= 0x02;
+                       if (moff & 0x00ff0000)
+                               out[outpos++] = moff >> 16, i |= 0x04;
+                       if (moff & 0xff000000)
+                               out[outpos++] = moff >> 24, i |= 0x08;
 
-                       if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
-                       msize >>= 8;
-                       if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
+                       if (msize & 0x00ff)
+                               out[outpos++] = msize >> 0, i |= 0x10;
+                       if (msize & 0xff00)
+                               out[outpos++] = msize >> 8, i |= 0x20;
 
                        *op = i;
+
+                       data += msize;
+                       moff += msize;
+                       msize = left;
+
+                       if (msize < 4096) {
+                               int j;
+                               val = 0;
+                               for (j = -RABIN_WINDOW; j < 0; j++)
+                                       val = ((val << 8) | data[j])
+                                             ^ T[val >> RABIN_SHIFT];
+                       }
                }
 
                if (outpos >= outsize - MAX_OP_SIZE) {
@@ -389,7 +398,7 @@ create_delta(const struct delta_index *index,
                                outsize = max_size + MAX_OP_SIZE + 1;
                        if (max_size && outpos > max_size)
                                break;
-                       out = xrealloc(out, outsize);
+                       out = realloc(out, outsize);
                        if (!out) {
                                free(tmp);
                                return NULL;
index 07f4e8106a51384d2236b182438472884c300da6..f5568c3b36338a203a42ce1e46109152ad4642bb 100644 (file)
@@ -24,7 +24,7 @@ static int read_directory(const char *path, struct path_list *list)
 
        while ((e = readdir(dir)))
                if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
-                       path_list_insert(xstrdup(e->d_name), list);
+                       path_list_insert(e->d_name, list);
 
        closedir(dir);
        return 0;
@@ -189,6 +189,7 @@ static int handle_diff_files_args(struct rev_info *revs,
                                !strcmp(argv[1], "--no-index")) {
                        revs->max_count = -2;
                        revs->diffopt.exit_with_status = 1;
+                       revs->diffopt.no_index = 1;
                }
                else if (!strcmp(argv[1], "-q"))
                        *silent = 1;
@@ -204,8 +205,10 @@ static int handle_diff_files_args(struct rev_info *revs,
                 */
                read_cache();
                if (!is_in_index(revs->diffopt.paths[0]) ||
-                                       !is_in_index(revs->diffopt.paths[1]))
+                                       !is_in_index(revs->diffopt.paths[1])) {
                        revs->max_count = -2;
+                       revs->diffopt.no_index = 1;
+               }
        }
 
        /*
@@ -293,6 +296,7 @@ int setup_diff_no_index(struct rev_info *revs,
        else
                revs->diffopt.paths = argv + argc - 2;
        revs->diffopt.nr_paths = 2;
+       revs->diffopt.no_index = 1;
        revs->max_count = -2;
        return 0;
 }
@@ -304,7 +308,7 @@ int run_diff_files_cmd(struct rev_info *revs, int argc, const char **argv)
        if (handle_diff_files_args(revs, argc, argv, &silent_on_removed))
                return -1;
 
-       if (revs->max_count == -2) {
+       if (revs->diffopt.no_index) {
                if (revs->diffopt.nr_paths != 2)
                        return error("need two files/directories with --no-index");
                if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
@@ -664,7 +668,7 @@ int run_diff_index(struct rev_info *revs, int cached)
        const char *tree_name;
        int match_missing = 0;
 
-       /* 
+       /*
         * Backward compatibility wart - "diff-index -m" does
         * not mean "do not ignore merges", but totally different.
         */
diff --git a/diff.c b/diff.c
index da992dd4851dd67ab9a307c1b08ebed442e406cb..97cc5bc085ba21693007328bb402860420949ec0 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -56,6 +56,14 @@ static struct ll_diff_driver {
        char *cmd;
 } *user_diff, **user_diff_tail;
 
+static void read_config_if_needed(void)
+{
+       if (!user_diff_tail) {
+               user_diff_tail = &user_diff;
+               git_config(git_diff_ui_config);
+       }
+}
+
 /*
  * Currently there is only "diff.<drivername>.command" variable;
  * because there are "diff.color.<slot>" variables, we are parsing
@@ -93,6 +101,45 @@ static int parse_lldiff_command(const char *var, const char *ep, const char *val
        return 0;
 }
 
+/*
+ * 'diff.<what>.funcname' attribute can be specified in the configuration
+ * to define a customized regexp to find the beginning of a function to
+ * be used for hunk header lines of "diff -p" style output.
+ */
+static struct funcname_pattern {
+       char *name;
+       char *pattern;
+       struct funcname_pattern *next;
+} *funcname_pattern_list;
+
+static int parse_funcname_pattern(const char *var, const char *ep, const char *value)
+{
+       const char *name;
+       int namelen;
+       struct funcname_pattern *pp;
+
+       name = var + 5; /* "diff." */
+       namelen = ep - name;
+
+       for (pp = funcname_pattern_list; pp; pp = pp->next)
+               if (!strncmp(pp->name, name, namelen) && !pp->name[namelen])
+                       break;
+       if (!pp) {
+               char *namebuf;
+               pp = xcalloc(1, sizeof(*pp));
+               namebuf = xmalloc(namelen + 1);
+               memcpy(namebuf, name, namelen);
+               namebuf[namelen] = 0;
+               pp->name = namebuf;
+               pp->next = funcname_pattern_list;
+               funcname_pattern_list = pp;
+       }
+       if (pp->pattern)
+               free(pp->pattern);
+       pp->pattern = xstrdup(value);
+       return 0;
+}
+
 /*
  * These are to give UI layer defaults.
  * The core-level commands such as git-diff-files should
@@ -122,8 +169,12 @@ int git_diff_ui_config(const char *var, const char *value)
        if (!prefixcmp(var, "diff.")) {
                const char *ep = strrchr(var, '.');
 
-               if (ep != var + 4 && !strcmp(ep, ".command"))
-                       return parse_lldiff_command(var, ep, value);
+               if (ep != var + 4) {
+                       if (!strcmp(ep, ".command"))
+                               return parse_lldiff_command(var, ep, value);
+                       if (!strcmp(ep, ".funcname"))
+                               return parse_funcname_pattern(var, ep, value);
+               }
        }
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
@@ -390,6 +441,7 @@ static void diff_words_show(struct diff_words_data *diff_words)
        mmfile_t minus, plus;
        int i;
 
+       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);
@@ -408,7 +460,6 @@ static void diff_words_show(struct diff_words_data *diff_words)
 
        xpp.flags = XDF_NEED_MINIMAL;
        xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc;
-       xecfg.flags = 0;
        ecb.outf = xdiff_outf;
        ecb.priv = diff_words;
        diff_words->xm.consume = fn_out_diff_words_aux;
@@ -1102,30 +1153,111 @@ static void setup_diff_attr_check(struct git_attr_check *check)
 {
        static struct git_attr *attr_diff;
 
-       if (!attr_diff)
+       if (!attr_diff) {
                attr_diff = git_attr("diff", 4);
-       check->attr = attr_diff;
+       }
+       check[0].attr = attr_diff;
 }
 
-static int file_is_binary(struct diff_filespec *one)
+static void diff_filespec_check_attr(struct diff_filespec *one)
 {
        struct git_attr_check attr_diff_check;
+       int check_from_data = 0;
+
+       if (one->checked_attr)
+               return;
 
        setup_diff_attr_check(&attr_diff_check);
+       one->is_binary = 0;
+       one->funcname_pattern_ident = NULL;
+
        if (!git_checkattr(one->path, 1, &attr_diff_check)) {
-               const char *value = attr_diff_check.value;
+               const char *value;
+
+               /* binaryness */
+               value = attr_diff_check.value;
                if (ATTR_TRUE(value))
-                       return 0;
+                       ;
                else if (ATTR_FALSE(value))
-                       return 1;
+                       one->is_binary = 1;
+               else
+                       check_from_data = 1;
+
+               /* funcname pattern ident */
+               if (ATTR_TRUE(value) || ATTR_FALSE(value) || ATTR_UNSET(value))
+                       ;
+               else
+                       one->funcname_pattern_ident = value;
        }
 
-       if (!one->data) {
-               if (!DIFF_FILE_VALID(one))
-                       return 0;
-               diff_populate_filespec(one, 0);
+       if (check_from_data) {
+               if (!one->data && DIFF_FILE_VALID(one))
+                       diff_populate_filespec(one, 0);
+
+               if (one->data)
+                       one->is_binary = buffer_is_binary(one->data, one->size);
        }
-       return buffer_is_binary(one->data, one->size);
+}
+
+int diff_filespec_is_binary(struct diff_filespec *one)
+{
+       diff_filespec_check_attr(one);
+       return one->is_binary;
+}
+
+static const char *funcname_pattern(const char *ident)
+{
+       struct funcname_pattern *pp;
+
+       read_config_if_needed();
+       for (pp = funcname_pattern_list; pp; pp = pp->next)
+               if (!strcmp(ident, pp->name))
+                       return pp->pattern;
+       return NULL;
+}
+
+static struct builtin_funcname_pattern {
+       const char *name;
+       const char *pattern;
+} builtin_funcname_pattern[] = {
+       { "java", "!^[  ]*\\(catch\\|do\\|for\\|if\\|instanceof\\|"
+                       "new\\|return\\|switch\\|throw\\|while\\)\n"
+                       "^[     ]*\\(\\([       ]*"
+                       "[A-Za-z_][A-Za-z_0-9]*\\)\\{2,\\}"
+                       "[      ]*([^;]*$\\)" },
+       { "tex", "^\\(\\\\\\(sub\\)*section{.*\\)$" },
+};
+
+static const char *diff_funcname_pattern(struct diff_filespec *one)
+{
+       const char *ident, *pattern;
+       int i;
+
+       diff_filespec_check_attr(one);
+       ident = one->funcname_pattern_ident;
+
+       if (!ident)
+               /*
+                * If the config file has "funcname.default" defined, that
+                * regexp is used; otherwise NULL is returned and xemit uses
+                * the built-in default.
+                */
+               return funcname_pattern("default");
+
+       /* Look up custom "funcname.$ident" regexp from config. */
+       pattern = funcname_pattern(ident);
+       if (pattern)
+               return pattern;
+
+       /*
+        * And define built-in fallback patterns here.  Note that
+        * these can be overriden by the user's config settings.
+        */
+       for (i = 0; i < ARRAY_SIZE(builtin_funcname_pattern); i++)
+               if (!strcmp(ident, builtin_funcname_pattern[i].name))
+                       return builtin_funcname_pattern[i].pattern;
+
+       return NULL;
 }
 
 static void builtin_diff(const char *name_a,
@@ -1182,7 +1314,8 @@ static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (!o->text && (file_is_binary(one) || file_is_binary(two))) {
+       if (!o->text &&
+           (diff_filespec_is_binary(one) || diff_filespec_is_binary(two))) {
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
@@ -1201,7 +1334,13 @@ static void builtin_diff(const char *name_a,
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
                struct emit_callback ecbdata;
+               const char *funcname_pattern;
 
+               funcname_pattern = diff_funcname_pattern(one);
+               if (!funcname_pattern)
+                       funcname_pattern = diff_funcname_pattern(two);
+
+               memset(&xecfg, 0, sizeof(xecfg));
                memset(&ecbdata, 0, sizeof(ecbdata));
                ecbdata.label_path = lbl;
                ecbdata.color_diff = o->color_diff;
@@ -1209,6 +1348,8 @@ static void builtin_diff(const char *name_a,
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.flags = XDL_EMIT_FUNCNAMES;
+               if (funcname_pattern)
+                       xdiff_set_find_func(&xecfg, funcname_pattern);
                if (!diffopts)
                        ;
                else if (!prefixcmp(diffopts, "--unified="))
@@ -1260,7 +1401,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (file_is_binary(one) || file_is_binary(two)) {
+       if (diff_filespec_is_binary(one) || diff_filespec_is_binary(two)) {
                data->is_binary = 1;
                data->added = mf2.size;
                data->deleted = mf1.size;
@@ -1270,9 +1411,8 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
 
+               memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = XDF_NEED_MINIMAL | o->xdl_opts;
-               xecfg.ctxlen = 0;
-               xecfg.flags = 0;
                ecb.outf = xdiff_outf;
                ecb.priv = diffstat;
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
@@ -1302,7 +1442,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
 
-       if (file_is_binary(two))
+       if (diff_filespec_is_binary(two))
                goto free_and_return;
        else {
                /* Crazy xdl interfaces.. */
@@ -1310,9 +1450,8 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
                xdemitconf_t xecfg;
                xdemitcb_t ecb;
 
+               memset(&xecfg, 0, sizeof(xecfg));
                xpp.flags = XDF_NEED_MINIMAL;
-               xecfg.ctxlen = 0;
-               xecfg.flags = 0;
                ecb.outf = xdiff_outf;
                ecb.priv = &data;
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
@@ -1460,7 +1599,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        if (size_only && 0 < s->size)
                return 0;
 
-       if (S_ISDIRLNK(s->mode))
+       if (S_ISGITLINK(s->mode))
                return diff_populate_gitlink(s, size_only);
 
        if (!s->sha1_valid ||
@@ -1556,7 +1695,7 @@ static void prep_temp_blob(struct diff_tempfile *temp,
 
        fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX");
        if (fd < 0)
-               die("unable to create temp-file");
+               die("unable to create temp-file: %s", strerror(errno));
        if (write_in_full(fd, blob, size) != size)
                die("unable to write temp-file");
        close(fd);
@@ -1753,10 +1892,7 @@ static const char *external_diff_attr(const char *name)
                    !ATTR_UNSET(value)) {
                        struct ll_diff_driver *drv;
 
-                       if (!user_diff_tail) {
-                               user_diff_tail = &user_diff;
-                               git_config(git_diff_ui_config);
-                       }
+                       read_config_if_needed();
                        for (drv = user_diff; drv; drv = drv->next)
                                if (!strcmp(drv->name, value))
                                        return drv->cmd;
@@ -1813,6 +1949,11 @@ static void diff_fill_sha1_info(struct diff_filespec *one)
                hashclr(one->sha1);
 }
 
+static int similarity_index(struct diff_filepair *p)
+{
+       return p->score * 100 / MAX_SCORE;
+}
+
 static void run_diff(struct diff_filepair *p, struct diff_options *o)
 {
        const char *pgm = external_diff();
@@ -1847,23 +1988,20 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
                                "similarity index %d%%\n"
                                "copy from %s\n"
                                "copy to %s\n",
-                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                               name_munged, other_munged);
+                               similarity_index(p), name_munged, other_munged);
                break;
        case DIFF_STATUS_RENAMED:
                len += snprintf(msg + len, sizeof(msg) - len,
                                "similarity index %d%%\n"
                                "rename from %s\n"
                                "rename to %s\n",
-                               (int)(0.5 + p->score * 100.0/MAX_SCORE),
-                               name_munged, other_munged);
+                               similarity_index(p), name_munged, other_munged);
                break;
        case DIFF_STATUS_MODIFIED:
                if (p->score) {
                        len += snprintf(msg + len, sizeof(msg) - len,
                                        "dissimilarity index %d%%\n",
-                                       (int)(0.5 + p->score *
-                                             100.0/MAX_SCORE));
+                                       similarity_index(p));
                        complete_rewrite = 1;
                        break;
                }
@@ -1878,8 +2016,8 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
 
                if (o->binary) {
                        mmfile_t mf;
-                       if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
-                           (!fill_mmfile(&mf, two) && file_is_binary(two)))
+                       if ((!fill_mmfile(&mf, one) && diff_filespec_is_binary(one)) ||
+                           (!fill_mmfile(&mf, two) && diff_filespec_is_binary(two)))
                                abbrev = 40;
                }
                len += snprintf(msg + len, sizeof(msg) - len,
@@ -2103,6 +2241,8 @@ static int opt_arg(const char *arg, int arg_short, const char *arg_long, int *va
        return 1;
 }
 
+static int diff_scoreopt_parse(const char *opt);
+
 int diff_opt_parse(struct diff_options *options, const char **av, int ac)
 {
        const char *arg = av[0];
@@ -2199,6 +2339,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->detect_rename = DIFF_DETECT_RENAME;
        }
        else if (!prefixcmp(arg, "-C")) {
+               if (options->detect_rename == DIFF_DETECT_COPY)
+                       options->find_copies_harder = 1;
                if ((options->rename_score =
                     diff_scoreopt_parse(arg)) == -1)
                        return -1;
@@ -2206,6 +2348,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        }
        else if (!strcmp(arg, "--find-copies-harder"))
                options->find_copies_harder = 1;
+       else if (!strcmp(arg, "--follow"))
+               options->follow_renames = 1;
        else if (!strcmp(arg, "--abbrev"))
                options->abbrev = DEFAULT_ABBREV;
        else if (!prefixcmp(arg, "--abbrev=")) {
@@ -2233,6 +2377,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->exit_with_status = 1;
        else if (!strcmp(arg, "--quiet"))
                options->quiet = 1;
+       else if (!strcmp(arg, "--ext-diff"))
+               options->allow_external = 1;
+       else if (!strcmp(arg, "--no-ext-diff"))
+               options->allow_external = 0;
        else
                return 0;
        return 1;
@@ -2274,7 +2422,7 @@ static int parse_num(const char **cp_p)
        return (int)((num >= scale) ? MAX_SCORE : (MAX_SCORE * num / scale));
 }
 
-int diff_scoreopt_parse(const char *opt)
+static int diff_scoreopt_parse(const char *opt)
 {
        int opt1, opt2, cmd;
 
@@ -2381,8 +2529,7 @@ static void diff_flush_raw(struct diff_filepair *p,
        }
 
        if (p->score)
-               sprintf(status, "%c%03d", p->status,
-                       (int)(0.5 + p->score * 100.0/MAX_SCORE));
+               sprintf(status, "%c%03d", p->status, similarity_index(p));
        else {
                status[0] = p->status;
                status[1] = 0;
@@ -2665,8 +2812,7 @@ static void show_rename_copy(const char *renamecopy, struct diff_filepair *p)
 {
        char *names = pprint_rename(p->one->path, p->two->path);
 
-       printf(" %s %s (%d%%)\n", renamecopy, names,
-              (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       printf(" %s %s (%d%%)\n", renamecopy, names, similarity_index(p));
        free(names);
        show_mode_change(p, 0);
 }
@@ -2690,7 +2836,7 @@ static void diff_summary(struct diff_filepair *p)
                if (p->score) {
                        char *name = quote_one(p->two->path);
                        printf(" rewrite %s (%d%%)\n", name,
-                               (int)(0.5 + p->score * 100.0/MAX_SCORE));
+                              similarity_index(p));
                        free(name);
                        show_mode_change(p, 0);
                } else  show_mode_change(p, 1);
@@ -2754,6 +2900,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                struct diff_filepair *p = q->queue[i];
                int len1, len2;
 
+               memset(&xecfg, 0, sizeof(xecfg));
                if (p->status == 0)
                        return error("internal diff status error");
                if (p->status == DIFF_STATUS_UNKNOWN)
@@ -2773,7 +2920,7 @@ static int diff_get_patch_id(struct diff_options *options, unsigned char *sha1)
                        return error("unable to read files to diff");
 
                /* Maybe hash p->two? into the patch id? */
-               if (file_is_binary(p->two))
+               if (diff_filespec_is_binary(p->two))
                        continue;
 
                len1 = remove_space(p->one->path, strlen(p->one->path));
@@ -2996,10 +3143,64 @@ static void diffcore_apply_filter(const char *filter)
        *q = outq;
 }
 
+static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
+{
+       int i;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_queue_struct outq;
+       outq.queue = NULL;
+       outq.nr = outq.alloc = 0;
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+
+               /*
+                * 1. Entries that come from stat info dirtyness
+                *    always have both sides (iow, not create/delete),
+                *    one side of the object name is unknown, with
+                *    the same mode and size.  Keep the ones that
+                *    do not match these criteria.  They have real
+                *    differences.
+                *
+                * 2. At this point, the file is known to be modified,
+                *    with the same mode and size, and the object
+                *    name of one side is unknown.  Need to inspect
+                *    the identical contents.
+                */
+               if (!DIFF_FILE_VALID(p->one) || /* (1) */
+                   !DIFF_FILE_VALID(p->two) ||
+                   (p->one->sha1_valid && p->two->sha1_valid) ||
+                   (p->one->mode != p->two->mode) ||
+                   diff_populate_filespec(p->one, 1) ||
+                   diff_populate_filespec(p->two, 1) ||
+                   (p->one->size != p->two->size) ||
+
+                   diff_populate_filespec(p->one, 0) || /* (2) */
+                   diff_populate_filespec(p->two, 0) ||
+                   memcmp(p->one->data, p->two->data, p->one->size))
+                       diff_q(&outq, p);
+               else {
+                       /*
+                        * The caller can subtract 1 from skip_stat_unmatch
+                        * to determine how many paths were dirty only
+                        * due to stat info mismatch.
+                        */
+                       if (!diffopt->no_index)
+                               diffopt->skip_stat_unmatch++;
+                       diff_free_filepair(p);
+               }
+       }
+       free(q->queue);
+       *q = outq;
+}
+
 void diffcore_std(struct diff_options *options)
 {
        if (options->quiet)
                return;
+
+       if (options->skip_stat_unmatch && !options->find_copies_harder)
+               diffcore_skip_stat_unmatch(options);
        if (options->break_opt != -1)
                diffcore_break(options->break_opt);
        if (options->detect_rename)
@@ -3032,7 +3233,7 @@ void diff_addremove(struct diff_options *options,
         * entries to the diff-core.  They will be prefixed
         * with something like '=' or '*' (I haven't decided
         * which but should not make any difference).
-        * Feeding the same new and old to diff_change() 
+        * Feeding the same new and old to diff_change()
         * also has the same effect.
         * Before the final output happens, they are pruned after
         * merged into rename/copy pairs as appropriate.
@@ -3059,7 +3260,7 @@ void diff_change(struct diff_options *options,
                 unsigned old_mode, unsigned new_mode,
                 const unsigned char *old_sha1,
                 const unsigned char *new_sha1,
-                const char *base, const char *path) 
+                const char *base, const char *path)
 {
        char concatpath[PATH_MAX];
        struct diff_filespec *one, *two;
diff --git a/diff.h b/diff.h
index 63738c1dd4c71cb1beacaffea40bf51377a137ea..4546aad219742e4ad878937dbd05436c93d298b9 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -55,15 +55,18 @@ struct diff_options {
                 full_index:1,
                 silent_on_remove:1,
                 find_copies_harder:1,
+                follow_renames:1,
                 color_diff:1,
                 color_diff_words:1,
                 has_changes:1,
                 quiet:1,
+                no_index:1,
                 allow_external:1,
                 exit_with_status:1;
        int context;
        int break_opt;
        int detect_rename;
+       int skip_stat_unmatch;
        int line_termination;
        int output_format;
        int pickaxe_opts;
@@ -155,8 +158,6 @@ extern void diff_unmerge(struct diff_options *,
                         unsigned mode,
                         const unsigned char *sha1);
 
-extern int diff_scoreopt_parse(const char *opt);
-
 #define DIFF_SETUP_REVERSE             1
 #define DIFF_SETUP_USE_CACHE           2
 #define DIFF_SETUP_USE_SIZE_CACHE      4
index 9c19b8cab778362b9d369135e743fb232a7cd295..ae8a7d03e2d103739897ac37fe1f9966956b9ec8 100644 (file)
@@ -66,8 +66,7 @@ static int should_break(struct diff_filespec *src,
        if (base_size < MINIMUM_BREAK_SIZE)
                return 0; /* we do not break too small filepair */
 
-       if (diffcore_count_changes(src->data, src->size,
-                                  dst->data, dst->size,
+       if (diffcore_count_changes(src, dst,
                                   NULL, NULL,
                                   0,
                                   &src_copied, &literal_added))
index 7338a40c5964ae6ddfb855465249fc1a2fa5a2a3..d9729e5ec27d2ff2bacf4ed930195f9293f8ca5a 100644 (file)
@@ -5,23 +5,20 @@
 /*
  * Idea here is very simple.
  *
- * We have total of (sz-N+1) N-byte overlapping sequences in buf whose
- * size is sz.  If the same N-byte sequence appears in both source and
- * destination, we say the byte that starts that sequence is shared
- * between them (i.e. copied from source to destination).
+ * Almost all data we are interested in are text, but sometimes we have
+ * to deal with binary data.  So we cut them into chunks delimited by
+ * LF byte, or 64-byte sequence, whichever comes first, and hash them.
  *
- * For each possible N-byte sequence, if the source buffer has more
- * instances of it than the destination buffer, that means the
- * difference are the number of bytes not copied from source to
- * destination.  If the counts are the same, everything was copied
- * from source to destination.  If the destination has more,
- * everything was copied, and destination added more.
+ * For those chunks, if the source buffer has more instances of it
+ * than the destination buffer, that means the difference are the
+ * number of bytes not copied from source to destination.  If the
+ * counts are the same, everything was copied from source to
+ * destination.  If the destination has more, everything was copied,
+ * and destination added more.
  *
  * We are doing an approximation so we do not really have to waste
  * memory by actually storing the sequence.  We just hash them into
  * somewhere around 2^16 hashbuckets and count the occurrences.
- *
- * The length of the sequence is arbitrarily set to 8 for now.
  */
 
 /* Wild guess at the initial hash size */
@@ -125,11 +122,14 @@ static struct spanhash_top *add_spanhash(struct spanhash_top *top,
        }
 }
 
-static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz)
+static struct spanhash_top *hash_chars(struct diff_filespec *one)
 {
        int i, n;
        unsigned int accum1, accum2, hashval;
        struct spanhash_top *hash;
+       unsigned char *buf = one->data;
+       unsigned int sz = one->size;
+       int is_text = !diff_filespec_is_binary(one);
 
        i = INITIAL_HASH_SIZE;
        hash = xmalloc(sizeof(*hash) + sizeof(struct spanhash) * (1<<i));
@@ -143,6 +143,11 @@ static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz)
                unsigned int c = *buf++;
                unsigned int old_1 = accum1;
                sz--;
+
+               /* Ignore CR in CRLF sequence if text */
+               if (is_text && c == '\r' && sz && *buf == '\n')
+                       continue;
+
                accum1 = (accum1 << 7) ^ (accum2 >> 25);
                accum2 = (accum2 << 7) ^ (old_1 >> 25);
                accum1 += c;
@@ -156,8 +161,8 @@ static struct spanhash_top *hash_chars(unsigned char *buf, unsigned int sz)
        return hash;
 }
 
-int diffcore_count_changes(void *src, unsigned long src_size,
-                          void *dst, unsigned long dst_size,
+int diffcore_count_changes(struct diff_filespec *src,
+                          struct diff_filespec *dst,
                           void **src_count_p,
                           void **dst_count_p,
                           unsigned long delta_limit,
@@ -172,14 +177,14 @@ int diffcore_count_changes(void *src, unsigned long src_size,
        if (src_count_p)
                src_count = *src_count_p;
        if (!src_count) {
-               src_count = hash_chars(src, src_size);
+               src_count = hash_chars(src);
                if (src_count_p)
                        *src_count_p = src_count;
        }
        if (dst_count_p)
                dst_count = *dst_count_p;
        if (!dst_count) {
-               dst_count = hash_chars(dst, dst_size);
+               dst_count = hash_chars(dst);
                if (dst_count_p)
                        *dst_count_p = dst_count;
        }
index c4a77d71da5602b68c541c2487fcbecfdfff2944..af9fffe6e8e145b066157da8791c749257e7c8e9 100644 (file)
@@ -102,7 +102,7 @@ void diffcore_pickaxe(const char *needle, int opts)
                for (i = 0; i < q->nr; i++)
                        diff_free_filepair(q->queue[i]);
        }
-       else 
+       else
                /* Showing only the filepairs that has the needle */
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
index 93c40d9e04f35897b1ac2ad578db0c0972aacbc1..6bde4396f212833cc1d411e723d5215c086e7c2d 100644 (file)
@@ -119,10 +119,26 @@ static int is_exact_match(struct diff_filespec *src,
        return 0;
 }
 
+static int basename_same(struct diff_filespec *src, struct diff_filespec *dst)
+{
+       int src_len = strlen(src->path), dst_len = strlen(dst->path);
+       while (src_len && dst_len) {
+               char c1 = src->path[--src_len];
+               char c2 = dst->path[--dst_len];
+               if (c1 != c2)
+                       return 0;
+               if (c1 == '/')
+                       return 1;
+       }
+       return (!src_len || src->path[src_len - 1] == '/') &&
+               (!dst_len || dst->path[dst_len - 1] == '/');
+}
+
 struct diff_score {
        int src; /* index in rename_src */
        int dst; /* index in rename_dst */
        int score;
+       int name_score;
 };
 
 static int estimate_similarity(struct diff_filespec *src,
@@ -174,8 +190,7 @@ static int estimate_similarity(struct diff_filespec *src,
 
        delta_limit = (unsigned long)
                (base_size * (MAX_SCORE-minimum_score) / MAX_SCORE);
-       if (diffcore_count_changes(src->data, src->size,
-                                  dst->data, dst->size,
+       if (diffcore_count_changes(src, dst,
                                   &src->cnt_data, &dst->cnt_data,
                                   delta_limit,
                                   &src_copied, &literal_added))
@@ -224,6 +239,10 @@ static void record_rename_pair(int dst_index, int src_index, int score)
 static int score_compare(const void *a_, const void *b_)
 {
        const struct diff_score *a = a_, *b = b_;
+
+       if (a->score == b->score)
+               return b->name_score - a->name_score;
+
        return b->score - a->score;
 }
 
@@ -295,9 +314,22 @@ void diffcore_rename(struct diff_options *options)
                        if (rename_dst[i].pair)
                                continue; /* dealt with an earlier round */
                        for (j = 0; j < rename_src_nr; j++) {
+                               int k;
                                struct diff_filespec *one = rename_src[j].one;
                                if (!is_exact_match(one, two, contents_too))
                                        continue;
+
+                               /* see if there is a basename match, too */
+                               for (k = j; k < rename_src_nr; k++) {
+                                       one = rename_src[k].one;
+                                       if (basename_same(one, two) &&
+                                               is_exact_match(one, two,
+                                                       contents_too)) {
+                                               j = k;
+                                               break;
+                                       }
+                               }
+
                                record_rename_pair(i, j, (int)MAX_SCORE);
                                rename_count++;
                                break; /* we are done with this entry */
@@ -329,6 +361,7 @@ void diffcore_rename(struct diff_options *options)
                        m->dst = i;
                        m->score = estimate_similarity(one, two,
                                                       minimum_score);
+                       m->name_score = basename_same(one, two);
                        diff_free_filespec_data(one);
                }
                /* We do not need the text anymore */
index 7b9294eab2c1cb9f7cb03307c90203344d97e3f6..eef17c4ca2e81c572fb110e9eb11e8ed9d51f9a0 100644 (file)
@@ -27,6 +27,7 @@ struct diff_filespec {
        char *path;
        void *data;
        void *cnt_data;
+       const char *funcname_pattern_ident;
        unsigned long size;
        int xfrm_flags;          /* for use by the xfrm */
        unsigned short mode;     /* file mode */
@@ -37,6 +38,8 @@ struct diff_filespec {
 #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)
        unsigned should_free : 1; /* data should be free()'ed */
        unsigned should_munmap : 1; /* data should be munmap()'ed */
+       unsigned checked_attr : 1;
+       unsigned is_binary : 1; /* data should be considered "binary" */
 };
 
 extern struct diff_filespec *alloc_filespec(const char *);
@@ -45,6 +48,7 @@ extern void fill_filespec(struct diff_filespec *, const unsigned char *,
 
 extern int diff_populate_filespec(struct diff_filespec *, int);
 extern void diff_free_filespec_data(struct diff_filespec *);
+extern int diff_filespec_is_binary(struct diff_filespec *);
 
 struct diff_filepair {
        struct diff_filespec *one;
@@ -103,8 +107,8 @@ void diff_debug_queue(const char *, struct diff_queue_struct *);
 #define diff_debug_queue(a,b) do {} while(0)
 #endif
 
-extern int diffcore_count_changes(void *src, unsigned long src_size,
-                                 void *dst, unsigned long dst_size,
+extern int diffcore_count_changes(struct diff_filespec *src,
+                                 struct diff_filespec *dst,
                                  void **src_count_p,
                                  void **dst_count_p,
                                  unsigned long delta_limit,
diff --git a/dir.c b/dir.c
index 11fab7f4bf9b93be33795dfd5c1a3c55c8a8079d..eb6c3abd30baefb9501c69b0e956ae1231f4b085 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -271,27 +271,34 @@ int excluded(struct dir_struct *dir, const char *pathname)
        return 0;
 }
 
-struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
-{
+static struct dir_entry *dir_entry_new(const char *pathname, int len) {
        struct dir_entry *ent;
 
-       if (cache_name_pos(pathname, len) >= 0)
-               return NULL;
-
-       if (dir->nr == dir->alloc) {
-               int alloc = alloc_nr(dir->alloc);
-               dir->alloc = alloc;
-               dir->entries = xrealloc(dir->entries, alloc*sizeof(ent));
-       }
        ent = xmalloc(sizeof(*ent) + len + 1);
-       ent->ignored = ent->ignored_dir = 0;
        ent->len = len;
        memcpy(ent->name, pathname, len);
        ent->name[len] = 0;
-       dir->entries[dir->nr++] = ent;
        return ent;
 }
 
+struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
+{
+       if (cache_name_pos(pathname, len) >= 0)
+               return NULL;
+
+       ALLOC_GROW(dir->entries, dir->nr+1, dir->alloc);
+       return dir->entries[dir->nr++] = dir_entry_new(pathname, len);
+}
+
+struct dir_entry *dir_add_ignored(struct dir_struct *dir, const char *pathname, int len)
+{
+       if (cache_name_pos(pathname, len) >= 0)
+               return NULL;
+
+       ALLOC_GROW(dir->ignored, dir->ignored_nr+1, dir->ignored_alloc);
+       return dir->ignored[dir->ignored_nr++] = dir_entry_new(pathname, len);
+}
+
 enum exist_status {
        index_nonexistent = 0,
        index_directory,
@@ -321,7 +328,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
                        break;
                if (endchar == '/')
                        return index_directory;
-               if (!endchar && S_ISDIRLNK(ntohl(ce->ce_mode)))
+               if (!endchar && S_ISGITLINK(ntohl(ce->ce_mode)))
                        return index_gitdir;
        }
        return index_nonexistent;
@@ -356,7 +363,7 @@ static enum exist_status directory_exists_in_index(const char *dirname, int len)
  *      also true and the directory is empty, in which case
  *      we just ignore it entirely.
  *  (b) if it looks like a git directory, and we don't have
- *      'no_dirlinks' set we treat it as a gitlink, and show it
+ *      'no_gitlinks' set we treat it as a gitlink, and show it
  *      as a directory.
  *  (c) otherwise, we recurse into it.
  */
@@ -383,7 +390,7 @@ static enum directory_treatment treat_directory(struct dir_struct *dir,
        case index_nonexistent:
                if (dir->show_other_directories)
                        break;
-               if (!dir->no_dirlinks) {
+               if (!dir->no_gitlinks) {
                        unsigned char sha1[20];
                        if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
                                return show_directory;
@@ -424,6 +431,18 @@ static int simplify_away(const char *path, int pathlen, const struct path_simpli
        return 0;
 }
 
+static int in_pathspec(const char *path, int len, const struct path_simplify *simplify)
+{
+       if (simplify) {
+               for (; simplify->path; simplify++) {
+                       if (len == simplify->len
+                           && !memcmp(path, simplify->path, len))
+                               return 1;
+               }
+       }
+       return 0;
+}
+
 /*
  * Read a directory tree. We currently ignore anything but
  * directories, regular files and symlinks. That's because git
@@ -464,6 +483,9 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                                continue;
 
                        exclude = excluded(dir, fullname);
+                       if (exclude && dir->collect_ignored
+                           && in_pathspec(fullname, baselen + len, simplify))
+                               dir_add_ignored(dir, fullname, baselen + len);
                        if (exclude != dir->show_ignored) {
                                if (!dir->show_ignored || DTYPE(de) != DT_DIR) {
                                        continue;
@@ -610,6 +632,7 @@ int read_directory(struct dir_struct *dir, const char *path, const char *base, i
        read_directory_recursive(dir, path, base, baselen, 0, simplify);
        free_simplify(simplify);
        qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
+       qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
        return dir->nr;
 }
 
@@ -619,3 +642,46 @@ file_exists(const char *f)
   struct stat sb;
   return stat(f, &sb) == 0;
 }
+
+/*
+ * get_relative_cwd() gets the prefix of the current working directory
+ * relative to 'dir'.  If we are not inside 'dir', it returns NULL.
+ *
+ * As a convenience, it also returns NULL if 'dir' is already NULL.  The
+ * reason for this behaviour is that it is natural for functions returning
+ * directory names to return NULL to say "this directory does not exist"
+ * or "this directory is invalid".  These cases are usually handled the
+ * same as if the cwd is not inside 'dir' at all, so get_relative_cwd()
+ * returns NULL for both of them.
+ *
+ * Most notably, get_relative_cwd(buffer, size, get_git_work_tree())
+ * unifies the handling of "outside work tree" with "no work tree at all".
+ */
+char *get_relative_cwd(char *buffer, int size, const char *dir)
+{
+       char *cwd = buffer;
+
+       if (!dir)
+               return NULL;
+       if (!getcwd(buffer, size))
+               die("can't find the current directory: %s", strerror(errno));
+
+       if (!is_absolute_path(dir))
+               dir = make_absolute_path(dir);
+
+       while (*dir && *dir == *cwd) {
+               dir++;
+               cwd++;
+       }
+       if (*dir)
+               return NULL;
+       if (*cwd == '/')
+               return cwd + 1;
+       return cwd;
+}
+
+int is_inside_dir(const char *dir)
+{
+       char buffer[PATH_MAX];
+       return get_relative_cwd(buffer, sizeof(buffer), dir) != NULL;
+}
diff --git a/dir.h b/dir.h
index 817c674da1e017cffea9dddae672f9125aca8475..f55a87b2cd5f2b4e06e14b4c1b832fc0a60ad319 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -13,9 +13,7 @@
 
 
 struct dir_entry {
-       unsigned int ignored : 1;
-       unsigned int ignored_dir : 1;
-       unsigned int len : 30;
+       unsigned int len;
        char name[FLEX_ARRAY]; /* more */
 };
 
@@ -31,11 +29,14 @@ struct exclude_list {
 
 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_dirlinks:1;
+                    no_gitlinks:1,
+                    collect_ignored:1;
        struct dir_entry **entries;
+       struct dir_entry **ignored;
 
        /* Exclude info */
        const char *exclude_per_dir;
@@ -60,4 +61,7 @@ extern void add_exclude(const char *string, const char *base,
 extern int file_exists(const char *);
 extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len);
 
+extern char *get_relative_cwd(char *buffer, int size, const char *dir);
+extern int is_inside_dir(const char *dir);
+
 #endif
diff --git a/entry.c b/entry.c
index 23687af7d5d140fa3ddc7a884acfa8a3412ed869..fc3a506ecef4ed654f26c11996df109dd135a4ed 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -54,7 +54,7 @@ static void remove_subtree(const char *path)
        struct dirent *de;
        char pathbuf[PATH_MAX];
        char *name;
-       
+
        if (!dir)
                die("cannot opendir %s (%s)", path, strerror(errno));
        strcpy(pathbuf, path);
@@ -112,6 +112,16 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                if (!new)
                        return error("git-checkout-index: unable to read sha1 file of %s (%s)",
                                path, sha1_to_hex(ce->sha1));
+
+               /*
+                * Convert from git internal format to working tree format
+                */
+               buf = convert_to_working_tree(ce->name, new, &size);
+               if (buf) {
+                       free(new);
+                       new = buf;
+               }
+
                if (to_tempfile) {
                        strcpy(path, ".merge_file_XXXXXX");
                        fd = mkstemp(path);
@@ -123,15 +133,6 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                                path, strerror(errno));
                }
 
-               /*
-                * Convert from git internal format to working tree format
-                */
-               buf = convert_to_working_tree(ce->name, new, &size);
-               if (buf) {
-                       free(new);
-                       new = buf;
-               }
-
                wrote = write_in_full(fd, new, size);
                close(fd);
                free(new);
@@ -168,7 +169,7 @@ static int write_entry(struct cache_entry *ce, char *path, const struct checkout
                                                 "symlink %s (%s)", path, strerror(errno));
                }
                break;
-       case S_IFDIRLNK:
+       case S_IFGITLINK:
                if (to_tempfile)
                        return error("git-checkout-index: cannot create temporary subproject %s", path);
                if (mkdir(path, 0777) < 0)
@@ -217,7 +218,7 @@ int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *t
                unlink(path);
                if (S_ISDIR(st.st_mode)) {
                        /* If it is a gitlink, leave it alone! */
-                       if (S_ISDIRLNK(ntohl(ce->ce_mode)))
+                       if (S_ISGITLINK(ntohl(ce->ce_mode)))
                                return 0;
                        if (!state->force)
                                return error("%s is a directory", path);
index 22316597df60648c850001e068938eaf39d57f6d..b5a6c69f7c1d214daa2556d04dd35e0976fc6e5e 100644 (file)
@@ -11,8 +11,8 @@
 
 char git_default_email[MAX_GITNAME];
 char git_default_name[MAX_GITNAME];
-int use_legacy_headers = 1;
 int trust_executable_bit = 1;
+int quote_path_fully = 1;
 int has_symlinks = 1;
 int assume_unchanged;
 int prefer_symlink_refs;
@@ -24,14 +24,22 @@ const char *git_commit_encoding;
 const char *git_log_output_encoding;
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace;
-int zlib_compression_level = Z_DEFAULT_COMPRESSION;
+int zlib_compression_level = Z_BEST_SPEED;
+int core_compression_level;
+int core_compression_seen;
 size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
 size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
 size_t delta_base_cache_limit = 16 * 1024 * 1024;
+char *pager_program;
 int pager_in_use;
 int pager_use_color = 1;
+char *editor_program;
 int auto_crlf = 0;     /* 1: both ways, -1: only when adding git objects */
 
+/* This is set by setup_git_dir_gently() and/or git_default_config() */
+char *git_work_tree_cfg;
+static const char *work_tree;
+
 static const char *git_dir;
 static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
 
@@ -59,15 +67,8 @@ static void setup_git_env(void)
 
 int is_bare_repository(void)
 {
-       const char *dir, *s;
-       if (0 <= is_bare_repository_cfg)
-               return is_bare_repository_cfg;
-
-       dir = get_git_dir();
-       if (!strcmp(dir, DEFAULT_GIT_DIR_ENVIRONMENT))
-               return 0;
-       s = strrchr(dir, '/');
-       return !s || strcmp(s + 1, DEFAULT_GIT_DIR_ENVIRONMENT);
+       /* if core.bare is not 'false', let's see if there is a work tree */
+       return is_bare_repository_cfg && !get_git_work_tree();
 }
 
 const char *get_git_dir(void)
@@ -77,6 +78,26 @@ const char *get_git_dir(void)
        return git_dir;
 }
 
+const char *get_git_work_tree(void)
+{
+       static int initialized = 0;
+       if (!initialized) {
+               work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+               /* core.bare = true overrides implicit and config work tree */
+               if (!work_tree && is_bare_repository_cfg < 1) {
+                       work_tree = git_work_tree_cfg;
+                       /* make_absolute_path also normalizes the path */
+                       if (work_tree && !is_absolute_path(work_tree))
+                               work_tree = xstrdup(make_absolute_path(git_path(work_tree)));
+               } else if (work_tree)
+                       work_tree = xstrdup(make_absolute_path(work_tree));
+               initialized = 1;
+               if (work_tree)
+                       is_bare_repository_cfg = 0;
+       }
+       return work_tree;
+}
+
 char *get_object_directory(void)
 {
        if (!git_object_dir)
@@ -105,4 +126,10 @@ char *get_graft_file(void)
        return git_graft_file;
 }
 
-
+int set_git_dir(const char *path)
+{
+       if (setenv(GIT_DIR_ENVIRONMENT, path, 1))
+               return error("Could not set GIT_DIR to '%s'", path);
+       setup_git_env();
+       return 0;
+}
index 17554f68493a8409c64b47334a94e622d47d8133..170cccdcebfd3c715c17d7d762556cd3878a5fd8 100644 (file)
@@ -26,9 +26,16 @@ Format of STDIN stream:
     lf;
   commit_msg ::= data;
 
-  file_change ::= file_clr | file_del | file_obm | file_inm;
+  file_change ::= file_clr
+    | file_del
+    | file_rnm
+    | file_cpy
+    | file_obm
+    | file_inm;
   file_clr ::= 'deleteall' lf;
   file_del ::= 'D' sp path_str lf;
+  file_rnm ::= 'R' sp path_str sp path_str lf;
+  file_cpy ::= 'C' sp path_str sp path_str lf;
   file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
   file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
     data;
@@ -622,6 +629,31 @@ static void release_tree_entry(struct tree_entry *e)
        avail_tree_entry = e;
 }
 
+static struct tree_content *dup_tree_content(struct tree_content *s)
+{
+       struct tree_content *d;
+       struct tree_entry *a, *b;
+       unsigned int i;
+
+       if (!s)
+               return NULL;
+       d = new_tree_content(s->entry_count);
+       for (i = 0; i < s->entry_count; i++) {
+               a = s->entries[i];
+               b = new_tree_entry();
+               memcpy(b, a, sizeof(*a));
+               if (a->tree && is_null_sha1(b->versions[1].sha1))
+                       b->tree = dup_tree_content(a->tree);
+               else
+                       b->tree = NULL;
+               d->entries[i] = b;
+       }
+       d->entry_count = s->entry_count;
+       d->delta_depth = s->delta_depth;
+
+       return d;
+}
+
 static void start_packfile(void)
 {
        static char tmpfile[PATH_MAX];
@@ -631,9 +663,7 @@ static void start_packfile(void)
 
        snprintf(tmpfile, sizeof(tmpfile),
                "%s/tmp_pack_XXXXXX", get_object_directory());
-       pack_fd = mkstemp(tmpfile);
-       if (pack_fd < 0)
-               die("Can't create %s: %s", tmpfile, strerror(errno));
+       pack_fd = xmkstemp(tmpfile);
        p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
        strcpy(p->pack_name, tmpfile);
        p->pack_fd = pack_fd;
@@ -695,9 +725,7 @@ static char *create_index(void)
 
        snprintf(tmpfile, sizeof(tmpfile),
                "%s/tmp_idx_XXXXXX", get_object_directory());
-       idx_fd = mkstemp(tmpfile);
-       if (idx_fd < 0)
-               die("Can't create %s: %s", tmpfile, strerror(errno));
+       idx_fd = xmkstemp(tmpfile);
        f = sha1fd(idx_fd, tmpfile);
        sha1write(f, array, 256 * sizeof(int));
        SHA1_Init(&ctx);
@@ -1154,7 +1182,8 @@ static int tree_content_set(
        struct tree_entry *root,
        const char *p,
        const unsigned char *sha1,
-       const uint16_t mode)
+       const uint16_t mode,
+       struct tree_content *subtree)
 {
        struct tree_content *t = root->tree;
        const char *slash1;
@@ -1168,20 +1197,22 @@ static int tree_content_set(
                n = strlen(p);
        if (!n)
                die("Empty path component found in input");
+       if (!slash1 && !S_ISDIR(mode) && subtree)
+               die("Non-directories cannot have subtrees");
 
        for (i = 0; i < t->entry_count; i++) {
                e = t->entries[i];
                if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
                        if (!slash1) {
-                               if (e->versions[1].mode == mode
+                               if (!S_ISDIR(mode)
+                                               && e->versions[1].mode == mode
                                                && !hashcmp(e->versions[1].sha1, sha1))
                                        return 0;
                                e->versions[1].mode = mode;
                                hashcpy(e->versions[1].sha1, sha1);
-                               if (e->tree) {
+                               if (e->tree)
                                        release_tree_content_recursive(e->tree);
-                                       e->tree = NULL;
-                               }
+                               e->tree = subtree;
                                hashclr(root->versions[1].sha1);
                                return 1;
                        }
@@ -1191,7 +1222,7 @@ static int tree_content_set(
                        }
                        if (!e->tree)
                                load_tree(e);
-                       if (tree_content_set(e, slash1 + 1, sha1, mode)) {
+                       if (tree_content_set(e, slash1 + 1, sha1, mode, subtree)) {
                                hashclr(root->versions[1].sha1);
                                return 1;
                        }
@@ -1209,9 +1240,9 @@ static int tree_content_set(
        if (slash1) {
                e->tree = new_tree_content(8);
                e->versions[1].mode = S_IFDIR;
-               tree_content_set(e, slash1 + 1, sha1, mode);
+               tree_content_set(e, slash1 + 1, sha1, mode, subtree);
        } else {
-               e->tree = NULL;
+               e->tree = subtree;
                e->versions[1].mode = mode;
                hashcpy(e->versions[1].sha1, sha1);
        }
@@ -1219,7 +1250,10 @@ static int tree_content_set(
        return 1;
 }
 
-static int tree_content_remove(struct tree_entry *root, const char *p)
+static int tree_content_remove(
+       struct tree_entry *root,
+       const char *p,
+       struct tree_entry *backup_leaf)
 {
        struct tree_content *t = root->tree;
        const char *slash1;
@@ -1239,13 +1273,14 @@ static int tree_content_remove(struct tree_entry *root, const char *p)
                                goto del_entry;
                        if (!e->tree)
                                load_tree(e);
-                       if (tree_content_remove(e, slash1 + 1)) {
+                       if (tree_content_remove(e, slash1 + 1, backup_leaf)) {
                                for (n = 0; n < e->tree->entry_count; n++) {
                                        if (e->tree->entries[n]->versions[1].mode) {
                                                hashclr(root->versions[1].sha1);
                                                return 1;
                                        }
                                }
+                               backup_leaf = NULL;
                                goto del_entry;
                        }
                        return 0;
@@ -1254,16 +1289,54 @@ static int tree_content_remove(struct tree_entry *root, const char *p)
        return 0;
 
 del_entry:
-       if (e->tree) {
+       if (backup_leaf)
+               memcpy(backup_leaf, e, sizeof(*backup_leaf));
+       else if (e->tree)
                release_tree_content_recursive(e->tree);
-               e->tree = NULL;
-       }
+       e->tree = NULL;
        e->versions[1].mode = 0;
        hashclr(e->versions[1].sha1);
        hashclr(root->versions[1].sha1);
        return 1;
 }
 
+static int tree_content_get(
+       struct tree_entry *root,
+       const char *p,
+       struct tree_entry *leaf)
+{
+       struct tree_content *t = root->tree;
+       const char *slash1;
+       unsigned int i, n;
+       struct tree_entry *e;
+
+       slash1 = strchr(p, '/');
+       if (slash1)
+               n = slash1 - p;
+       else
+               n = strlen(p);
+
+       for (i = 0; i < t->entry_count; i++) {
+               e = t->entries[i];
+               if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+                       if (!slash1) {
+                               memcpy(leaf, e, sizeof(*leaf));
+                               if (e->tree && is_null_sha1(e->versions[1].sha1))
+                                       leaf->tree = dup_tree_content(e->tree);
+                               else
+                                       leaf->tree = NULL;
+                               return 1;
+                       }
+                       if (!S_ISDIR(e->versions[1].mode))
+                               return 0;
+                       if (!e->tree)
+                               load_tree(e);
+                       return tree_content_get(e, slash1 + 1, leaf);
+               }
+       }
+       return 0;
+}
+
 static int update_branch(struct branch *b)
 {
        static const char *msg = "fast-import";
@@ -1272,7 +1345,7 @@ static int update_branch(struct branch *b)
 
        if (read_ref(b->name, old_sha1))
                hashclr(old_sha1);
-       lock = lock_any_ref_for_update(b->name, old_sha1);
+       lock = lock_any_ref_for_update(b->name, old_sha1, 0);
        if (!lock)
                return error("Unable to lock %s", b->name);
        if (!force_update && !is_null_sha1(old_sha1)) {
@@ -1629,7 +1702,7 @@ static void file_change_m(struct branch *b)
                            typename(type), command_buf.buf);
        }
 
-       tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode);
+       tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode, NULL);
        free(p_uq);
 }
 
@@ -1645,10 +1718,61 @@ static void file_change_d(struct branch *b)
                        die("Garbage after path in: %s", command_buf.buf);
                p = p_uq;
        }
-       tree_content_remove(&b->branch_tree, p);
+       tree_content_remove(&b->branch_tree, p, NULL);
        free(p_uq);
 }
 
+static void file_change_cr(struct branch *b, int rename)
+{
+       const char *s, *d;
+       char *s_uq, *d_uq;
+       const char *endp;
+       struct tree_entry leaf;
+
+       s = command_buf.buf + 2;
+       s_uq = unquote_c_style(s, &endp);
+       if (s_uq) {
+               if (*endp != ' ')
+                       die("Missing space after source: %s", command_buf.buf);
+       }
+       else {
+               endp = strchr(s, ' ');
+               if (!endp)
+                       die("Missing space after source: %s", command_buf.buf);
+               s_uq = xmalloc(endp - s + 1);
+               memcpy(s_uq, s, endp - s);
+               s_uq[endp - s] = 0;
+       }
+       s = s_uq;
+
+       endp++;
+       if (!*endp)
+               die("Missing dest: %s", command_buf.buf);
+
+       d = endp;
+       d_uq = unquote_c_style(d, &endp);
+       if (d_uq) {
+               if (*endp)
+                       die("Garbage after dest in: %s", command_buf.buf);
+               d = d_uq;
+       }
+
+       memset(&leaf, 0, sizeof(leaf));
+       if (rename)
+               tree_content_remove(&b->branch_tree, s, &leaf);
+       else
+               tree_content_get(&b->branch_tree, s, &leaf);
+       if (!leaf.versions[1].mode)
+               die("Path %s not in branch", s);
+       tree_content_set(&b->branch_tree, d,
+               leaf.versions[1].sha1,
+               leaf.versions[1].mode,
+               leaf.tree);
+
+       free(s_uq);
+       free(d_uq);
+}
+
 static void file_change_deleteall(struct branch *b)
 {
        release_tree_content_recursive(b->branch_tree.tree);
@@ -1816,6 +1940,10 @@ static void cmd_new_commit(void)
                        file_change_m(b);
                else if (!prefixcmp(command_buf.buf, "D "))
                        file_change_d(b);
+               else if (!prefixcmp(command_buf.buf, "R "))
+                       file_change_cr(b, 1);
+               else if (!prefixcmp(command_buf.buf, "C "))
+                       file_change_cr(b, 0);
                else if (!strcmp("deleteall", command_buf.buf))
                        file_change_deleteall(b);
                else
index 06f4aeced439312c43f2736a1684f9e22218899b..9c81305be5f4c1a78794c9d5397bd828ba705fcc 100644 (file)
@@ -114,7 +114,7 @@ static const unsigned char* get_rev(void)
                commit->object.flags |= POPPED;
                if (!(commit->object.flags & COMMON))
                        non_common_revs--;
-       
+
                parents = commit->parents;
 
                if (commit->object.flags & COMMON) {
@@ -733,7 +733,7 @@ int main(int argc, char **argv)
        }
        if (!dest)
                usage(fetch_pack_usage);
-       pid = git_connect(fd, dest, uploadpack);
+       pid = git_connect(fd, dest, uploadpack, verbose ? CONNECT_VERBOSE : 0);
        if (pid < 0)
                return 1;
        if (heads && nr_heads)
diff --git a/fetch.c b/fetch.c
index 6f1f420be26ac6dafec366b3dc0671fb7f4940dc..811be87a3c1e0d14d9f2b37650d56575b49caa22 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -15,7 +15,7 @@ int get_verbosely = 0;
 int get_recover = 0;
 static unsigned char current_commit_sha1[20];
 
-void pull_say(const char *fmt, const char *hex) 
+void pull_say(const char *fmt, const char *hex)
 {
        if (get_verbosely)
                fprintf(stderr, fmt, hex);
@@ -47,7 +47,7 @@ static int process_tree(struct tree *tree)
                struct object *obj = NULL;
 
                /* submodule commits are not stored in the superproject */
-               if (S_ISDIRLNK(entry.mode))
+               if (S_ISGITLINK(entry.mode))
                        continue;
                if (S_ISDIR(entry.mode)) {
                        struct tree *tree = lookup_tree(entry.sha1);
@@ -156,7 +156,7 @@ static int process(struct object *obj)
                        return 0;
                prefetch(obj->sha1);
        }
-               
+
        object_list_insert(obj, process_queue_end);
        process_queue_end = &(*process_queue_end)->next;
        return 0;
diff --git a/fixup-builtins b/fixup-builtins
new file mode 100755 (executable)
index 0000000..49e861d
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/sh
+while [ "$1" ]
+do
+       old="$1"
+       new=$(echo "$1" | sed 's/git-/git /')
+       echo "Converting '$old' to '$new'"
+       git ls-files '*.sh' | while read file
+       do
+               sed "s/\\<$old\\>/$new/g" < $file > $file.new
+               chmod --reference=$file $file.new
+               mv $file.new $file
+       done
+       shift
+done
+git update-index --refresh >& /dev/null
+exit 0
index 975777f05ed69ef78f1fd91ac512f5b92a384141..17df47b95067449f03039b8ecd7715701302fa68 100755 (executable)
@@ -7,7 +7,7 @@ struct cmdname_help
     char help[80];
 };
 
-struct cmdname_help common_cmds[] = {"
+static struct cmdname_help common_cmds[] = {"
 
 sort <<\EOF |
 add
index dc3038091dd7db7dd4bb96cf8832df789b4d127f..7921cde8cbd3b58f1d7222e9828bf85e10f363e3 100755 (executable)
@@ -3,9 +3,16 @@
 use strict;
 
 sub run_cmd_pipe {
-       my $fh = undef;
-       open($fh, '-|', @_) or die;
-       return <$fh>;
+       if ($^O eq 'MSWin32') {
+               my @invalid = grep {m/[":*]/} @_;
+               die "$^O does not support: @invalid\n" if @invalid;
+               my @args = map { m/ /o ? "\"$_\"": $_ } @_;
+               return qx{@args};
+       } else {
+               my $fh = undef;
+               open($fh, '-|', @_) or die;
+               return <$fh>;
+       }
 }
 
 my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
@@ -17,7 +24,7 @@ chomp($GIT_DIR);
 
 sub refresh {
        my $fh;
-       open $fh, '-|', qw(git update-index --refresh)
+       open $fh, 'git update-index --refresh |'
            or die;
        while (<$fh>) {
                ;# ignore 'needs update'
@@ -296,7 +303,7 @@ sub revert_cmd {
                my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
                                         map { $_->{VALUE} } @update);
                my $fh;
-               open $fh, '|-', qw(git update-index --index-info)
+               open $fh, '| git update-index --index-info'
                    or die;
                for (@lines) {
                        print $fh $_;
@@ -725,7 +732,7 @@ sub patch_update_cmd {
        if (@result) {
                my $fh;
 
-               open $fh, '|-', qw(git apply --cached);
+               open $fh, '| git apply --cached';
                for (@{$head->{TEXT}}, @result) {
                        print $fh $_;
                }
index 8b5712968ebcfe3f8fb5426e5104de2e7d2d3334..b5ed8ca15c9811ccd87b6d81a82ca301736f0b66 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -60,17 +60,17 @@ fall_back_3way () {
     mkdir "$dotest/patch-merge-tmp-dir"
 
     # First see if the patch records the index info that we can use.
-    git-apply -z --index-info "$dotest/patch" \
+    git apply -z --index-info "$dotest/patch" \
        >"$dotest/patch-merge-index-info" &&
     GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-    git-update-index -z --index-info <"$dotest/patch-merge-index-info" &&
+    git update-index -z --index-info <"$dotest/patch-merge-index-info" &&
     GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-    git-write-tree >"$dotest/patch-merge-base+" ||
+    git write-tree >"$dotest/patch-merge-base+" ||
     cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
 
     echo Using index info to reconstruct a base tree...
     if GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-       git-apply $binary --cached <"$dotest/patch"
+       git apply $binary --cached <"$dotest/patch"
     then
        mv "$dotest/patch-merge-base+" "$dotest/patch-merge-base"
        mv "$dotest/patch-merge-tmp-index" "$dotest/patch-merge-index"
@@ -80,7 +80,7 @@ It does not apply to blobs recorded in its index."
     fi
 
     test -f "$dotest/patch-merge-index" &&
-    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git-write-tree) &&
+    his_tree=$(GIT_INDEX_FILE="$dotest/patch-merge-index" git write-tree) &&
     orig_tree=$(cat "$dotest/patch-merge-base") &&
     rm -fr "$dotest"/patch-merge-* || exit 1
 
@@ -95,10 +95,7 @@ It does not apply to blobs recorded in its index."
     eval GITHEAD_$his_tree='"$SUBJECT"'
     export GITHEAD_$his_tree
     git-merge-recursive $orig_tree -- HEAD $his_tree || {
-           if test -d "$GIT_DIR/rr-cache"
-           then
-               git-rerere
-           fi
+           git rerere
            echo Failed to merge in the changes.
            exit 1
     }
@@ -106,7 +103,8 @@ It does not apply to blobs recorded in its index."
 }
 
 prec=4
-dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary= resolvemsg=
+dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary=
+resolvemsg= resume=
 git_apply_opt=
 
 while case "$#" in 0) break;; esac
@@ -198,7 +196,7 @@ else
        # Start afresh.
        mkdir -p "$dotest" || exit
 
-       git-mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
+       git mailsplit -d"$prec" -o"$dotest" -b -- "$@" > "$dotest/last" ||  {
                rm -fr "$dotest"
                exit 1
        }
@@ -216,7 +214,7 @@ fi
 
 case "$resolved" in
 '')
-       files=$(git-diff-index --cached --name-only HEAD) || exit
+       files=$(git diff-index --cached --name-only HEAD) || exit
        if [ "$files" ]; then
           echo "Dirty index: cannot apply patches (dirty: $files)" >&2
           exit 1
@@ -252,10 +250,7 @@ last=`cat "$dotest/last"`
 this=`cat "$dotest/next"`
 if test "$skip" = t
 then
-       if test -d "$GIT_DIR/rr-cache"
-       then
-               git-rerere clear
-       fi
+       git rerere clear
        this=`expr "$this" + 1`
        resume=
 fi
@@ -287,14 +282,20 @@ do
        # by the user, or the user can tell us to do so by --resolved flag.
        case "$resume" in
        '')
-               git-mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
+               git mailinfo $keep $utf8 "$dotest/msg" "$dotest/patch" \
                        <"$dotest/$msgnum" >"$dotest/info" ||
                        stop_here $this
+
+               # skip pine's internal folder data
+               grep '^Author: Mail System Internal Data$' \
+                       <"$dotest"/info >/dev/null &&
+                       go_next && continue
+
                test -s $dotest/patch || {
                        echo "Patch is empty.  Was it split wrong?"
                        stop_here $this
                }
-               git-stripspace < "$dotest/msg" > "$dotest/msg-clean"
+               git stripspace < "$dotest/msg" > "$dotest/msg-clean"
                ;;
        esac
 
@@ -347,7 +348,7 @@ do
                case "$resolved$interactive" in
                tt)
                        # This is used only for interactive view option.
-                       git-diff-index -p --cached HEAD >"$dotest/patch"
+                       git diff-index -p --cached HEAD >"$dotest/patch"
                        ;;
                esac
        esac
@@ -370,7 +371,7 @@ do
                [yY]*) action=yes ;;
                [aA]*) action=yes interactive= ;;
                [nN]*) action=skip ;;
-               [eE]*) "${VISUAL:-${EDITOR:-vi}}" "$dotest/final-commit"
+               [eE]*) git_editor "$dotest/final-commit"
                       action=again ;;
                [vV]*) action=again
                       LESS=-S ${PAGER:-less} "$dotest/patch" ;;
@@ -399,7 +400,7 @@ do
 
        case "$resolved" in
        '')
-               git-apply $git_apply_opt $binary --index "$dotest/patch"
+               git apply $git_apply_opt $binary --index "$dotest/patch"
                apply_status=$?
                ;;
        t)
@@ -408,11 +409,11 @@ do
                # trust what the user has in the index file and the
                # working tree.
                resolved=
-               git-diff-index --quiet --cached HEAD && {
+               git diff-index --quiet --cached HEAD && {
                        echo "No changes - did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                }
-               unmerged=$(git-ls-files -u)
+               unmerged=$(git ls-files -u)
                if test -n "$unmerged"
                then
                        echo "You still have unmerged paths in your index"
@@ -420,10 +421,7 @@ do
                        stop_here_user_resolve $this
                fi
                apply_status=0
-               if test -d "$GIT_DIR/rr-cache"
-               then
-                       git rerere
-               fi
+               git rerere
                ;;
        esac
 
@@ -433,7 +431,7 @@ do
                then
                    # Applying the patch to an earlier tree and merging the
                    # result may have produced the same tree as ours.
-                   git-diff-index --quiet --cached HEAD && {
+                   git diff-index --quiet --cached HEAD && {
                        echo No changes -- Patch already applied.
                        go_next
                        continue
@@ -453,12 +451,12 @@ do
                "$GIT_DIR"/hooks/pre-applypatch || stop_here $this
        fi
 
-       tree=$(git-write-tree) &&
+       tree=$(git write-tree) &&
        echo Wrote tree $tree &&
-       parent=$(git-rev-parse --verify HEAD) &&
-       commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
+       parent=$(git rev-parse --verify HEAD) &&
+       commit=$(git commit-tree $tree -p $parent <"$dotest/final-commit") &&
        echo Committed: $commit &&
-       git-update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent ||
+       git update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent ||
        stop_here $this
 
        if test -x "$GIT_DIR"/hooks/post-applypatch
diff --git a/git-applymbox.sh b/git-applymbox.sh
deleted file mode 100755 (executable)
index c18e80f..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/sh
-##
-## "dotest" is my stupid name for my patch-application script, which
-## I never got around to renaming after I tested it. We're now on the
-## second generation of scripts, still called "dotest".
-##
-## Update: Ryan Anderson finally shamed me into naming this "applymbox".
-##
-## You give it a mbox-format collection of emails, and it will try to
-## apply them to the kernel using "applypatch"
-##
-## The patch application may fail in the middle.  In which case:
-## (1) look at .dotest/patch and fix it up to apply
-## (2) re-run applymbox with -c .dotest/msg-number for the current one.
-## Pay a special attention to the commit log message if you do this and
-## use a Signoff_file, because applypatch wants to append the sign-off
-## message to msg-clean every time it is run.
-##
-## git-am is supposed to be the newer and better tool for this job.
-
-USAGE='[-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]'
-. git-sh-setup
-
-git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-keep_subject= query_apply= continue= utf8=-u resume=t
-while case "$#" in 0) break ;; esac
-do
-       case "$1" in
-       -u)     utf8=-u ;;
-       -n)     utf8=-n ;;
-       -k)     keep_subject=-k ;;
-       -q)     query_apply=t ;;
-       -c)     continue="$2"; resume=f; shift ;;
-       -m)     fall_back_3way=t ;;
-       -*)     usage ;;
-       *)      break ;;
-       esac
-       shift
-done
-
-case "$continue" in
-'')
-       rm -rf .dotest
-       mkdir .dotest
-       num_msgs=$(git-mailsplit "$1" .dotest) || exit 1
-       echo "$num_msgs patch(es) to process."
-       shift
-esac
-
-files=$(git-diff-index --cached --name-only HEAD) || exit
-if [ "$files" ]; then
-   echo "Dirty index: cannot apply patches (dirty: $files)" >&2
-   exit 1
-fi
-
-case "$query_apply" in
-t)     touch .dotest/.query_apply
-esac
-case "$fall_back_3way" in
-t)     : >.dotest/.3way
-esac
-case "$keep_subject" in
--k)    : >.dotest/.keep_subject
-esac
-
-signoff="$1"
-set x .dotest/0*
-shift
-while case "$#" in 0) break;; esac
-do
-    i="$1" 
-    case "$resume,$continue" in
-    f,$i)      resume=t;;
-    f,*)       shift
-               continue;;
-    *)
-           git-mailinfo $keep_subject $utf8 \
-               .dotest/msg .dotest/patch <$i >.dotest/info || exit 1
-           test -s .dotest/patch || {
-               echo "Patch is empty.  Was it split wrong?"
-               exit 1
-           }
-           git-stripspace < .dotest/msg > .dotest/msg-clean
-           ;;
-    esac
-    while :; # for fixing up and retry
-    do
-       git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff"
-       case "$?" in
-       0)
-               # Remove the cleanly applied one to reduce clutter.
-               rm -f .dotest/$i
-               ;;
-       2)
-               # 2 is a special exit code from applypatch to indicate that
-               # the patch wasn't applied, but continue anyway 
-               ;;
-       *)
-               ret=$?
-               if test -f .dotest/.query_apply
-               then
-                       echo >&2 "* Patch failed."
-                       echo >&2 "* You could fix it up in your editor and"
-                       echo >&2 "  retry.  If you want to do so, say yes here"
-                       echo >&2 "  AFTER fixing .dotest/patch up."
-                       echo >&2 -n "Retry [y/N]? "
-                       read yesno
-                       case "$yesno" in
-                       [Yy]*)
-                               continue ;;
-                       esac
-               fi
-               exit $ret
-       esac
-       break
-    done
-    shift
-done
-# return to pristine
-rm -fr .dotest
diff --git a/git-applypatch.sh b/git-applypatch.sh
deleted file mode 100755 (executable)
index 8df2aee..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-#!/bin/sh
-##
-## applypatch takes four file arguments, and uses those to
-## apply the unpacked patch (surprise surprise) that they
-## represent to the current tree.
-##
-## The arguments are:
-##     $1 - file with commit message
-##     $2 - file with the actual patch
-##     $3 - "info" file with Author, email and subject
-##     $4 - optional file containing signoff to add
-##
-
-USAGE='<msg> <patch> <info> [<signoff>]'
-. git-sh-setup
-
-case "$#" in 3|4) ;; *) usage ;; esac
-
-final=.dotest/final-commit
-##
-## If this file exists, we ask before applying
-##
-query_apply=.dotest/.query_apply
-
-## We do not munge the first line of the commit message too much
-## if this file exists.
-keep_subject=.dotest/.keep_subject
-
-## We do not attempt the 3-way merge fallback unless this file exists.
-fall_back_3way=.dotest/.3way
-
-MSGFILE=$1
-PATCHFILE=$2
-INFO=$3
-SIGNOFF=$4
-EDIT=${VISUAL:-${EDITOR:-vi}}
-
-export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")"
-export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")"
-export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")"
-export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")"
-
-if test '' != "$SIGNOFF"
-then
-       if test -f "$SIGNOFF"
-       then
-               SIGNOFF=`cat "$SIGNOFF"` || exit
-       elif case "$SIGNOFF" in yes | true | me | please) : ;; *) false ;; esac
-       then
-               SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
-                               s/>.*/>/
-                               s/^/Signed-off-by: /'
-               `
-       else
-               SIGNOFF=
-       fi
-       if test '' != "$SIGNOFF"
-       then
-               LAST_SIGNED_OFF_BY=`
-                       sed -ne '/^Signed-off-by: /p' "$MSGFILE" |
-                       tail -n 1
-               `
-               test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || {
-                   test '' = "$LAST_SIGNED_OFF_BY" && echo
-                   echo "$SIGNOFF"
-               } >>"$MSGFILE"
-       fi
-fi
-
-patch_header=
-test -f "$keep_subject" || patch_header='[PATCH] '
-
-{
-       echo "$patch_header$SUBJECT"
-       if test -s "$MSGFILE"
-       then
-               echo
-               cat "$MSGFILE"
-       fi
-} >"$final"
-
-interactive=yes
-test -f "$query_apply" || interactive=no
-
-while [ "$interactive" = yes ]; do
-       echo "Commit Body is:"
-       echo "--------------------------"
-       cat "$final"
-       echo "--------------------------"
-       printf "Apply? [y]es/[n]o/[e]dit/[a]ccept all "
-       read reply
-       case "$reply" in
-               y|Y) interactive=no;;
-               n|N) exit 2;;   # special value to tell dotest to keep going
-               e|E) "$EDIT" "$final";;
-               a|A) rm -f "$query_apply"
-                    interactive=no ;;
-       esac
-done
-
-if test -x "$GIT_DIR"/hooks/applypatch-msg
-then
-       "$GIT_DIR"/hooks/applypatch-msg "$final" || exit
-fi
-
-echo
-echo Applying "'$SUBJECT'"
-echo
-
-git-apply --index "$PATCHFILE" || {
-
-       # git-apply exits with status 1 when the patch does not apply,
-       # but it die()s with other failures, most notably upon corrupt
-       # patch.  In the latter case, there is no point to try applying
-       # it to another tree and do 3-way merge.
-       test $? = 1 || exit 1
-
-       test -f "$fall_back_3way" || exit 1
-
-       # Here if we know which revision the patch applies to,
-       # we create a temporary working tree and index, apply the
-       # patch, and attempt 3-way merge with the resulting tree.
-
-       O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd`
-       rm -fr .patch-merge-*
-
-       if git-apply -z --index-info "$PATCHFILE" \
-               >.patch-merge-index-info 2>/dev/null &&
-               GIT_INDEX_FILE=.patch-merge-tmp-index \
-               git-update-index -z --index-info <.patch-merge-index-info &&
-               GIT_INDEX_FILE=.patch-merge-tmp-index \
-               git-write-tree >.patch-merge-tmp-base &&
-               (
-                       mkdir .patch-merge-tmp-dir &&
-                       cd .patch-merge-tmp-dir &&
-                       GIT_INDEX_FILE="../.patch-merge-tmp-index" \
-                       GIT_OBJECT_DIRECTORY="$O_OBJECT" \
-                       git-apply $binary --index
-               ) <"$PATCHFILE"
-       then
-               echo Using index info to reconstruct a base tree...
-               mv .patch-merge-tmp-base .patch-merge-base
-               mv .patch-merge-tmp-index .patch-merge-index
-       else
-       (
-               N=10
-
-               # Otherwise, try nearby trees that can be used to apply the
-               # patch.
-               git-rev-list --max-count=$N HEAD
-
-               # or hoping the patch is against known tags...
-               git-ls-remote --tags .
-       ) |
-           while read base junk
-           do
-               # Try it if we have it as a tree.
-               git-cat-file tree "$base" >/dev/null 2>&1 || continue
-
-               rm -fr .patch-merge-tmp-* &&
-               mkdir .patch-merge-tmp-dir || break
-               (
-                       cd .patch-merge-tmp-dir &&
-                       GIT_INDEX_FILE=../.patch-merge-tmp-index &&
-                       GIT_OBJECT_DIRECTORY="$O_OBJECT" &&
-                       export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY &&
-                       git-read-tree "$base" &&
-                       git-apply --index &&
-                       mv ../.patch-merge-tmp-index ../.patch-merge-index &&
-                       echo "$base" >../.patch-merge-base
-               ) <"$PATCHFILE"  2>/dev/null && break
-           done
-       fi
-
-       test -f .patch-merge-index &&
-       his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) &&
-       orig_tree=$(cat .patch-merge-base) &&
-       rm -fr .patch-merge-* || exit 1
-
-       echo Falling back to patching base and 3-way merge using $orig_tree...
-
-       # This is not so wrong.  Depending on which base we picked,
-       # orig_tree may be wildly different from ours, but his_tree
-       # has the same set of wildly different changes in parts the
-       # patch did not touch, so resolve ends up canceling them,
-       # saying that we reverted all those changes.
-
-       if git-merge-resolve $orig_tree -- HEAD $his_tree
-       then
-               echo Done.
-       else
-               echo Failed to merge in the changes.
-               exit 1
-       fi
-}
-
-if test -x "$GIT_DIR"/hooks/pre-applypatch
-then
-       "$GIT_DIR"/hooks/pre-applypatch || exit
-fi
-
-tree=$(git-write-tree) || exit 1
-echo Wrote tree $tree
-parent=$(git-rev-parse --verify HEAD) &&
-commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1
-echo Committed: $commit
-git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit
-
-if test -x "$GIT_DIR"/hooks/post-applypatch
-then
-       "$GIT_DIR"/hooks/post-applypatch
-fi
index c1e7c1ddcbbf45995b53785a0f39314f6d499d2c..b21077206a93139fd8652d5174817e18c89ba22e 100755 (executable)
@@ -3,19 +3,19 @@
 # This tool is copyright (c) 2005, Martin Langhoff.
 # It is released under the Gnu Public License, version 2.
 #
-# The basic idea is to walk the output of tla abrowse, 
-# fetch the changesets and apply them. 
+# The basic idea is to walk the output of tla abrowse,
+# fetch the changesets and apply them.
 #
 
 =head1 Invocation
 
-    git-archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ] 
-       [ -D depth] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
+    git-archimport [ -h ] [ -v ] [ -o ] [ -a ] [ -f ] [ -T ]
+       [ -D depth] [ -t tempdir ] <archive>/<branch> [ <archive>/<branch> ]
 
 Imports a project from one or more Arch repositories. It will follow branches
 and repositories within the namespaces defined by the <archive/branch>
 parameters supplied. If it cannot find the remote branch a merge comes from
-it will just import it as a regular commit. If it can find it, it will mark it 
+it will just import it as a regular commit. If it can find it, it will mark it
 as a merge whenever possible.
 
 See man (1) git-archimport for more details.
@@ -25,14 +25,14 @@ See man (1) git-archimport for more details.
  - create tag objects instead of ref tags
  - audit shell-escaping of filenames
  - hide our private tags somewhere smarter
- - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines  
+ - find a way to make "cat *patches | patch" safe even when patchfiles are missing newlines
  - sort and apply patches by graphing ancestry relations instead of just
    relying in dates supplied in the changeset itself.
    tla ancestry-graph -m could be helpful here...
 
 =head1 Devel tricks
 
-Add print in front of the shell commands invoked via backticks. 
+Add print in front of the shell commands invoked via backticks.
 
 =head1 Devel Notes
 
@@ -126,16 +126,16 @@ sub do_abrowse {
     my $stage = shift;
     while (my ($limit, $level) = each %arch_branches) {
         next unless $level == $stage;
-        
-       open ABROWSE, "$TLA abrowse -fkD --merges $limit |" 
+
+       open ABROWSE, "$TLA abrowse -fkD --merges $limit |"
                                 or die "Problems with tla abrowse: $!";
-    
+
         my %ps        = ();         # the current one
         my $lastseen  = '';
-    
+
         while (<ABROWSE>) {
             chomp;
-            
+
             # first record padded w 8 spaces
             if (s/^\s{8}\b//) {
                 my ($id, $type) = split(m/\s+/, $_, 2);
@@ -147,13 +147,13 @@ sub do_abrowse {
                     push (@psets, \%last_ps);
                     $psets{ $last_ps{id} } = \%last_ps;
                 }
-                
+
                 my $branch = extract_versionname($id);
                 %ps = ( id => $id, branch => $branch );
                 if (%last_ps && ($last_ps{branch} eq $branch)) {
                     $ps{parent_id} = $last_ps{id};
                 }
-                
+
                 $arch_branches{$branch} = 1;
                 $lastseen = 'id';
 
@@ -166,16 +166,16 @@ sub do_abrowse {
                     $ps{type} = 't';
                     # read which revision we've tagged when we parse the log
                     $ps{tag}  = $1;
-                } else { 
+                } else {
                     warn "Unknown type $type";
                 }
 
                 $arch_branches{$branch} = 1;
                 $lastseen = 'id';
-            } elsif (s/^\s{10}//) { 
-                # 10 leading spaces or more 
+            } elsif (s/^\s{10}//) {
+                # 10 leading spaces or more
                 # indicate commit metadata
-                
+
                 # date
                 if ($lastseen eq 'id' && m/^(\d{4}-\d\d-\d\d \d\d:\d\d:\d\d)/){
                     $ps{date}   = $1;
@@ -186,12 +186,12 @@ sub do_abrowse {
                 } elsif ($lastseen eq 'merges' && s/^\s{2}//) {
                     my $id = $_;
                     push (@{$ps{merges}}, $id);
-                   
+
                     # aggressive branch finding:
                     if ($opt_D) {
                         my $branch = extract_versionname($id);
                         my $repo = extract_reponame($branch);
-                        
+
                         if (archive_reachable($repo) &&
                                 !defined $arch_branches{$branch}) {
                             $arch_branches{$branch} = $stage + 1;
@@ -208,10 +208,10 @@ sub do_abrowse {
             if (@psets && $psets[$#psets]{branch} eq $ps{branch}) {
                 $temp{parent_id} = $psets[$#psets]{id};
             }
-            push (@psets, \%temp);  
+            push (@psets, \%temp);
             $psets{ $temp{id} } = \%temp;
-        }    
-        
+        }
+
         close ABROWSE or die "$TLA abrowse failed on $limit\n";
     }
 }                               # end foreach $root
@@ -253,7 +253,7 @@ unless (-d $git_dir) { # initial import
     while (my $file = readdir(DIR)) {
         # skip non-interesting-files
         next unless -f "$ptag_dir/$file";
-   
+
         # convert first '--' to '/' from old git-archimport to use
         # as an archivename/c--b--v private tag
         if ($file !~ m!,!) {
@@ -275,7 +275,7 @@ sub extract_reponame {
     my $fq_cvbr = shift; # archivename/[[[[category]branch]version]revision]
     return (split(/\//, $fq_cvbr))[0];
 }
+
 sub extract_versionname {
     my $name = shift;
     $name =~ s/--(?:patch|version(?:fix)?|base)-\d+$//;
@@ -283,7 +283,7 @@ sub extract_versionname {
 }
 
 # convert a fully-qualified revision or version to a unique dirname:
-#   normalperson@yhbt.net-05/mpd--uclinux--1--patch-2 
+#   normalperson@yhbt.net-05/mpd--uclinux--1--patch-2
 # becomes: normalperson@yhbt.net-05,mpd--uclinux--1
 #
 # the git notion of a branch is closer to
@@ -339,7 +339,7 @@ sub git_branchname {
 
 sub process_patchset_accurate {
     my $ps = shift;
-    
+
     # switch to that branch if we're not already in that branch:
     if (-e "$git_dir/refs/heads/$ps->{branch}") {
        system('git-checkout','-f',$ps->{branch}) == 0 or die "$! $?\n";
@@ -348,7 +348,7 @@ sub process_patchset_accurate {
        my $rm = safe_pipe_capture('git-ls-files','--others','-z');
        rmtree(split(/\0/,$rm)) if $rm;
     }
-    
+
     # Apply the import/changeset/merge into the working tree
     my $dir = sync_to_ps($ps);
     # read the new log entry:
@@ -361,9 +361,9 @@ sub process_patchset_accurate {
     parselog($ps, \@commitlog);
 
     if ($ps->{id} =~ /--base-0$/ && $ps->{id} ne $psets[0]{id}) {
-        # this should work when importing continuations 
+        # this should work when importing continuations
         if ($ps->{tag} && (my $branchpoint = eval { ptag($ps->{tag}) })) {
-            
+
             # find where we are supposed to branch from
            if (! -e "$git_dir/refs/heads/$ps->{branch}") {
                system('git-branch',$ps->{branch},$branchpoint) == 0 or die "$! $?\n";
@@ -388,8 +388,8 @@ sub process_patchset_accurate {
         }
         # allow multiple bases/imports here since Arch supports cherry-picks
         # from unrelated trees
-    } 
-    
+    }
+
     # update the index with all the changes we got
     system('git-diff-files --name-only -z | '.
             'git-update-index --remove -z --stdin') == 0 or die "$! $?\n";
@@ -402,7 +402,7 @@ sub process_patchset_accurate {
 # does not handle permissions or any renames involving directories
 sub process_patchset_fast {
     my $ps = shift;
-    # 
+    #
     # create the branch if needed
     #
     if ($ps->{type} eq 'i' && !$import) {
@@ -417,9 +417,9 @@ sub process_patchset_fast {
             # new branch! we need to verify a few things
             die "Branch on a non-tag!" unless $ps->{type} eq 't';
             my $branchpoint = ptag($ps->{tag});
-            die "Tagging from unknown id unsupported: $ps->{tag}" 
+            die "Tagging from unknown id unsupported: $ps->{tag}"
                 unless $branchpoint;
-            
+
             # find where we are supposed to branch from
            if (! -e "$git_dir/refs/heads/$ps->{branch}") {
                system('git-branch',$ps->{branch},$branchpoint) == 0 or die "$! $?\n";
@@ -435,13 +435,13 @@ sub process_patchset_fast {
             }
             system('git-checkout',$ps->{branch}) == 0 or die "$! $?\n";
             return 0;
-        } 
+        }
         die $! if $?;
-    } 
+    }
 
     #
     # Apply the import/changeset/merge into the working tree
-    # 
+    #
     if ($ps->{type} eq 'i' || $ps->{type} eq 't') {
         apply_import($ps) or die $!;
         $stats{import_or_tag}++;
@@ -455,10 +455,10 @@ sub process_patchset_fast {
     # prepare update git's index, based on what arch knows
     # about the pset, resolve parents, etc
     #
-    
-    my @commitlog = safe_pipe_capture($TLA,'cat-archive-log',$ps->{id}); 
+
+    my @commitlog = safe_pipe_capture($TLA,'cat-archive-log',$ps->{id});
     die "Error in cat-archive-log: $!" if $?;
-        
+
     parselog($ps,\@commitlog);
 
     # imports don't give us good info
@@ -485,10 +485,10 @@ sub process_patchset_fast {
         if (@$ren % 2) {
             die "Odd number of entries in rename!?";
         }
-        
+
         while (@$ren) {
             my $from = shift @$ren;
-            my $to   = shift @$ren;           
+            my $to   = shift @$ren;
 
             unless (-d dirname($to)) {
                 mkpath(dirname($to)); # will die on err
@@ -529,20 +529,20 @@ if ($opt_f) {
             "Things may be a bit slow\n";
     *process_patchset = *process_patchset_accurate;
 }
-    
+
 foreach my $ps (@psets) {
     # process patchsets
     $ps->{branch} = git_branchname($ps->{id});
 
     #
-    # ensure we have a clean state 
-    # 
+    # ensure we have a clean state
+    #
     if (my $dirty = `git-diff-files`) {
         die "Unclean tree when about to process $ps->{id} " .
             " - did we fail to commit cleanly before?\n$dirty";
     }
     die $! if $?;
-    
+
     #
     # skip commits already in repo
     #
@@ -559,7 +559,7 @@ foreach my $ps (@psets) {
     my $tree = `git-write-tree`;
     die "cannot write tree $!" if $?;
     chomp $tree;
-    
+
     #
     # Who's your daddy?
     #
@@ -570,18 +570,18 @@ foreach my $ps (@psets) {
             close HEAD;
             chomp $p;
             push @par, '-p', $p;
-        } else { 
+        } else {
             if ($ps->{type} eq 's') {
                 warn "Could not find the right head for the branch $ps->{branch}";
             }
         }
     }
-    
+
     if ($ps->{merges}) {
         push @par, find_parents($ps);
     }
 
-    #    
+    #
     # Commit, tag and clean state
     #
     $ENV{TZ}                  = 'GMT';
@@ -592,14 +592,14 @@ foreach my $ps (@psets) {
     $ENV{GIT_COMMITTER_EMAIL} = $ps->{email};
     $ENV{GIT_COMMITTER_DATE}  = $ps->{date};
 
-    my $pid = open2(*READER, *WRITER,'git-commit-tree',$tree,@par) 
+    my $pid = open2(*READER, *WRITER,'git-commit-tree',$tree,@par)
         or die $!;
     print WRITER $ps->{summary},"\n\n";
     print WRITER $ps->{message},"\n";
-    
+
     # make it easy to backtrack and figure out which Arch revision this was:
     print WRITER 'git-archimport-id: ',$ps->{id},"\n";
-    
+
     close WRITER;
     my $commitid = <READER>;    # read
     chomp $commitid;
@@ -611,7 +611,7 @@ foreach my $ps (@psets) {
     }
     #
     # Update the branch
-    # 
+    #
     open  HEAD, ">","$git_dir/refs/heads/$ps->{branch}";
     print HEAD $commitid;
     close HEAD;
@@ -640,7 +640,7 @@ exit 0;
 sub sync_to_ps {
     my $ps = shift;
     my $tree_dir = $tmp.'/'.tree_dirname($ps->{id});
-    
+
     $opt_v && print "sync_to_ps($ps->{id}) method: ";
 
     if (-d $tree_dir) {
@@ -674,7 +674,7 @@ sub sync_to_ps {
         safe_pipe_capture($TLA,'get','--no-pristine',$ps->{id},$tree_dir);
         $stats{get_new}++;
     }
-   
+
     # added -I flag to rsync since we're going to fast! AIEEEEE!!!!
     system('rsync','-aI','--delete','--exclude',$git_dir,
 #               '--exclude','.arch-inventory',
@@ -691,15 +691,15 @@ sub apply_import {
     mkpath($tmp);
 
     safe_pipe_capture($TLA,'get','-s','--no-pristine',$ps->{id},"$tmp/import");
-    die "Cannot get import: $!" if $?;    
+    die "Cannot get import: $!" if $?;
     system('rsync','-aI','--delete', '--exclude',$git_dir,
                '--exclude','.arch-ids','--exclude','{arch}',
                "$tmp/import/", './');
     die "Cannot rsync import:$!" if $?;
-    
+
     rmtree("$tmp/import");
     die "Cannot remove tempdir: $!" if $?;
-    
+
 
     return 1;
 }
@@ -712,13 +712,13 @@ sub apply_cset {
     # get the changeset
     safe_pipe_capture($TLA,'get-changeset',$ps->{id},"$tmp/changeset");
     die "Cannot get changeset: $!" if $?;
-    
+
     # apply patches
     if (`find $tmp/changeset/patches -type f -name '*.patch'`) {
         # this can be sped up considerably by doing
         #    (find | xargs cat) | patch
         # but that can get mucked up by patches
-        # with missing trailing newlines or the standard 
+        # with missing trailing newlines or the standard
         # 'missing newline' flag in the patch - possibly
         # produced with an old/buggy diff.
         # slow and safe, we invoke patch once per patchfile
@@ -741,7 +741,7 @@ sub apply_cset {
 
     # bring in new files
     system('rsync','-aI','--exclude',$git_dir,
-               '--exclude','.arch-ids',
+               '--exclude','.arch-ids',
                '--exclude', '{arch}',
                "$tmp/changeset/new-files-archive/",'./');
 
@@ -789,7 +789,7 @@ sub parselog {
         removed_files => 1,
         removed_directories => 1,
     );
-    
+
     chomp (@$log);
     while ($_ = shift @$log) {
         if (/^Continuation-of:\s*(.*)/) {
@@ -828,7 +828,7 @@ sub parselog {
             }
         }
     }
-   
+
     # drop leading empty lines from the log message
     while (@$log && $log->[0] eq '') {
        shift @$log;
@@ -842,7 +842,7 @@ sub parselog {
        $ps->{summary} = $log->[0] . '...';
     }
     $ps->{message} = join("\n",@$log);
-    
+
     # skip Arch control files, unescape pika-escaped files
     foreach my $k (keys %want_headers) {
         next unless (defined $ps->{$k});
@@ -867,7 +867,7 @@ sub parselog {
 # write/read a tag
 sub tag {
     my ($tag, $commit) = @_;
+
     if ($opt_o) {
         $tag =~ s|/|--|g;
     } else {
@@ -875,7 +875,7 @@ sub tag {
        $patchname =~ s/.*--//;
         $tag = git_branchname ($tag) . '--' . $patchname;
     }
-    
+
     if ($commit) {
         open(C,">","$git_dir/refs/tags/$tag")
             or die "Cannot create tag $tag: $!\n";
@@ -902,8 +902,8 @@ sub ptag {
     my ($tag, $commit) = @_;
 
     # don't use subdirs for tags yet, it could screw up other porcelains
-    $tag =~ s|/|,|g; 
-    
+    $tag =~ s|/|,|g;
+
     my $tag_file = "$ptag_dir/$tag";
     my $tag_branch_dir = dirname($tag_file);
     mkpath($tag_branch_dir) unless (-d $tag_branch_dir);
@@ -915,7 +915,7 @@ sub ptag {
             or die "Cannot write tag $tag: $!\n";
         close(C)
             or die "Cannot write tag $tag: $!\n";
-       $rptags{$commit} = $tag 
+       $rptags{$commit} = $tag
            unless $tag =~ m/--base-0$/;
     } else {                    # read
         # if the tag isn't there, return 0
@@ -941,7 +941,7 @@ sub find_parents {
     # Identify what branches are merging into me
     # and whether we are fully merged
     # git-merge-base <headsha> <headsha> should tell
-    # me what the base of the merge should be 
+    # me what the base of the merge should be
     #
     my $ps = shift;
 
@@ -963,14 +963,14 @@ sub find_parents {
     }
 
     #
-    # foreach branch find a merge base and walk it to the 
+    # foreach branch find a merge base and walk it to the
     # head where we are, collecting the merged patchsets that
     # Arch has recorded. Keep that in @have
     # Compare that with the commits on the other branch
     # between merge-base and the tip of the branch (@need)
     # and see if we have a series of consecutive patches
     # starting from the merge base. The tip of the series
-    # of consecutive patches merged is our new parent for 
+    # of consecutive patches merged is our new parent for
     # that branch.
     #
     foreach my $branch (keys %branches) {
@@ -979,13 +979,13 @@ sub find_parents {
        next unless -e "$git_dir/refs/heads/$branch";
 
        my $mergebase = `git-merge-base $branch $ps->{branch}`;
-       if ($?) { 
-           # Don't die here, Arch supports one-way cherry-picking
-           # between branches with no common base (or any relationship
-           # at all beforehand)
-           warn "Cannot find merge base for $branch and $ps->{branch}";
-           next;
-       }
+       if ($?) {
+           # Don't die here, Arch supports one-way cherry-picking
+           # between branches with no common base (or any relationship
+           # at all beforehand)
+           warn "Cannot find merge base for $branch and $ps->{branch}";
+           next;
+       }
        chomp $mergebase;
 
        # now walk up to the mergepoint collecting what patches we have
@@ -1010,7 +1010,7 @@ sub find_parents {
        # merge what we have with what ancestors have
        %have = (%have, %ancestorshave);
 
-       # see what the remote branch has - these are the merges we 
+       # see what the remote branch has - these are the merges we
        # will want to have in a consecutive series from the mergebase
        my $otherbranchtip = git_rev_parse($branch);
        my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`;
@@ -1018,7 +1018,7 @@ sub find_parents {
        foreach my $needps (@needraw) {         # get the psets
            $needps = commitid2pset($needps);
            # git-rev-list will also
-           # list commits merged in via earlier 
+           # list commits merged in via earlier
            # merges. we are only interested in commits
            # from the branch we're looking at
            if ($branch eq $needps->{branch}) {
@@ -1054,7 +1054,7 @@ sub find_parents {
        next unless ref    $psets{$p}{merges};
        my @merges = @{$psets{$p}{merges}};
        foreach my $merge (@merges) {
-           if ($parents{$merge}) { 
+           if ($parents{$merge}) {
                delete $parents{$merge};
            }
        }
@@ -1079,10 +1079,10 @@ sub git_rev_parse {
 sub commitid2pset {
     my $commitid = shift;
     chomp $commitid;
-    my $name = $rptags{$commitid} 
+    my $name = $rptags{$commitid}
        || die "Cannot find reverse tag mapping for $commitid";
     $name =~ s|,|/|;
-    my $ps   = $psets{$name} 
+    my $ps   = $psets{$name}
        || (print Dumper(sort keys %psets)) && die "Cannot find patchset for $name";
     return $ps;
 }
@@ -1112,7 +1112,7 @@ sub archive_reachable {
     my $archive = shift;
     return 1 if $reachable{$archive};
     return 0 if $unreachable{$archive};
-    
+
     if (system "$TLA whereis-archive $archive >/dev/null") {
         if ($opt_a && (system($TLA,'register-archive',
                       "http://mirrors.sourcecontrol.net/$archive") == 0)) {
@@ -1127,4 +1127,3 @@ sub archive_reachable {
         return 1;
     }
 }
-
index 1cd456173dc386528cbbbca35e327badf97a25e2..388887a556e47fa803c1965777d52532ac233b05 100755 (executable)
@@ -56,7 +56,7 @@ bisect_start() {
        # Verify HEAD. If we were bisecting before this, reset to the
        # top-of-line master first!
        #
-       head=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD) ||
+       head=$(GIT_DIR="$GIT_DIR" git symbolic-ref HEAD) ||
        die "Bad HEAD - I need a symbolic ref"
        case "$head" in
        refs/heads/bisect)
@@ -99,7 +99,7 @@ bisect_start() {
                break
                ;;
            *)
-               rev=$(git-rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
+               rev=$(git rev-parse --verify "$arg^{commit}" 2>/dev/null) || {
                    test $has_double_dash -eq 1 &&
                        die "'$arg' does not appear to be a valid revision"
                    break
@@ -124,9 +124,9 @@ bisect_bad() {
        bisect_autostart
        case "$#" in
        0)
-               rev=$(git-rev-parse --verify HEAD) ;;
+               rev=$(git rev-parse --verify HEAD) ;;
        1)
-               rev=$(git-rev-parse --verify "$1^{commit}") ;;
+               rev=$(git rev-parse --verify "$1^{commit}") ;;
        *)
                usage ;;
        esac || exit
@@ -138,19 +138,19 @@ bisect_bad() {
 bisect_write_bad() {
        rev="$1"
        echo "$rev" >"$GIT_DIR/refs/bisect/bad"
-       echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+       echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
 }
 
 bisect_good() {
        bisect_autostart
         case "$#" in
-       0)    revs=$(git-rev-parse --verify HEAD) || exit ;;
-       *)    revs=$(git-rev-parse --revs-only --no-flags "$@") &&
+       0)    revs=$(git rev-parse --verify HEAD) || exit ;;
+       *)    revs=$(git rev-parse --revs-only --no-flags "$@") &&
                test '' != "$revs" || die "Bad rev input: $@" ;;
        esac
        for rev in $revs
        do
-               rev=$(git-rev-parse --verify "$rev^{commit}") || exit
+               rev=$(git rev-parse --verify "$rev^{commit}") || exit
                bisect_write_good "$rev"
                echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
 
@@ -161,7 +161,7 @@ bisect_good() {
 bisect_write_good() {
        rev="$1"
        echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
-       echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+       echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
 }
 
 bisect_next_check() {
@@ -211,10 +211,10 @@ bisect_next() {
        bisect_autostart
        bisect_next_check good
 
-       bad=$(git-rev-parse --verify refs/bisect/bad) &&
+       bad=$(git rev-parse --verify refs/bisect/bad) &&
        good=$(git for-each-ref --format='^%(objectname)' \
                "refs/bisect/good-*" | tr '[\012]' ' ') &&
-       eval="git-rev-list --bisect-vars $good $bad --" &&
+       eval="git rev-list --bisect-vars $good $bad --" &&
        eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
        eval=$(eval "$eval") &&
        eval "$eval" || exit
@@ -225,7 +225,7 @@ bisect_next() {
        fi
        if [ "$bisect_rev" = "$bad" ]; then
                echo "$bisect_rev is first bad commit"
-               git-diff-tree --pretty $bisect_rev
+               git diff-tree --pretty $bisect_rev
                exit 0
        fi
 
@@ -233,8 +233,8 @@ bisect_next() {
        echo "$bisect_rev" >"$GIT_DIR/refs/heads/new-bisect"
        git checkout -q new-bisect || exit
        mv "$GIT_DIR/refs/heads/new-bisect" "$GIT_DIR/refs/heads/bisect" &&
-       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD refs/heads/bisect
-       git-show-branch "$bisect_rev"
+       GIT_DIR="$GIT_DIR" git symbolic-ref HEAD refs/heads/bisect
+       git show-branch "$bisect_rev"
 }
 
 bisect_visualize() {
@@ -250,7 +250,7 @@ bisect_reset() {
           else
               branch=master
           fi ;;
-       1) git-show-ref --verify --quiet -- "refs/heads/$1" || {
+       1) git show-ref --verify --quiet -- "refs/heads/$1" || {
               echo >&2 "$1 does not seem to be a valid branch"
               exit 1
           }
@@ -288,12 +288,12 @@ bisect_replay () {
                        ;;
                good)
                        echo "$rev" >"$GIT_DIR/refs/bisect/good-$rev"
-                       echo "# good: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+                       echo "# good: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
                        echo "git-bisect good $rev" >>"$GIT_DIR/BISECT_LOG"
                        ;;
                bad)
                        echo "$rev" >"$GIT_DIR/refs/bisect/bad"
-                       echo "# bad: "$(git-show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
+                       echo "# bad: "$(git show-branch $rev) >>"$GIT_DIR/BISECT_LOG"
                        echo "git-bisect bad $rev" >>"$GIT_DIR/BISECT_LOG"
                        ;;
                *)
index 7c5ca3d62f85b78ec82f02c2045435d889c32cdd..17f43927aa7b766c1ba28150c4945fac7ff3e132 100755 (executable)
@@ -6,8 +6,8 @@ SUBDIRECTORY_OK=Sometimes
 require_work_tree
 
 old_name=HEAD
-old=$(git-rev-parse --verify $old_name 2>/dev/null)
-oldbranch=$(git-symbolic-ref $old_name 2>/dev/null)
+old=$(git rev-parse --verify $old_name 2>/dev/null)
+oldbranch=$(git symbolic-ref $old_name 2>/dev/null)
 new=
 new_name=
 force=
@@ -29,9 +29,9 @@ while [ "$#" != "0" ]; do
                shift
                [ -z "$newbranch" ] &&
                        die "git checkout: -b needs a branch name"
-               git-show-ref --verify --quiet -- "refs/heads/$newbranch" &&
+               git show-ref --verify --quiet -- "refs/heads/$newbranch" &&
                        die "git checkout: branch $newbranch already exists"
-               git-check-ref-format "heads/$newbranch" ||
+               git check-ref-format "heads/$newbranch" ||
                        die "git checkout: we do not like '$newbranch' as a branch name."
                ;;
        "-l")
@@ -57,20 +57,20 @@ while [ "$#" != "0" ]; do
                usage
                ;;
        *)
-               if rev=$(git-rev-parse --verify "$arg^0" 2>/dev/null)
+               if rev=$(git rev-parse --verify "$arg^0" 2>/dev/null)
                then
                        if [ -z "$rev" ]; then
                                echo "unknown flag $arg"
                                exit 1
                        fi
                        new_name="$arg"
-                       if git-show-ref --verify --quiet -- "refs/heads/$arg"
+                       if git show-ref --verify --quiet -- "refs/heads/$arg"
                        then
-                               rev=$(git-rev-parse --verify "refs/heads/$arg^0")
+                               rev=$(git rev-parse --verify "refs/heads/$arg^0")
                                branch="$arg"
                        fi
                        new="$rev"
-               elif rev=$(git-rev-parse --verify "$arg^{tree}" 2>/dev/null)
+               elif rev=$(git rev-parse --verify "$arg^{tree}" 2>/dev/null)
                then
                        # checking out selected paths from a tree-ish.
                        new="$rev"
@@ -129,14 +129,14 @@ Did you intend to checkout '$@' which can not be resolved as commit?"
                # from a specific tree-ish; note that this is for
                # rescuing paths and is never meant to remove what
                # is not in the named tree-ish.
-               git-ls-tree --full-name -r "$new" "$@" |
-               git-update-index --index-info || exit $?
+               git ls-tree --full-name -r "$new" "$@" |
+               git update-index --index-info || exit $?
        fi
 
        # Make sure the request is about existing paths.
-       git-ls-files --error-unmatch -- "$@" >/dev/null || exit
-       git-ls-files -- "$@" |
-       git-checkout-index -f -u --stdin
+       git ls-files --error-unmatch -- "$@" >/dev/null || exit
+       git ls-files -- "$@" |
+       git checkout-index -f -u --stdin
        exit $?
 else
        # Make sure we did not fall back on $arg^{tree} codepath
@@ -144,7 +144,7 @@ else
        # but switching branches.
        if test '' != "$new"
        then
-               git-rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
+               git rev-parse --verify "$new^{commit}" >/dev/null 2>&1 ||
                die "Cannot switch branch to a non-commit."
        fi
 fi
@@ -200,10 +200,10 @@ fi
 
 if [ "$force" ]
 then
-    git-read-tree $v --reset -u $new
+    git read-tree $v --reset -u $new
 else
-    git-update-index --refresh >/dev/null
-    merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
+    git update-index --refresh >/dev/null
+    merge_error=$(git read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
        case "$merge" in
        '')
                echo >&2 "$merge_error"
@@ -211,7 +211,7 @@ else
        esac
 
        # Match the index to the working tree, and do a three-way.
-       git diff-files --name-only | git update-index --remove --stdin &&
+       git diff-files --name-only | git update-index --remove --stdin &&
        work=`git write-tree` &&
        git read-tree $v --reset -u $new || exit
 
@@ -246,7 +246,7 @@ else
     (exit $saved_err)
 fi
 
-# 
+#
 # Switch the HEAD pointer to the new branch if we
 # checked out a branch head, and remove any potential
 # old MERGE_HEAD's (subsequent commits will clearly not
@@ -254,12 +254,13 @@ fi
 #
 if [ "$?" -eq 0 ]; then
        if [ "$newbranch" ]; then
-               git-branch $track $newbranch_log "$newbranch" "$new_name" || exit
+               git branch $track $newbranch_log "$newbranch" "$new_name" || exit
                branch="$newbranch"
        fi
        if test -n "$branch"
        then
-               GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch"
+               old_branch_name=`expr "z$oldbranch" : 'zrefs/heads/\(.*\)'`
+               GIT_DIR="$GIT_DIR" git symbolic-ref -m "checkout: moving from $old_branch_name to $branch" HEAD "refs/heads/$branch"
                if test -n "$quiet"
                then
                        true    # nothing
@@ -271,15 +272,7 @@ if [ "$?" -eq 0 ]; then
                fi
        elif test -n "$detached"
        then
-               # NEEDSWORK: we would want a command to detach the HEAD
-               # atomically, instead of this handcrafted command sequence.
-               # Perhaps:
-               #       git update-ref --detach HEAD $new
-               # or something like that...
-               #
-               git-rev-parse HEAD >"$GIT_DIR/HEAD.new" &&
-               mv "$GIT_DIR/HEAD.new" "$GIT_DIR/HEAD" &&
-               git-update-ref -m "checkout: moving to $arg" HEAD "$detached" ||
+               git update-ref --no-deref -m "checkout: moving to $arg" HEAD "$detached" ||
                        die "Cannot detach HEAD"
                if test -n "$detach_warn"
                then
index 299309d97169e6d9a52a8175dd0b8f6d54c5b369..a5cfd9f07a48b97ceebfe54e88fdcc519c3e0015 100755 (executable)
@@ -20,7 +20,7 @@ require_work_tree
 ignored=
 ignoredonly=
 cleandir=
-disabled="`git-config --bool clean.requireForce`"
+disabled="`git config --bool clean.requireForce`"
 rmf="rm -f --"
 rmrf="rm -rf --"
 rm_refuse="echo Not removing"
@@ -83,7 +83,7 @@ if [ -z "$ignored" ]; then
        fi
 fi
 
-git-ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
+git ls-files --others --directory $excl ${excl_info:+"$excl_info"} -- "$@" |
 while read -r file; do
        if [ -d "$file" -a ! -L "$file" ]; then
                if [ -z "$cleandir" ]; then
index d45618d9afe203d103cb6cdcbd66e23b0dc42932..4c9b1c9710c0fd55dfdbaf6bec6b77e0dae93562 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # Copyright (c) 2005, Linus Torvalds
 # Copyright (c) 2005, Junio C Hamano
-# 
+#
 # Clone a repository into a different directory that does not yet exist.
 
 # See git-sh-setup why.
@@ -43,7 +43,7 @@ clone_dumb_http () {
        clone_tmp="$GIT_DIR/clone-tmp" &&
        mkdir -p "$clone_tmp" || exit 1
        if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
-               "`git-config --bool http.noEPSV`" = true ]; then
+               "`git config --bool http.noEPSV`" = true ]; then
                curl_extra_args="${curl_extra_args} --disable-epsv"
        fi
        http_fetch "$1/info/refs" "$clone_tmp/refs" ||
@@ -72,11 +72,22 @@ Perhaps git-update-server-info needs to be run there?"
        rm -fr "$clone_tmp"
        http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
        rm -f "$GIT_DIR/REMOTE_HEAD"
+       if test -f "$GIT_DIR/REMOTE_HEAD"; then
+               head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+               case "$head_sha1" in
+               'ref: refs/'*)
+                       ;;
+               *)
+                       git-http-fetch $v -a "$head_sha1" "$1" ||
+                       rm -f "$GIT_DIR/REMOTE_HEAD"
+                       ;;
+               esac
+       fi
 }
 
 quiet=
 local=no
-use_local=no
+use_local_hardlink=yes
 local_shared=no
 unset template
 no_checkout=
@@ -97,9 +108,13 @@ while
          no_checkout=yes ;;
        *,--na|*,--nak|*,--nake|*,--naked|\
        *,-b|*,--b|*,--ba|*,--bar|*,--bare) bare=yes ;;
-       *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local) use_local=yes ;;
-        *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared) 
-          local_shared=yes; use_local=yes ;;
+       *,-l|*,--l|*,--lo|*,--loc|*,--loca|*,--local)
+         use_local_hardlink=yes ;;
+       *,--no-h|*,--no-ha|*,--no-har|*,--no-hard|*,--no-hardl|\
+       *,--no-hardli|*,--no-hardlin|*,--no-hardlink|*,--no-hardlinks)
+         use_local_hardlink=no ;;
+        *,-s|*,--s|*,--sh|*,--sha|*,--shar|*,--share|*,--shared)
+          local_shared=yes; ;;
        1,--template) usage ;;
        *,--template)
                shift; template="--template=$1" ;;
@@ -121,7 +136,7 @@ while
                */*)
                    die "'$2' is not suitable for an origin name"
                esac
-               git-check-ref-format "heads/$2" ||
+               git check-ref-format "heads/$2" ||
                    die "'$2' is not suitable for a branch name"
                test -z "$origin_override" ||
                    die "Do not give more than one --origin options."
@@ -176,15 +191,31 @@ dir="$2"
 # Try using "humanish" part of source repo if user didn't specify one
 [ -z "$dir" ] && dir=$(echo "$repo" | sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
 [ -e "$dir" ] && die "destination directory '$dir' already exists."
-mkdir -p "$dir" &&
-D=$(cd "$dir" && pwd) &&
-trap 'err=$?; cd ..; rm -rf "$D"; exit $err' 0
-case "$bare" in
-yes)
-       GIT_DIR="$D" ;;
-*)
-       GIT_DIR="$D/.git" ;;
-esac && export GIT_DIR && git-init ${template+"$template"} || usage
+[ yes = "$bare" ] && unset GIT_WORK_TREE
+[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
+die "working tree '$GIT_WORK_TREE' already exists."
+D=
+W=
+cleanup() {
+       err=$?
+       test -z "$D" && rm -rf "$dir"
+       test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
+       cd ..
+       test -n "$D" && rm -rf "$D"
+       test -n "$W" && rm -rf "$W"
+       exit $err
+}
+trap cleanup 0
+mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
+test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
+W=$(cd "$GIT_WORK_TREE" && pwd) && export GIT_WORK_TREE="$W"
+if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
+       GIT_DIR="$D"
+else
+       GIT_DIR="$D/.git"
+fi &&
+export GIT_DIR &&
+git-init $quiet ${template+"$template"} || usage
 
 if test -n "$reference"
 then
@@ -222,34 +253,36 @@ fi
 rm -f "$GIT_DIR/CLONE_HEAD"
 
 # We do local magic only when the user tells us to.
-case "$local,$use_local" in
-yes,yes)
+case "$local" in
+yes)
        ( cd "$repo/objects" ) ||
-               die "-l flag seen but repository '$repo' is not local."
-
-       case "$local_shared" in
-       no)
-           # See if we can hardlink and drop "l" if not.
-           sample_file=$(cd "$repo" && \
-                         find objects -type f -print | sed -e 1q)
-
-           # objects directory should not be empty since we are cloning!
-           test -f "$repo/$sample_file" || exit
+               die "cannot chdir to local '$repo/objects'."
 
-           l=
-           if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
-           then
-                   l=l
-           fi &&
-           rm -f "$GIT_DIR/objects/sample" &&
-           cd "$repo" &&
-           find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
-           ;;
-       yes)
-           mkdir -p "$GIT_DIR/objects/info"
-           echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates"
-           ;;
-       esac
+       if test "$local_shared" = yes
+       then
+               mkdir -p "$GIT_DIR/objects/info"
+               echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
+       else
+               l= &&
+               if test "$use_local_hardlink" = yes
+               then
+                       # See if we can hardlink and drop "l" if not.
+                       sample_file=$(cd "$repo" && \
+                                     find objects -type f -print | sed -e 1q)
+                       # objects directory should not be empty because
+                       # we are cloning!
+                       test -f "$repo/$sample_file" || exit
+                       if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
+                       then
+                               rm -f "$GIT_DIR/objects/sample"
+                               l=l
+                       else
+                               echo >&2 "Warning: -l asked but cannot hardlink to $repo"
+                       fi
+               fi &&
+               cd "$repo" &&
+               find objects -depth -print | cpio -pumd$l "$GIT_DIR/" || exit 1
+       fi
        git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
        ;;
 *)
@@ -334,11 +367,15 @@ then
                *)
                        continue ;;
                esac
-               git-update-ref -m "clone: from $repo" "$destname" "$sha1" ""
+               git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
        done < "$GIT_DIR/CLONE_HEAD"
 fi
 
-cd "$D" || exit
+if test -n "$W"; then
+       cd "$W" || exit
+else
+       cd "$D" || exit
+fi
 
 if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD"
 then
@@ -377,37 +414,42 @@ then
                )
        )
 
+       # Upstream URL
+       git config remote."$origin".url "$repo" &&
+
+       # Set up the mappings to track the remote branches.
+       git config remote."$origin".fetch \
+               "+refs/heads/*:$remote_top/*" '^$' &&
+
        # Write out remote.$origin config, and update our "$head_points_at".
        case "$head_points_at" in
        ?*)
                # Local default branch
-               git-symbolic-ref HEAD "refs/heads/$head_points_at" &&
+               git symbolic-ref HEAD "refs/heads/$head_points_at" &&
 
                # Tracking branch for the primary branch at the remote.
-               origin_track="$remote_top/$head_points_at" &&
-               git-update-ref HEAD "$head_sha1" &&
+               git update-ref HEAD "$head_sha1" &&
 
-               # Upstream URL
-               git-config remote."$origin".url "$repo" &&
-
-               # Set up the mappings to track the remote branches.
-               git-config remote."$origin".fetch \
-                       "+refs/heads/*:$remote_top/*" '^$' &&
                rm -f "refs/remotes/$origin/HEAD"
-               git-symbolic-ref "refs/remotes/$origin/HEAD" \
+               git symbolic-ref "refs/remotes/$origin/HEAD" \
                        "refs/remotes/$origin/$head_points_at" &&
 
-               git-config branch."$head_points_at".remote "$origin" &&
-               git-config branch."$head_points_at".merge "refs/heads/$head_points_at"
+               git config branch."$head_points_at".remote "$origin" &&
+               git config branch."$head_points_at".merge "refs/heads/$head_points_at"
+               ;;
+       '')
+               # Source had detached HEAD pointing nowhere
+               git update-ref --no-deref HEAD "$head_sha1" &&
+               rm -f "refs/remotes/$origin/HEAD"
+               ;;
        esac
 
        case "$no_checkout" in
        '')
                test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
-               git-read-tree -m -u $v HEAD HEAD
+               git read-tree -m -u $v HEAD HEAD
        esac
 fi
 rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
 
 trap - 0
-
index e8b60f70497c71bd66b96115b9bba78cee523a8c..d7e7028c15aeceaf2ba64a82ee9fc15f54b20eaa 100755 (executable)
@@ -3,12 +3,12 @@
 # Copyright (c) 2005 Linus Torvalds
 # Copyright (c) 2006 Junio C Hamano
 
-USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'
+USAGE='[-a | --interactive] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [--template <file>] [[-i | -o] <path>...]'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
 require_work_tree
 
-git-rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
+git rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
 
 case "$0" in
 *status)
@@ -53,7 +53,7 @@ run_status () {
        t) color= ;;
        *) color=--nocolor ;;
        esac
-       git-runstatus ${color} \
+       git runstatus ${color} \
                ${verbose:+--verbose} \
                ${amend:+--amend} \
                ${untracked_files:+--untracked}
@@ -87,6 +87,7 @@ signoff=
 force_author=
 only_include_assumed=
 untracked_files=
+templatefile="`git config commit.template`"
 while case "$#" in 0) break;; esac
 do
        case "$1" in
@@ -189,7 +190,6 @@ $1"
                ;;
        --a|--am|--ame|--amen|--amend)
                amend=t
-               log_given=t$log_given
                use_commit=HEAD
                shift
                ;;
@@ -248,6 +248,13 @@ $1"
                signoff=t
                shift
                ;;
+       -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
+               case "$#" in 1) usage ;; esac
+               shift
+               templatefile="$1"
+               no_edit=
+               shift
+               ;;
        -q|--q|--qu|--qui|--quie|--quiet)
                quiet=t
                shift
@@ -290,9 +297,9 @@ esac
 
 case "$log_given" in
 tt*)
-       die "Only one of -c/-C/-F/--amend can be used." ;;
+       die "Only one of -c/-C/-F can be used." ;;
 *tm*|*mt*)
-       die "Option -m cannot be combined with -c/-C/-F/--amend." ;;
+       die "Option -m cannot be combined with -c/-C/-F." ;;
 esac
 
 case "$#,$also,$only,$amend" in
@@ -321,6 +328,14 @@ t,,[1-9]*)
        die "No paths with -i does not make sense." ;;
 esac
 
+if test ! -z "$templatefile" -a -z "$log_given"
+then
+       if test ! -f "$templatefile"
+       then
+               die "Commit template file does not exist."
+       fi
+fi
+
 ################################################################
 # Prepare index to have a tree to be committed
 
@@ -335,20 +350,20 @@ t,)
                cd_to_toplevel &&
                GIT_INDEX_FILE="$NEXT_INDEX" &&
                export GIT_INDEX_FILE &&
-               git-diff-files --name-only -z |
-               git-update-index --remove -z --stdin
+               git diff-files --name-only -z |
+               git update-index --remove -z --stdin
        ) || exit
        ;;
 ,t)
        save_index &&
-       git-ls-files --error-unmatch -- "$@" >/dev/null || exit
+       git ls-files --error-unmatch -- "$@" >/dev/null || exit
 
-       git-diff-files --name-only -z -- "$@"  |
+       git diff-files --name-only -z -- "$@"  |
        (
                cd_to_toplevel &&
                GIT_INDEX_FILE="$NEXT_INDEX" &&
                export GIT_INDEX_FILE &&
-               git-update-index --remove -z --stdin
+               git update-index --remove -z --stdin
        ) || exit
        ;;
 ,)
@@ -364,28 +379,28 @@ t,)
                        refuse_partial "Cannot do a partial commit during a merge."
                fi
                TMP_INDEX="$GIT_DIR/tmp-index$$"
-               commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
+               commit_only=`git ls-files --error-unmatch -- "$@"` || exit
 
                # Build a temporary index and update the real index
                # the same way.
                if test -z "$initial_commit"
                then
                        GIT_INDEX_FILE="$THIS_INDEX" \
-                       git-read-tree --index-output="$TMP_INDEX" -i -m HEAD
+                       git read-tree --index-output="$TMP_INDEX" -i -m HEAD
                else
                        rm -f "$TMP_INDEX"
                fi || exit
 
                printf '%s\n' "$commit_only" |
                GIT_INDEX_FILE="$TMP_INDEX" \
-               git-update-index --add --remove --stdin &&
+               git update-index --add --remove --stdin &&
 
                save_index &&
                printf '%s\n' "$commit_only" |
                (
                        GIT_INDEX_FILE="$NEXT_INDEX"
                        export GIT_INDEX_FILE
-                       git-update-index --remove --stdin
+                       git update-index --remove --stdin
                ) || exit
                ;;
        esac
@@ -408,12 +423,12 @@ case "$status_only" in
 t)
        # This will silently fail in a read-only repository, which is
        # what we want.
-       GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --unmerged --refresh
+       GIT_INDEX_FILE="$USE_INDEX" git update-index -q --unmerged --refresh
        run_status
        exit $?
        ;;
 '')
-       GIT_INDEX_FILE="$USE_INDEX" git-update-index -q --refresh || exit
+       GIT_INDEX_FILE="$USE_INDEX" git update-index -q --refresh || exit
        ;;
 esac
 
@@ -454,20 +469,25 @@ then
 elif test -f "$GIT_DIR/SQUASH_MSG"
 then
        cat "$GIT_DIR/SQUASH_MSG"
-fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
+elif test "$templatefile" != ""
+then
+       cat "$templatefile"
+fi | git stripspace >"$GIT_DIR"/COMMIT_EDITMSG
 
 case "$signoff" in
 t)
-       need_blank_before_signoff=
+       sign=$(git-var GIT_COMMITTER_IDENT | sed -e '
+               s/>.*/>/
+               s/^/Signed-off-by: /
+               ')
+       blank_before_signoff=
        tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
-       grep 'Signed-off-by:' >/dev/null || need_blank_before_signoff=yes
-       {
-               test -z "$need_blank_before_signoff" || echo
-               git-var GIT_COMMITTER_IDENT | sed -e '
-                       s/>.*/>/
-                       s/^/Signed-off-by: /
-               '
-       } >>"$GIT_DIR"/COMMIT_EDITMSG
+       grep 'Signed-off-by:' >/dev/null || blank_before_signoff='
+'
+       tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+       grep "$sign"$ >/dev/null ||
+       printf '%s%s\n' "$blank_before_signoff" "$sign" \
+               >>"$GIT_DIR"/COMMIT_EDITMSG
        ;;
 esac
 
@@ -483,34 +503,8 @@ fi >>"$GIT_DIR"/COMMIT_EDITMSG
 # Author
 if test '' != "$use_commit"
 then
-       pick_author_script='
-       /^author /{
-               s/'\''/'\''\\'\'\''/g
-               h
-               s/^author \([^<]*\) <[^>]*> .*$/\1/
-               s/'\''/'\''\'\'\''/g
-               s/.*/GIT_AUTHOR_NAME='\''&'\''/p
-
-               g
-               s/^author [^<]* <\([^>]*\)> .*$/\1/
-               s/'\''/'\''\'\'\''/g
-               s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
-
-               g
-               s/^author [^<]* <[^>]*> \(.*\)$/\1/
-               s/'\''/'\''\'\'\''/g
-               s/.*/GIT_AUTHOR_DATE='\''&'\''/p
-
-               q
-       }
-       '
-       encoding=$(git config i18n.commitencoding || echo UTF-8)
-       set_author_env=`git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
-       LANG=C LC_ALL=C sed -ne "$pick_author_script"`
-       eval "$set_author_env"
-       export GIT_AUTHOR_NAME
-       export GIT_AUTHOR_EMAIL
-       export GIT_AUTHOR_DATE
+       eval "$(get_author_ident_from_commit "$use_commit")"
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
 fi
 if test '' != "$force_author"
 then
@@ -531,12 +525,12 @@ then
                PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
        elif test -n "$amend"; then
                rloga='commit (amend)'
-               PARENTS=$(git-cat-file commit HEAD |
+               PARENTS=$(git cat-file commit HEAD |
                        sed -n -e '/^$/q' -e 's/^parent /-p /p')
        fi
-       current="$(git-rev-parse --verify HEAD)"
+       current="$(git rev-parse --verify HEAD)"
 else
-       if [ -z "$(git-ls-files)" ]; then
+       if [ -z "$(git ls-files)" ]; then
                echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
                exit 1
        fi
@@ -557,7 +551,7 @@ then
        } >>"$GIT_DIR"/COMMIT_EDITMSG
 else
        # we need to check if there is anything to commit
-       run_status >/dev/null 
+       run_status >/dev/null
 fi
 if [ "$?" != "0" -a ! -f "$GIT_DIR/MERGE_HEAD" -a -z "$amend" ]
 then
@@ -568,18 +562,9 @@ fi
 
 case "$no_edit" in
 '')
-       case "${VISUAL:-$EDITOR},$TERM" in
-       ,dumb)
-               echo >&2 "Terminal is dumb but no VISUAL nor EDITOR defined."
-               echo >&2 "Please supply the commit log message using either"
-               echo >&2 "-m or -F option.  A boilerplate log message has"
-               echo >&2 "been prepared in $GIT_DIR/COMMIT_EDITMSG"
-               exit 1
-               ;;
-       esac
        git-var GIT_AUTHOR_IDENT > /dev/null  || die
        git-var GIT_COMMITTER_IDENT > /dev/null  || die
-       ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/COMMIT_EDITMSG"
+       git_editor "$GIT_DIR/COMMIT_EDITMSG"
        ;;
 esac
 
@@ -603,23 +588,48 @@ then
 else
     cat "$GIT_DIR"/COMMIT_EDITMSG
 fi |
-git-stripspace >"$GIT_DIR"/COMMIT_MSG
+git stripspace >"$GIT_DIR"/COMMIT_MSG
+
+# Test whether the commit message has any content we didn't supply.
+have_commitmsg=
+grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
+       git stripspace > "$GIT_DIR"/COMMIT_BAREMSG
+
+# Is the commit message totally empty?
+if test -s "$GIT_DIR"/COMMIT_BAREMSG
+then
+       if test "$templatefile" != ""
+       then
+               # Test whether this is just the unaltered template.
+               if cnt=`sed -e '/^#/d' < "$templatefile" |
+                       git stripspace |
+                       diff "$GIT_DIR"/COMMIT_BAREMSG - |
+                       wc -l` &&
+                  test 0 -lt $cnt
+               then
+                       have_commitmsg=t
+               fi
+       else
+               # No template, so the content in the commit message must
+               # have come from the user.
+               have_commitmsg=t
+       fi
+fi
+
+rm -f "$GIT_DIR"/COMMIT_BAREMSG
 
-if cnt=`grep -v -i '^Signed-off-by' "$GIT_DIR"/COMMIT_MSG |
-       git-stripspace |
-       wc -l` &&
-   test 0 -lt $cnt
+if test "$have_commitmsg" = "t"
 then
        if test -z "$TMP_INDEX"
        then
-               tree=$(GIT_INDEX_FILE="$USE_INDEX" git-write-tree)
+               tree=$(GIT_INDEX_FILE="$USE_INDEX" git write-tree)
        else
-               tree=$(GIT_INDEX_FILE="$TMP_INDEX" git-write-tree) &&
+               tree=$(GIT_INDEX_FILE="$TMP_INDEX" git write-tree) &&
                rm -f "$TMP_INDEX"
        fi &&
-       commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
+       commit=$(git commit-tree $tree $PARENTS <"$GIT_DIR/COMMIT_MSG") &&
        rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
-       git-update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
+       git update-ref -m "$GIT_REFLOG_ACTION: $rlogm" HEAD $commit "$current" &&
        rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" &&
        if test -f "$NEXT_INDEX"
        then
@@ -636,10 +646,7 @@ rm -f "$GIT_DIR/COMMIT_MSG" "$GIT_DIR/COMMIT_EDITMSG" "$GIT_DIR/SQUASH_MSG"
 
 cd_to_toplevel
 
-if test -d "$GIT_DIR/rr-cache"
-then
-       git-rerere
-fi
+git rerere
 
 if test "$ret" = 0
 then
@@ -649,7 +656,7 @@ then
        fi
        if test -z "$quiet"
        then
-               commit=`git-diff-tree --always --shortstat --pretty="format:%h: %s"\
+               commit=`git diff-tree --always --shortstat --pretty="format:%h: %s"\
                       --summary --root HEAD --`
                echo "Created${initial_commit:+ initial} commit $commit"
        fi
index b2ab3f82567d54607a07f4061153adae86854fa9..ca0a597a282328bff9e0e29fd3653dbd656489fb 100644 (file)
@@ -287,6 +287,32 @@ static inline ssize_t xwrite(int fd, const void *buf, size_t len)
        }
 }
 
+static inline int xdup(int fd)
+{
+       int ret = dup(fd);
+       if (ret < 0)
+               die("dup failed: %s", strerror(errno));
+       return ret;
+}
+
+static inline FILE *xfdopen(int fd, const char *mode)
+{
+       FILE *stream = fdopen(fd, mode);
+       if (stream == NULL)
+               die("Out of memory? fdopen failed: %s", strerror(errno));
+       return stream;
+}
+
+static inline int xmkstemp(char *template)
+{
+       int fd;
+
+       fd = mkstemp(template);
+       if (fd < 0)
+               die("Unable to create temporary file: %s", strerror(errno));
+       return fd;
+}
+
 static inline size_t xsize_t(off_t len)
 {
        return (size_t)len;
index d6ae99b8c00c278f6e4b0190dfb892b857f27103..a33fa8d4c86da54a4d741cf921d1cf7b2138d186 100755 (executable)
@@ -15,9 +15,9 @@ unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){
     die "GIT_DIR is not defined or is unreadable";
 }
 
-our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d);
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m, $opt_d, $opt_u);
 
-getopts('hPpvcfam:d:');
+getopts('uhPpvcfam:d:');
 
 $opt_h && usage();
 
@@ -178,6 +178,10 @@ foreach my $f (@files) {
 
 my %cvsstat;
 if (@canstatusfiles) {
+    if ($opt_u) {
+      my @updated = safe_pipe_capture(@cvs, 'update', @canstatusfiles);
+      print @updated;
+    }
     my @cvsoutput;
     @cvsoutput= safe_pipe_capture(@cvs, 'status', @canstatusfiles);
     my $matchcount = 0;
@@ -193,7 +197,7 @@ if (@canstatusfiles) {
 # ... validate new files,
 foreach my $f (@afiles) {
     if (defined ($cvsstat{$f}) and $cvsstat{$f} ne "Unknown") {
-       $dirty = 1;
+       $dirty = 1;
        warn "File $f is already known in your CVS checkout -- perhaps it has been added by another user. Or this may indicate that it exists on a different branch. If this is the case, use -f to force the merge.\n";
        warn "Status was: $cvsstat{$f}\n";
     }
@@ -277,6 +281,11 @@ if ($opt_c) {
 # clean up
 unlink(".cvsexportcommit.diff");
 
+# CVS version 1.11.x and 1.12.x sleeps the wrong way to ensure the timestamp
+# used by CVS and the one set by subsequence file modifications are different.
+# If they are not different CVS will not detect changes.
+sleep(1);
+
 sub usage {
        print STDERR <<END;
 Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
index 4e6c9c6cc7c1fbbf69c455d6095cc8d18c81de16..ba23eb8eebdb841139062d4221c87515b96d8729 100755 (executable)
@@ -29,7 +29,7 @@ use IPC::Open2;
 $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
-our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L, $opt_a);
+our ($opt_h,$opt_o,$opt_v,$opt_k,$opt_u,$opt_d,$opt_p,$opt_C,$opt_z,$opt_i,$opt_P, $opt_s,$opt_m,$opt_M,$opt_A,$opt_S,$opt_L, $opt_a, $opt_r);
 my (%conv_author_name, %conv_author_email);
 
 sub usage(;$) {
@@ -40,7 +40,7 @@ Usage: ${\basename $0}     # fetch/update GIT from CVS
        [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
        [-p opts-for-cvsps] [-P file] [-C GIT_repository] [-z fuzz] [-i] [-k]
        [-u] [-s subst] [-a] [-m] [-M regex] [-S regex] [-L commitlimit]
-       [CVS_module]
+       [-r remote] [CVS_module]
 END
        exit(1);
 }
@@ -114,7 +114,7 @@ sub read_repo_config {
     }
 }
 
-my $opts = "haivmkuo:d:p:C:z:s:M:P:A:S:L:";
+my $opts = "haivmkuo:d:p:r:C:z:s:M:P:A:S:L:";
 read_repo_config($opts);
 getopts($opts) or usage();
 usage if $opt_h;
@@ -134,18 +134,26 @@ if ($opt_d) {
 } else {
        usage("CVSROOT needs to be set");
 }
-$opt_o ||= "origin";
 $opt_s ||= "-";
 $opt_a ||= 0;
 
 my $git_tree = $opt_C;
 $git_tree ||= ".";
 
+my $remote;
+if (defined $opt_r) {
+       $remote = 'refs/remotes/' . $opt_r;
+       $opt_o ||= "master";
+} else {
+       $opt_o ||= "origin";
+       $remote = 'refs/heads';
+}
+
 my $cvs_tree;
 if ($#ARGV == 0) {
        $cvs_tree = $ARGV[0];
 } elsif (-f 'CVS/Repository') {
-       open my $f, '<', 'CVS/Repository' or 
+       open my $f, '<', 'CVS/Repository' or
            die 'Failed to open CVS/Repository';
        $cvs_tree = <$f>;
        chomp $cvs_tree;
@@ -434,7 +442,7 @@ sub file {
        my ($self,$fn,$rev) = @_;
        my $res;
 
-       my ($fh, $name) = tempfile('gitcvs.XXXXXX', 
+       my ($fh, $name) = tempfile('gitcvs.XXXXXX',
                    DIR => File::Spec->tmpdir(), UNLINK => 1);
 
        $self->_file($fn,$rev) and $res = $self->_line($fh);
@@ -520,9 +528,9 @@ sub is_sha1 {
 
 sub get_headref ($$) {
     my $name    = shift;
-    my $git_dir = shift; 
-    
-    my $f = "$git_dir/refs/heads/$name";
+    my $git_dir = shift;
+
+    my $f = "$git_dir/$remote/$name";
     if (open(my $fh, $f)) {
            chomp(my $r = <$fh>);
            is_sha1($r) or die "Cannot get head id for $name ($r): $!";
@@ -573,12 +581,12 @@ unless (-d $git_dir) {
 
        # Get the last import timestamps
        my $fmt = '($ref, $author) = (%(refname), %(author));';
-       open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
+       open(H, "git-for-each-ref --perl --format='$fmt' $remote |") or
                die "Cannot run git-for-each-ref: $!\n";
        while (defined(my $entry = <H>)) {
                my ($ref, $author);
                eval($entry) || die "cannot eval refs list: $@";
-               my ($head) = ($ref =~ m|^refs/heads/(.*)|);
+               my ($head) = ($ref =~ m|^$remote/(.*)|);
                $author =~ /^.*\s(\d+)\s[-+]\d{4}$/;
                $branch_date{$head} = $1;
        }
@@ -701,9 +709,9 @@ sub commit {
                $index{$branch} = tmpnam();
                $ENV{GIT_INDEX_FILE} = $index{$branch};
                if ($ancestor) {
-                   system("git-read-tree", $ancestor);
+                   system("git-read-tree", "$remote/$ancestor");
                } else {
-                   system("git-read-tree", $branch);
+                   system("git-read-tree", "$remote/$branch");
                }
                die "read-tree failed: $?\n" if $?;
            }
@@ -762,40 +770,17 @@ sub commit {
        waitpid($pid,0);
        die "Error running git-commit-tree: $?\n" if $?;
 
-       system("git-update-ref refs/heads/$branch $cid") == 0
+       system("git-update-ref $remote/$branch $cid") == 0
                or die "Cannot write branch $branch for update: $!\n";
 
        if ($tag) {
-               my ($in, $out) = ('','');
                my ($xtag) = $tag;
                $xtag =~ s/\s+\*\*.*$//; # Remove stuff like ** INVALID ** and ** FUNKY **
                $xtag =~ tr/_/\./ if ( $opt_u );
                $xtag =~ s/[\/]/$opt_s/g;
-               
-               my $pid = open2($in, $out, 'git-mktag');
-               print $out "object $cid\n".
-                   "type commit\n".
-                   "tag $xtag\n".
-                   "tagger $author_name <$author_email>\n"
-                   or die "Cannot create tag object $xtag: $!\n";
-               close($out)
-                   or die "Cannot create tag object $xtag: $!\n";
-
-               my $tagobj = <$in>;
-               chomp $tagobj;
-
-               if ( !close($in) or waitpid($pid, 0) != $pid or
-                    $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
-                   die "Cannot create tag object $xtag: $!\n";
-               }
-               
-
-               open(C,">$git_dir/refs/tags/$xtag")
+
+               system('git-tag', $xtag, $cid) == 0
                        or die "Cannot create tag $xtag: $!\n";
-               print C "$tagobj\n"
-                       or die "Cannot write tag $xtag: $!\n";
-               close(C)
-                       or die "Cannot write tag $xtag: $!\n";
 
                print "Created tag '$xtag' on '$branch'\n" if $opt_v;
        }
@@ -883,12 +868,12 @@ while (<CVS>) {
                                print STDERR "Branch $branch erroneously stems from itself -- changed ancestor to $opt_o\n";
                                $ancestor = $opt_o;
                        }
-                       if (-f "$git_dir/refs/heads/$branch") {
+                       if (-f "$git_dir/$remote/$branch") {
                                print STDERR "Branch $branch already exists!\n";
                                $state=11;
                                next;
                        }
-                       unless (open(H,"$git_dir/refs/heads/$ancestor")) {
+                       unless (open(H,"$git_dir/$remote/$ancestor")) {
                                print STDERR "Branch $ancestor does not exist!\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
@@ -896,7 +881,7 @@ while (<CVS>) {
                        }
                        chomp(my $id = <H>);
                        close(H);
-                       unless (open(H,"> $git_dir/refs/heads/$branch")) {
+                       unless (open(H,"> $git_dir/$remote/$branch")) {
                                print STDERR "Could not create branch $branch: $!\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
@@ -1010,17 +995,19 @@ if ($orig_branch) {
                die "Fast-forward update failed: $?\n" if $?;
        }
        else {
-               system(qw(git-merge cvsimport HEAD), "refs/heads/$opt_o");
+               system(qw(git-merge cvsimport HEAD), "$remote/$opt_o");
                die "Could not merge $opt_o into the current branch.\n" if $?;
        }
 } else {
        $orig_branch = "master";
        print "DONE; creating $orig_branch branch\n" if $opt_v;
-       system("git-update-ref", "refs/heads/master", "refs/heads/$opt_o")
+       system("git-update-ref", "refs/heads/master", "$remote/$opt_o")
                unless -f "$git_dir/refs/heads/master";
+       system("git-symbolic-ref", "$remote/HEAD", "$remote/$opt_o")
+               if ($opt_r && $opt_o ne 'HEAD');
        system('git-update-ref', 'HEAD', "$orig_branch");
        unless ($opt_i) {
-               system('git checkout');
+               system('git checkout -f');
                die "checkout failed: $?\n" if $?;
        }
 }
index 1de517791f8a293a9c770f6260ff48f9719845ad..13dbd27a80adfee43ed6282041b948bd92e5cc12 100755 (executable)
@@ -22,6 +22,9 @@ use bytes;
 use Fcntl;
 use File::Temp qw/tempdir tempfile/;
 use File::Basename;
+use Getopt::Long qw(:config require_order no_ignore_case);
+
+my $VERSION = '@@GIT_VERSION@@';
 
 my $log = GITCVS::log->new();
 my $cfg;
@@ -85,19 +88,62 @@ my $methods = {
 my $state = { prependdir => '' };
 $log->info("--------------- STARTING -----------------");
 
+my $usage =
+    "Usage: git-cvsserver [options] [pserver|server] [<directory> ...]\n".
+    "    --base-path <path>  : Prepend to requested CVSROOT\n".
+    "    --strict-paths      : Don't allow recursing into subdirectories\n".
+    "    --export-all        : Don't check for gitcvs.enabled in config\n".
+    "    --version, -V       : Print version information and exit\n".
+    "    --help, -h, -H      : Print usage information and exit\n".
+    "\n".
+    "<directory> ... is a list of allowed directories. If no directories\n".
+    "are given, all are allowed. This is an additional restriction, gitcvs\n".
+    "access still needs to be enabled by the gitcvs.enabled config option.\n";
+
+my @opts = ( 'help|h|H', 'version|V',
+            'base-path=s', 'strict-paths', 'export-all' );
+GetOptions( $state, @opts )
+    or die $usage;
+
+if ($state->{version}) {
+    print "git-cvsserver version $VERSION\n";
+    exit;
+}
+if ($state->{help}) {
+    print $usage;
+    exit;
+}
+
 my $TEMP_DIR = tempdir( CLEANUP => 1 );
 $log->debug("Temporary directory is '$TEMP_DIR'");
 
+$state->{method} = 'ext';
+if (@ARGV) {
+    if ($ARGV[0] eq 'pserver') {
+       $state->{method} = 'pserver';
+       shift @ARGV;
+    } elsif ($ARGV[0] eq 'server') {
+       shift @ARGV;
+    }
+}
+
+# everything else is a directory
+$state->{allowed_roots} = [ @ARGV ];
+
+# don't export the whole system unless the users requests it
+if ($state->{'export-all'} && !@{$state->{allowed_roots}}) {
+    die "--export-all can only be used together with an explicit whitelist\n";
+}
+
 # if we are called with a pserver argument,
 # deal with the authentication cat before entering the
 # main loop
-$state->{method} = 'ext';
-if (@ARGV && $ARGV[0] eq 'pserver') {
-    $state->{method} = 'pserver';
+if ($state->{method} eq 'pserver') {
     my $line = <STDIN>; chomp $line;
-    unless( $line eq 'BEGIN AUTH REQUEST') {
+    unless( $line =~ /^BEGIN (AUTH|VERIFICATION) REQUEST$/) {
        die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n";
     }
+    my $request = $1;
     $line = <STDIN>; chomp $line;
     req_Root('root', $line) # reuse Root
        or die "E Invalid root $line \n";
@@ -109,10 +155,11 @@ if (@ARGV && $ARGV[0] eq 'pserver') {
     }
     $line = <STDIN>; chomp $line;    # validate the password?
     $line = <STDIN>; chomp $line;
-    unless ($line eq 'END AUTH REQUEST') {
-       die "E Do not understand $line -- expecting END AUTH REQUEST\n";
+    unless ($line eq "END $request REQUEST") {
+       die "E Do not understand $line -- expecting END $request REQUEST\n";
     }
     print "I LOVE YOU\n";
+    exit if $request eq 'VERIFICATION'; # cvs login
     # and now back to our regular programme...
 }
 
@@ -165,13 +212,53 @@ sub req_Root
     my ( $cmd, $data ) = @_;
     $log->debug("req_Root : $data");
 
-    $state->{CVSROOT} = $data;
+    unless ($data =~ m#^/#) {
+       print "error 1 Root must be an absolute pathname\n";
+       return 0;
+    }
+
+    my $cvsroot = $state->{'base-path'} || '';
+    $cvsroot =~ s#/+$##;
+    $cvsroot .= $data;
+
+    if ($state->{CVSROOT}
+       && ($state->{CVSROOT} ne $cvsroot)) {
+       print "error 1 Conflicting roots specified\n";
+       return 0;
+    }
+
+    $state->{CVSROOT} = $cvsroot;
 
     $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+
+    if (@{$state->{allowed_roots}}) {
+       my $allowed = 0;
+       foreach my $dir (@{$state->{allowed_roots}}) {
+           next unless $dir =~ m#^/#;
+           $dir =~ s#/+$##;
+           if ($state->{'strict-paths'}) {
+               if ($ENV{GIT_DIR} =~ m#^\Q$dir\E/?$#) {
+                   $allowed = 1;
+                   last;
+               }
+           } elsif ($ENV{GIT_DIR} =~ m#^\Q$dir\E(/?$|/)#) {
+               $allowed = 1;
+               last;
+           }
+       }
+
+       unless ($allowed) {
+           print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
+           print "E \n";
+           print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+           return 0;
+       }
+    }
+
     unless (-d $ENV{GIT_DIR} && -e $ENV{GIT_DIR}.'HEAD') {
        print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
-        print "E \n";
-        print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+       print "E \n";
+       print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
        return 0;
     }
 
@@ -194,7 +281,8 @@ sub req_Root
 
     my $enabled = ($cfg->{gitcvs}{$state->{method}}{enabled}
                   || $cfg->{gitcvs}{enabled});
-    unless ($enabled && $enabled =~ /^\s*(1|true|yes)\s*$/i) {
+    unless ($state->{'export-all'} ||
+           ($enabled && $enabled =~ /^\s*(1|true|yes)\s*$/i)) {
         print "E GITCVS emulation needs to be enabled on this repo\n";
         print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n";
         print "E \n";
@@ -535,8 +623,12 @@ sub req_Modified
     my ( $cmd, $data ) = @_;
 
     my $mode = <STDIN>;
+    defined $mode
+        or (print "E end of file reading mode for $data\n"), return;
     chomp $mode;
     my $size = <STDIN>;
+    defined $size
+        or (print "E end of file reading size of $data\n"), return;
     chomp $size;
 
     # Grab config information
@@ -556,7 +648,8 @@ sub req_Modified
         $bytesleft -= $blocksize;
     }
 
-    close $fh;
+    close $fh
+        or (print "E failed to write temporary, $filename: $!\n"), return;
 
     # Ensure we have something sensible for the file mode
     if ( $mode =~ /u=(\w+)/ )
@@ -813,8 +906,13 @@ sub req_update
     # projects (heads in this case) to checkout.
     #
     if ($state->{module} eq '') {
+       my $heads_dir = $state->{CVSROOT} . '/refs/heads';
+       if (!opendir HEADS, $heads_dir) {
+           print "E [server aborted]: Failed to open directory, "
+             . "$heads_dir: $!\nerror\n";
+           return 0;
+       }
         print "E cvs update: Updating .\n";
-       opendir HEADS, $state->{CVSROOT} . '/refs/heads';
        while (my $head = readdir(HEADS)) {
            if (-f $state->{CVSROOT} . '/refs/heads/' . $head) {
                print "E cvs update: New directory `$head'\n";
@@ -1098,6 +1196,7 @@ sub req_ci
     $log->info("Lockless commit start, basing commit on '$tmpdir', index file is '$file_index'");
 
     $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+    $ENV{GIT_WORK_TREE} = ".";
     $ENV{GIT_INDEX_FILE} = $file_index;
 
     # Remember where the head was at the beginning.
@@ -1623,6 +1722,7 @@ sub req_annotate
     $log->info("Temp checkoutdir creation successful, basing annotate session work on '$tmpdir', index file is '$file_index'");
 
     $ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+    $ENV{GIT_WORK_TREE} = ".";
     $ENV{GIT_INDEX_FILE} = $file_index;
 
     chdir $tmpdir;
@@ -1649,14 +1749,16 @@ sub req_annotate
        system("git-read-tree", $lastseenin);
        unless ($? == 0)
        {
-           die "Error running git-read-tree $lastseenin $file_index $!";
+           print "E error running git-read-tree $lastseenin $file_index $!\n";
+           return;
        }
        $log->info("Created index '$file_index' with commit $lastseenin - exit status $?");
 
         # do a checkout of the file
         system('git-checkout-index', '-f', '-u', $filename);
         unless ($? == 0) {
-            die "Error running git-checkout-index -f -u $filename : $!";
+            print "E error running git-checkout-index -f -u $filename : $!\n";
+            return;
         }
 
         $log->info("Annotate $filename");
@@ -1666,7 +1768,11 @@ sub req_annotate
         # git-jsannotate telling us about commits we are hiding
         # from the client.
 
-        open(ANNOTATEHINTS, ">$tmpdir/.annotate_hints") or die "Error opening > $tmpdir/.annotate_hints $!";
+        my $a_hints = "$tmpdir/.annotate_hints";
+        if (!open(ANNOTATEHINTS, '>', $a_hints)) {
+            print "E failed to open '$a_hints' for writing: $!\n";
+            return;
+        }
         for (my $i=0; $i < @$revisions; $i++)
         {
             print ANNOTATEHINTS $revisions->[$i][2];
@@ -1677,11 +1783,14 @@ sub req_annotate
         }
 
         print ANNOTATEHINTS "\n";
-        close ANNOTATEHINTS;
+        close ANNOTATEHINTS
+            or (print "E failed to write $a_hints: $!\n"), return;
 
-        my $annotatecmd = 'git-annotate';
-        open(ANNOTATE, "-|", $annotatecmd, '-l', '-S', "$tmpdir/.annotate_hints", $filename)
-           or die "Error invoking $annotatecmd -l -S $tmpdir/.annotate_hints $filename : $!";
+        my @cmd = (qw(git-annotate -l -S), $a_hints, $filename);
+        if (!open(ANNOTATE, "-|", @cmd)) {
+            print "E error invoking ". join(' ',@cmd) .": $!\n";
+            return;
+        }
         my $metadata = {};
         print "E Annotations for $filename\n";
         print "E ***************\n";
@@ -1725,14 +1834,14 @@ sub req_annotate
 # the second is $state->{files} which is everything after it.
 sub argsplit
 {
-    return unless( defined($state->{arguments}) and ref $state->{arguments} eq "ARRAY" );
-
-    my $type = shift;
-
     $state->{args} = [];
     $state->{files} = [];
     $state->{opt} = {};
 
+    return unless( defined($state->{arguments}) and ref $state->{arguments} eq "ARRAY" );
+
+    my $type = shift;
+
     if ( defined($type) )
     {
         my $opt = {};
@@ -1908,12 +2017,12 @@ sub transmitfile
         {
             open NEWFILE, ">", $targetfile or die("Couldn't open '$targetfile' for writing : $!");
             print NEWFILE $_ while ( <$fh> );
-            close NEWFILE;
+            close NEWFILE or die("Failed to write '$targetfile': $!");
         } else {
             print "$size\n";
             print while ( <$fh> );
         }
-        close $fh or die ("Couldn't close filehandle for transmitfile()");
+        close $fh or die ("Couldn't close filehandle for transmitfile(): $!");
     } else {
         die("Couldn't execute git-cat-file");
     }
@@ -2413,17 +2522,14 @@ sub update
                     if ($parent eq $lastpicked) {
                         next;
                     }
-                    open my $p, 'git-merge-base '. $lastpicked . ' '
-                    . $parent . '|';
-                    my @output = (<$p>);
-                    close $p;
-                    my $base = join('', @output);
+                    my $base = safe_pipe_capture('git-merge-base',
+                                                $lastpicked, $parent);
                     chomp $base;
                     if ($base) {
                         my @merged;
                         # print "want to log between  $base $parent \n";
                         open(GITLOG, '-|', 'git-log', "$base..$parent")
-                        or die "Cannot call git-log: $!";
+                         or die "Cannot call git-log: $!";
                         my $mergedhash;
                         while (<GITLOG>) {
                             chomp;
index 0e05cf1195737d2c7afc4b9447d7b4105908bf77..c3a200120df636fe8db0f02902d92c351c6c5e2e 100755 (executable)
@@ -61,7 +61,7 @@ do
                quiet=--quiet
                ;;
        -v|--verbose)
-               verbose=Yes
+               verbose="$verbose"Yes
                ;;
        -k|--k|--ke|--kee|--keep)
                keep='-k -k'
@@ -117,20 +117,20 @@ append_fetch_head () {
        test -n "$verbose" && flags="$flags$LF-v"
        test -n "$force$single_force" && flags="$flags$LF-f"
        GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
-               git-fetch--tool $flags append-fetch-head "$@"
+               git fetch--tool $flags append-fetch-head "$@"
 }
 
 # updating the current HEAD with git-fetch in a bare
 # repository is always fine.
 if test -z "$update_head_ok" && test $(is_bare_repository) = false
 then
-       orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+       orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
 fi
 
 # Allow --notags from remote.$1.tagopt
 case "$tags$no_tags" in
 '')
-       case "$(git-config --get "remote.$1.tagopt")" in
+       case "$(git config --get "remote.$1.tagopt")" in
        --no-tags)
                no_tags=t ;;
        esac
@@ -146,7 +146,7 @@ if test "$tags"
 then
        taglist=`IFS='  ' &&
                  echo "$ls_remote_result" |
-                 git-show-ref --exclude-existing=refs/tags/ |
+                 git show-ref --exclude-existing=refs/tags/ |
                  while read sha1 name
                  do
                        echo ".${name}:${name}"
@@ -163,18 +163,18 @@ fi
 
 fetch_all_at_once () {
 
-  eval=$(echo "$1" | git-fetch--tool parse-reflist "-")
+  eval=$(echo "$1" | git fetch--tool parse-reflist "-")
   eval "$eval"
 
     ( : subshell because we muck with IFS
       IFS="    $LF"
       (
        if test "$remote" = . ; then
-           git-show-ref $rref || echo failed "$remote"
+           git show-ref $rref || echo failed "$remote"
        elif test -f "$remote" ; then
            test -n "$shallow_depth" &&
                die "shallow clone with bundle is not supported"
-           git-bundle unbundle "$remote" $rref ||
+           git bundle unbundle "$remote" $rref ||
            echo failed "$remote"
        else
                if      test -d "$remote" &&
@@ -190,19 +190,25 @@ fetch_all_at_once () {
                        # connected to our repository's tips, in which
                        # case we do not have to do any fetch.
                        theirs=$(echo "$ls_remote_result" | \
-                               git-fetch--tool -s pick-rref "$rref" "-") &&
+                               git fetch--tool -s pick-rref "$rref" "-") &&
 
                        # This will barf when $theirs reach an object that
                        # we do not have in our repository.  Otherwise,
                        # we already have everything the fetch would bring in.
-                       git-rev-list --objects $theirs --not --all \
+                       git rev-list --objects $theirs --not --all \
                                >/dev/null 2>/dev/null
                then
                        echo "$ls_remote_result" | \
-                               git-fetch--tool pick-rref "$rref" "-"
+                               git fetch--tool pick-rref "$rref" "-"
                else
+                       flags=
+                       case $verbose in
+                       YesYes*)
+                           flags="-v"
+                           ;;
+                       esac
                        git-fetch-pack --thin $exec $keep $shallow_depth \
-                               $quiet $no_progress "$remote" $rref ||
+                               $quiet $no_progress $flags "$remote" $rref ||
                        echo failed "$remote"
                fi
        fi
@@ -212,7 +218,7 @@ fetch_all_at_once () {
        test -n "$verbose" && flags="$flags -v"
        test -n "$force" && flags="$flags -f"
        GIT_REFLOG_ACTION="$GIT_REFLOG_ACTION" \
-               git-fetch--tool $flags native-store \
+               git fetch--tool $flags native-store \
                        "$remote" "$remote_nick" "$refs"
       )
     ) || exit
@@ -259,13 +265,13 @@ fetch_per_ref () {
              curl_extra_args="-k"
          fi
          if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
-               "`git-config --bool http.noEPSV`" = true ]; then
+               "`git config --bool http.noEPSV`" = true ]; then
              noepsv_opt="--disable-epsv"
          fi
 
          # Find $remote_name from ls-remote output.
          head=$(echo "$ls_remote_result" | \
-               git-fetch--tool -s pick-rref "$remote_name" "-")
+               git fetch--tool -s pick-rref "$remote_name" "-")
          expr "z$head" : "z$_x40\$" >/dev/null ||
                die "No such ref $remote_name at $remote"
          echo >&2 "Fetching $remote_name from $remote using $proto"
@@ -277,7 +283,7 @@ fetch_per_ref () {
                die "shallow clone with rsync not supported"
          TMP_HEAD="$GIT_DIR/TMP_HEAD"
          rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1
-         head=$(git-rev-parse --verify TMP_HEAD)
+         head=$(git rev-parse --verify TMP_HEAD)
          rm -f "$TMP_HEAD"
          case "$quiet" in '') v=-v ;; *) v= ;; esac
          test "$rsync_slurped_objects" || {
@@ -336,10 +342,10 @@ case "$no_tags$tags" in
                # using local tracking branch.
                taglist=$(IFS=' ' &&
                echo "$ls_remote_result" |
-               git-show-ref --exclude-existing=refs/tags/ |
+               git show-ref --exclude-existing=refs/tags/ |
                while read sha1 name
                do
-                       git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
+                       git cat-file -t "$sha1" >/dev/null 2>&1 || continue
                        echo >&2 "Auto-following $name"
                        echo ".${name}:${name}"
                done)
@@ -359,10 +365,10 @@ case "$orig_head" in
 '')
        ;;
 ?*)
-       curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+       curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
        if test "$curr_head" != "$orig_head"
        then
-           git-update-ref \
+           git update-ref \
                        -m "$GIT_REFLOG_ACTION: Undoing incorrectly fetched HEAD." \
                        HEAD "$orig_head"
                die "Cannot fetch into the current branch."
diff --git a/git-filter-branch.sh b/git-filter-branch.sh
new file mode 100755 (executable)
index 0000000..c42e451
--- /dev/null
@@ -0,0 +1,390 @@
+#!/bin/sh
+#
+# Rewrite revision history
+# Copyright (c) Petr Baudis, 2006
+# Minimal changes to "port" it to core-git (c) Johannes Schindelin, 2007
+#
+# Lets you rewrite the revision history of the current branch, creating
+# a new branch. You can specify a number of filters to modify the commits,
+# files and trees.
+
+USAGE="git-filter-branch [-d TEMPDIR] [FILTERS] DESTBRANCH [REV-RANGE]"
+. git-sh-setup
+
+warn () {
+        echo "$*" >&2
+}
+
+map()
+{
+       # if it was not rewritten, take the original
+       if test -r "$workdir/../map/$1"
+       then
+               cat "$workdir/../map/$1"
+       else
+               echo "$1"
+       fi
+}
+
+# override die(): this version puts in an extra line break, so that
+# the progress is still visible
+
+die()
+{
+       echo >&2
+       echo "$*" >&2
+       exit 1
+}
+
+# When piped a commit, output a script to set the ident of either
+# "author" or "committer
+
+set_ident () {
+       lid="$(echo "$1" | tr "A-Z" "a-z")"
+       uid="$(echo "$1" | tr "a-z" "A-Z")"
+       pick_id_script='
+               /^'$lid' /{
+                       s/'\''/'\''\\'\'\''/g
+                       h
+                       s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
+                       s/'\''/'\''\'\'\''/g
+                       s/.*/export GIT_'$uid'_NAME='\''&'\''/p
+
+                       g
+                       s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
+                       s/'\''/'\''\'\'\''/g
+                       s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
+
+                       g
+                       s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
+                       s/'\''/'\''\'\'\''/g
+                       s/.*/export GIT_'$uid'_DATE='\''&'\''/p
+
+                       q
+               }
+       '
+
+       LANG=C LC_ALL=C sed -ne "$pick_id_script"
+       # Ensure non-empty id name.
+       echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
+}
+
+tempdir=.git-rewrite
+filter_env=
+filter_tree=
+filter_index=
+filter_parent=
+filter_msg=cat
+filter_commit='git commit-tree "$@"'
+filter_tag_name=
+filter_subdir=
+orig_namespace=refs/original/
+force=
+while case "$#" in 0) usage;; esac
+do
+       case "$1" in
+       --)
+               shift
+               break
+               ;;
+       --force|-f)
+               shift
+               force=t
+               continue
+               ;;
+       -*)
+               ;;
+       *)
+               break;
+       esac
+
+       # all switches take one argument
+       ARG="$1"
+       case "$#" in 1) usage ;; esac
+       shift
+       OPTARG="$1"
+       shift
+
+       case "$ARG" in
+       -d)
+               tempdir="$OPTARG"
+               ;;
+       --env-filter)
+               filter_env="$OPTARG"
+               ;;
+       --tree-filter)
+               filter_tree="$OPTARG"
+               ;;
+       --index-filter)
+               filter_index="$OPTARG"
+               ;;
+       --parent-filter)
+               filter_parent="$OPTARG"
+               ;;
+       --msg-filter)
+               filter_msg="$OPTARG"
+               ;;
+       --commit-filter)
+               filter_commit="$OPTARG"
+               ;;
+       --tag-name-filter)
+               filter_tag_name="$OPTARG"
+               ;;
+       --subdirectory-filter)
+               filter_subdir="$OPTARG"
+               ;;
+       --original)
+               orig_namespace="$OPTARG"
+               ;;
+       *)
+               usage
+               ;;
+       esac
+done
+
+case "$force" in
+t)
+       rm -rf "$tempdir"
+;;
+'')
+       test -d "$tempdir" &&
+               die "$tempdir already exists, please remove it"
+esac
+mkdir -p "$tempdir/t" &&
+tempdir="$(cd "$tempdir"; pwd)" &&
+cd "$tempdir/t" &&
+workdir="$(pwd)" ||
+die ""
+
+# Make sure refs/original is empty
+git for-each-ref > "$tempdir"/backup-refs
+while read sha1 type name
+do
+       case "$force,$name" in
+       ,$orig_namespace*)
+               die "Namespace $orig_namespace not empty"
+       ;;
+       t,$orig_namespace*)
+               git update-ref -d "$name" $sha1
+       ;;
+       esac
+done < "$tempdir"/backup-refs
+
+export GIT_DIR GIT_WORK_TREE=.
+
+# These refs should be updated if their heads were rewritten
+
+git rev-parse --revs-only --symbolic "$@" |
+while read ref
+do
+       # normalize ref
+       case "$ref" in
+       HEAD)
+               ref="$(git symbolic-ref "$ref")"
+       ;;
+       refs/*)
+       ;;
+       *)
+               ref="$(git for-each-ref --format='%(refname)' |
+                       grep /"$ref")"
+       esac
+
+       git check-ref-format "$ref" && echo "$ref"
+done > "$tempdir"/heads
+
+test -s "$tempdir"/heads ||
+       die "Which ref do you want to rewrite?"
+
+export GIT_INDEX_FILE="$(pwd)/../index"
+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"
+
+case "$filter_subdir" in
+"")
+       git rev-list --reverse --topo-order --default HEAD \
+               --parents "$@"
+       ;;
+*)
+       git rev-list --reverse --topo-order --default HEAD \
+               --parents --full-history "$@" -- "$filter_subdir"
+esac > ../revs || die "Could not get the commits"
+commits=$(wc -l <../revs | tr -d " ")
+
+test $commits -eq 0 && die "Found nothing to rewrite"
+
+# Rewrite the commits
+
+i=0
+while read commit parents; do
+       i=$(($i+1))
+       printf "\rRewrite $commit ($i/$commits)"
+
+       case "$filter_subdir" in
+       "")
+               git read-tree -i -m $commit
+               ;;
+       *)
+               git read-tree -i -m $commit:"$filter_subdir"
+       esac || die "Could not initialize the index"
+
+       export GIT_COMMIT=$commit
+       git cat-file commit "$commit" >../commit ||
+               die "Cannot read commit $commit"
+
+       eval "$(set_ident AUTHOR <../commit)" ||
+               die "setting author failed for commit $commit"
+       eval "$(set_ident COMMITTER <../commit)" ||
+               die "setting committer failed for commit $commit"
+       eval "$filter_env" < /dev/null ||
+               die "env filter failed: $filter_env"
+
+       if [ "$filter_tree" ]; then
+               git checkout-index -f -u -a ||
+                       die "Could not checkout the index"
+               # files that $commit removed are now still in the working tree;
+               # remove them, else they would be added again
+               git ls-files -z --others | xargs -0 rm -f
+               eval "$filter_tree" < /dev/null ||
+                       die "tree filter failed: $filter_tree"
+
+               git diff-index -r $commit | cut -f 2- | tr '\n' '\0' | \
+                       xargs -0 git update-index --add --replace --remove
+               git ls-files -z --others | \
+                       xargs -0 git update-index --add --replace --remove
+       fi
+
+       eval "$filter_index" < /dev/null ||
+               die "index filter failed: $filter_index"
+
+       parentstr=
+       for parent in $parents; do
+               for reparent in $(map "$parent"); do
+                       parentstr="$parentstr -p $reparent"
+               done
+       done
+       if [ "$filter_parent" ]; then
+               parentstr="$(echo "$parentstr" | eval "$filter_parent")" ||
+                               die "parent filter failed: $filter_parent"
+       fi
+
+       sed -e '1,/^$/d' <../commit | \
+               eval "$filter_msg" > ../message ||
+                       die "msg filter failed: $filter_msg"
+       sh -c "$filter_commit" "git commit-tree" \
+               $(git write-tree) $parentstr < ../message > ../map/$commit
+done <../revs
+
+# In case of a subdirectory filter, it is possible that a specified head
+# is not in the set of rewritten commits, because it was pruned by the
+# revision walker.  Fix it by mapping these heads to the next rewritten
+# ancestor(s), i.e. the boundaries in the set of rewritten commits.
+
+# NEEDSWORK: we should sort the unmapped refs topologically first
+while read ref
+do
+       sha1=$(git rev-parse "$ref"^0)
+       test -f "$workdir"/../map/$sha1 && continue
+       # Assign the boundarie(s) in the set of rewritten commits
+       # as the replacement commit(s).
+       # (This would look a bit nicer if --not --stdin worked.)
+       for p in $( (cd "$workdir"/../map; ls | sed "s/^/^/") |
+               git rev-list $ref --boundary --stdin |
+               sed -n "s/^-//p")
+       do
+               map $p >> "$workdir"/../map/$sha1
+       done
+done < "$tempdir"/heads
+
+# Finally update the refs
+
+_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"
+count=0
+echo
+while read ref
+do
+       # avoid rewriting a ref twice
+       test -f "$orig_namespace$ref" && continue
+
+       sha1=$(git rev-parse "$ref"^0)
+       rewritten=$(map $sha1)
+
+       test $sha1 = "$rewritten" &&
+               warn "WARNING: Ref '$ref' is unchanged" &&
+               continue
+
+       case "$rewritten" in
+       '')
+               echo "Ref '$ref' was deleted"
+               git update-ref -m "filter-branch: delete" -d "$ref" $sha1 ||
+                       die "Could not delete $ref"
+       ;;
+       $_x40)
+               echo "Ref '$ref' was rewritten"
+               git update-ref -m "filter-branch: rewrite" \
+                               "$ref" $rewritten $sha1 ||
+                       die "Could not rewrite $ref"
+       ;;
+       *)
+               # NEEDSWORK: possibly add -Werror, making this an error
+               warn "WARNING: '$ref' was rewritten into multiple commits:"
+               warn "$rewritten"
+               warn "WARNING: Ref '$ref' points to the first one now."
+               rewritten=$(echo "$rewritten" | head -n 1)
+               git update-ref -m "filter-branch: rewrite to first" \
+                               "$ref" $rewritten $sha1 ||
+                       die "Could not rewrite $ref"
+       ;;
+       esac
+       git update-ref -m "filter-branch: backup" "$orig_namespace$ref" $sha1
+       count=$(($count+1))
+done < "$tempdir"/heads
+
+# TODO: This should possibly go, with the semantics that all positive given
+#       refs are updated, and their original heads stored in refs/original/
+# Filter tags
+
+if [ "$filter_tag_name" ]; then
+       git for-each-ref --format='%(objectname) %(objecttype) %(refname)' refs/tags |
+       while read sha1 type ref; do
+               ref="${ref#refs/tags/}"
+               # XXX: Rewrite tagged trees as well?
+               if [ "$type" != "commit" -a "$type" != "tag" ]; then
+                       continue;
+               fi
+
+               if [ "$type" = "tag" ]; then
+                       # Dereference to a commit
+                       sha1t="$sha1"
+                       sha1="$(git rev-parse "$sha1"^{commit} 2>/dev/null)" || continue
+               fi
+
+               [ -f "../map/$sha1" ] || continue
+               new_sha1="$(cat "../map/$sha1")"
+               export GIT_COMMIT="$sha1"
+               new_ref="$(echo "$ref" | eval "$filter_tag_name")" ||
+                       die "tag name filter failed: $filter_tag_name"
+
+               echo "$ref -> $new_ref ($sha1 -> $new_sha1)"
+
+               if [ "$type" = "tag" ]; then
+                       # Warn that we are not rewriting the tag object itself.
+                       warn "unreferencing tag object $sha1t"
+               fi
+
+               git update-ref "refs/tags/$new_ref" "$new_sha1" ||
+                       die "Could not write tag $new_ref"
+       done
+fi
+
+cd ../..
+rm -rf "$tempdir"
+echo
+test $count -gt 0 && echo "These refs were rewritten:"
+git show-ref | grep ^"$orig_namespace"
+
+exit $ret
index eee495a98666094415233a7023c03db9852dfe53..9770b0bc27ae4dfd44f4bfcfc74946fabefdc127 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=0.7.GITGUI
+DEF_VER=0.8.GITGUI
 
 LF='
 '
index ab550fc6a70f861f7e6eac5295342ccc95aee9a3..1bac6fed46635e2387d16e801294a4750476ddaa 100644 (file)
@@ -31,11 +31,35 @@ ifndef INSTALL
        INSTALL = install
 endif
 
+INSTALL_D0 = $(INSTALL) -d -m755 # space is required here
+INSTALL_D1 =
+INSTALL_R0 = $(INSTALL) -m644 # space is required here
+INSTALL_R1 =
+INSTALL_X0 = $(INSTALL) -m755 # space is required here
+INSTALL_X1 =
+INSTALL_L0 = rm -f # space is required here
+INSTALL_L1 = && ln # space is required here
+INSTALL_L2 =
+INSTALL_L3 =
+
 ifndef V
-       QUIET_GEN      = @echo '   ' GEN $@;
-       QUIET_BUILT_IN = @echo '   ' BUILTIN $@;
-       QUIET_INDEX    = @echo '   ' INDEX $(dir $@);
+       QUIET          = @
+       QUIET_GEN      = $(QUIET)echo '   ' GEN $@ &&
+       QUIET_BUILT_IN = $(QUIET)echo '   ' BUILTIN $@ &&
+       QUIET_INDEX    = $(QUIET)echo '   ' INDEX $(dir $@) &&
        QUIET_2DEVNULL = 2>/dev/null
+
+       INSTALL_D0 = dir=
+       INSTALL_D1 = && echo ' ' DEST $$dir && $(INSTALL) -d -m755 "$$dir"
+       INSTALL_R0 = src=
+       INSTALL_R1 = && echo '   ' INSTALL 644 `basename $$src` && $(INSTALL) -m644 $$src
+       INSTALL_X0 = src=
+       INSTALL_X1 = && echo '   ' INSTALL 755 `basename $$src` && $(INSTALL) -m755 $$src
+
+       INSTALL_L0 = dst=
+       INSTALL_L1 = && src=
+       INSTALL_L2 = && dst=
+       INSTALL_L3 = && echo '   ' 'LINK       ' `basename "$$dst"` '->' `basename "$$src"` && rm -f "$$dst" && ln "$$src" "$$dst"
 endif
 
 TCL_PATH   ?= tclsh
@@ -115,12 +139,12 @@ GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
 all:: $(ALL_PROGRAMS) lib/tclIndex
 
 install: all
-       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)'
-       $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)'
-       $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;)
-       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)'
-       $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)'
-       $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;)
+       $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(gitexecdir_SQ)' $(INSTALL_D1)
+       $(QUIET)$(INSTALL_X0)git-gui $(INSTALL_X1) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
+       $(QUIET)$(foreach p,$(GITGUI_BUILT_INS), $(INSTALL_L0)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L1)'$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' $(INSTALL_L2)'$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' $(INSTALL_L3) &&) true
+       $(QUIET)$(INSTALL_D0)'$(DESTDIR_SQ)$(libdir_SQ)' $(INSTALL_D1)
+       $(QUIET)$(INSTALL_R0)lib/tclIndex $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)'
+       $(QUIET)$(foreach p,$(ALL_LIBFILES), $(INSTALL_R0)$p $(INSTALL_R1) '$(DESTDIR_SQ)$(libdir_SQ)' &&) true
 
 dist-version:
        @mkdir -p $(TARDIR)
index c38aa067acf12135fbd203b028326dddbad97448..671b8873f27e68f8ed2c93efd6734f583f8b7500 100755 (executable)
@@ -42,6 +42,24 @@ if {[catch {package require Tcl 8.4} err]
        exit 1
 }
 
+######################################################################
+##
+## enable verbose loading?
+
+if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
+       unset _verbose
+       rename auto_load real__auto_load
+       proc auto_load {name args} {
+               puts stderr "auto_load $name"
+               return [uplevel 1 real__auto_load $name $args]
+       }
+       rename source real__source
+       proc source {name} {
+               puts stderr "source    $name"
+               uplevel 1 real__source $name
+       }
+}
+
 ######################################################################
 ##
 ## configure our library
@@ -54,26 +72,33 @@ if {$oguirel eq {1}} {
 } elseif {[string match @@* $oguirel]} {
        set oguilib [file join [file dirname [file normalize $argv0]] lib]
 }
+
 set idx [file join $oguilib tclIndex]
-catch {
-       set fd [open $idx r]
-       if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
-               set idx [list]
-               while {[gets $fd n] >= 0} {
-                       if {$n ne {} && ![string match #* $n]} {
-                               lappend idx $n
-                       }
+if {[catch {set fd [open $idx r]} err]} {
+       catch {wm withdraw .}
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "git-gui: fatal error" \
+               -message $err
+       exit 1
+}
+if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} {
+       set idx [list]
+       while {[gets $fd n] >= 0} {
+               if {$n ne {} && ![string match #* $n]} {
+                       lappend idx $n
                }
-       } else {
-               set idx {}
        }
-       close $fd
+} else {
+       set idx {}
 }
+close $fd
+
 if {$idx ne {}} {
        set loaded [list]
        foreach p $idx {
                if {[lsearch -exact $loaded $p] >= 0} continue
-               puts $p
                source [file join $oguilib $p]
                lappend loaded $p
        }
@@ -81,21 +106,7 @@ if {$idx ne {}} {
 } else {
        set auto_path [concat [list $oguilib] $auto_path]
 }
-unset -nocomplain oguilib oguirel idx fd
-
-if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} {
-       unset _verbose
-       rename auto_load real__auto_load
-       proc auto_load {name args} {
-               puts stderr "auto_load $name"
-               return [uplevel 1 real__auto_load $name $args]
-       }
-       rename source real__source
-       proc source {name} {
-               puts stderr "source    $name"
-               uplevel 1 real__source $name
-       }
-}
+unset -nocomplain oguirel idx fd
 
 ######################################################################
 ##
@@ -106,6 +117,7 @@ set _gitdir {}
 set _gitexec {}
 set _reponame {}
 set _iscygwin {}
+set _search_path {}
 
 proc appname {} {
        global _appname
@@ -117,7 +129,7 @@ proc gitdir {args} {
        if {$args eq {}} {
                return $_gitdir
        }
-       return [eval [concat [list file join $_gitdir] $args]]
+       return [eval [list file join $_gitdir] $args]
 }
 
 proc gitexec {args} {
@@ -126,20 +138,26 @@ proc gitexec {args} {
                if {[catch {set _gitexec [git --exec-path]} err]} {
                        error "Git not installed?\n\n$err"
                }
+               if {[is_Cygwin]} {
+                       set _gitexec [exec cygpath \
+                               --windows \
+                               --absolute \
+                               $_gitexec]
+               } else {
+                       set _gitexec [file normalize $_gitexec]
+               }
        }
        if {$args eq {}} {
                return $_gitexec
        }
-       return [eval [concat [list file join $_gitexec] $args]]
+       return [eval [list file join $_gitexec] $args]
 }
 
 proc reponame {} {
-       global _reponame
-       return $_reponame
+       return $::_reponame
 }
 
 proc is_MacOSX {} {
-       global tcl_platform tk_library
        if {[tk windowingsystem] eq {aqua}} {
                return 1
        }
@@ -147,17 +165,16 @@ proc is_MacOSX {} {
 }
 
 proc is_Windows {} {
-       global tcl_platform
-       if {$tcl_platform(platform) eq {windows}} {
+       if {$::tcl_platform(platform) eq {windows}} {
                return 1
        }
        return 0
 }
 
 proc is_Cygwin {} {
-       global tcl_platform _iscygwin
+       global _iscygwin
        if {$_iscygwin eq {}} {
-               if {$tcl_platform(platform) eq {windows}} {
+               if {$::tcl_platform(platform) eq {windows}} {
                        if {[catch {set p [exec cygpath --windir]} err]} {
                                set _iscygwin 0
                        } else {
@@ -211,13 +228,22 @@ proc is_config_true {name} {
        }
 }
 
+proc get_config {name} {
+       global repo_config
+       if {[catch {set v $repo_config($name)}]} {
+               return {}
+       } else {
+               return $v
+       }
+}
+
 proc load_config {include_global} {
        global repo_config global_config default_config
 
        array unset global_config
        if {$include_global} {
                catch {
-                       set fd_rc [open "| git config --global --list" r]
+                       set fd_rc [git_read config --global --list]
                        while {[gets $fd_rc line] >= 0} {
                                if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
                                        if {[is_many_config $name]} {
@@ -233,7 +259,7 @@ proc load_config {include_global} {
 
        array unset repo_config
        catch {
-               set fd_rc [open "| git config --list" r]
+               set fd_rc [git_read config --list]
                while {[gets $fd_rc line] >= 0} {
                        if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
                                if {[is_many_config $name]} {
@@ -260,8 +286,224 @@ proc load_config {include_global} {
 ##
 ## handy utils
 
+proc _git_cmd {name} {
+       global _git_cmd_path
+
+       if {[catch {set v $_git_cmd_path($name)}]} {
+               switch -- $name {
+                 version   -
+               --version   -
+               --exec-path { return [list $::_git $name] }
+               }
+
+               set p [gitexec git-$name$::_search_exe]
+               if {[file exists $p]} {
+                       set v [list $p]
+               } elseif {[is_Windows] && [file exists [gitexec git-$name]]} {
+                       # Try to determine what sort of magic will make
+                       # git-$name go and do its thing, because native
+                       # Tcl on Windows doesn't know it.
+                       #
+                       set p [gitexec git-$name]
+                       set f [open $p r]
+                       set s [gets $f]
+                       close $f
+
+                       switch -glob -- $s {
+                       #!*sh     { set i sh     }
+                       #!*perl   { set i perl   }
+                       #!*python { set i python }
+                       default   { error "git-$name is not supported: $s" }
+                       }
+
+                       upvar #0 _$i interp
+                       if {![info exists interp]} {
+                               set interp [_which $i]
+                       }
+                       if {$interp eq {}} {
+                               error "git-$name requires $i (not in PATH)"
+                       }
+                       set v [list $interp $p]
+               } else {
+                       # Assume it is builtin to git somehow and we
+                       # aren't actually able to see a file for it.
+                       #
+                       set v [list $::_git $name]
+               }
+               set _git_cmd_path($name) $v
+       }
+       return $v
+}
+
+proc _which {what} {
+       global env _search_exe _search_path
+
+       if {$_search_path eq {}} {
+               if {[is_Cygwin]} {
+                       set _search_path [split [exec cygpath \
+                               --windows \
+                               --path \
+                               --absolute \
+                               $env(PATH)] {;}]
+                       set _search_exe .exe
+               } elseif {[is_Windows]} {
+                       set _search_path [split $env(PATH) {;}]
+                       set _search_exe .exe
+               } else {
+                       set _search_path [split $env(PATH) :]
+                       set _search_exe {}
+               }
+       }
+
+       foreach p $_search_path {
+               set p [file join $p $what$_search_exe]
+               if {[file exists $p]} {
+                       return [file normalize $p]
+               }
+       }
+       return {}
+}
+
+proc _lappend_nice {cmd_var} {
+       global _nice
+       upvar $cmd_var cmd
+
+       if {![info exists _nice]} {
+               set _nice [_which nice]
+       }
+       if {$_nice ne {}} {
+               lappend cmd $_nice
+       }
+}
+
 proc git {args} {
-       return [eval exec git $args]
+       set opt [list exec]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       _lappend_nice opt
+               }
+
+               default {
+                       break
+               }
+
+               }
+
+               set args [lrange $args 1 end]
+       }
+
+       set cmdp [_git_cmd [lindex $args 0]]
+       set args [lrange $args 1 end]
+
+       return [eval $opt $cmdp $args]
+}
+
+proc _open_stdout_stderr {cmd} {
+       if {[catch {
+                       set fd [open $cmd r]
+               } err]} {
+               if {   [lindex $cmd end] eq {2>@1}
+                   && $err eq {can not find channel named "1"}
+                       } {
+                       # Older versions of Tcl 8.4 don't have this 2>@1 IO
+                       # redirect operator.  Fallback to |& cat for those.
+                       # The command was not actually started, so its safe
+                       # to try to start it a second time.
+                       #
+                       set fd [open [concat \
+                               [lrange $cmd 0 end-1] \
+                               [list |& cat] \
+                               ] r]
+               } else {
+                       error $err
+               }
+       }
+       fconfigure $fd -eofchar {}
+       return $fd
+}
+
+proc git_read {args} {
+       set opt [list |]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       _lappend_nice opt
+               }
+
+               --stderr {
+                       lappend args 2>@1
+               }
+
+               default {
+                       break
+               }
+
+               }
+
+               set args [lrange $args 1 end]
+       }
+
+       set cmdp [_git_cmd [lindex $args 0]]
+       set args [lrange $args 1 end]
+
+       return [_open_stdout_stderr [concat $opt $cmdp $args]]
+}
+
+proc git_write {args} {
+       set opt [list |]
+
+       while {1} {
+               switch -- [lindex $args 0] {
+               --nice {
+                       _lappend_nice opt
+               }
+
+               default {
+                       break
+               }
+
+               }
+
+               set args [lrange $args 1 end]
+       }
+
+       set cmdp [_git_cmd [lindex $args 0]]
+       set args [lrange $args 1 end]
+
+       return [open [concat $opt $cmdp $args] w]
+}
+
+proc sq {value} {
+       regsub -all ' $value "'\\''" value
+       return "'$value'"
+}
+
+proc load_current_branch {} {
+       global current_branch is_detached
+
+       set fd [open [gitdir HEAD] r]
+       if {[gets $fd ref] < 1} {
+               set ref {}
+       }
+       close $fd
+
+       set pfx {ref: refs/heads/}
+       set len [string length $pfx]
+       if {[string equal -length $len $pfx $ref]} {
+               # We're on a branch.  It might not exist.  But
+               # HEAD looks good enough to be a branch.
+               #
+               set current_branch [string range $ref $len end]
+               set is_detached 0
+       } else {
+               # Assume this is a detached head.
+               #
+               set current_branch HEAD
+               set is_detached 1
+       }
 }
 
 auto_load tk_optionMenu
@@ -275,35 +517,155 @@ proc tk_optionMenu {w varName args} {
 
 ######################################################################
 ##
-## version check
+## find git
 
-set req_maj 1
-set req_min 5
+set _git  [_which git]
+if {$_git eq {}} {
+       catch {wm withdraw .}
+       error_popup "Cannot find git in PATH."
+       exit 1
+}
+
+######################################################################
+##
+## version check
 
-if {[catch {set v [git --version]} err]} {
+if {[catch {set _git_version [git --version]} err]} {
        catch {wm withdraw .}
        error_popup "Cannot determine Git version:
 
 $err
 
-[appname] requires Git $req_maj.$req_min or later."
+[appname] requires Git 1.5.0 or later."
        exit 1
 }
-if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} {
-       if {$act_maj < $req_maj
-               || ($act_maj == $req_maj && $act_min < $req_min)} {
-               catch {wm withdraw .}
-               error_popup "[appname] requires Git $req_maj.$req_min or later.
+if {![regsub {^git version } $_git_version {} _git_version]} {
+       catch {wm withdraw .}
+       error_popup "Cannot parse Git version string:\n\n$_git_version"
+       exit 1
+}
+
+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 {\.GIT$} $_git_version {} _git_version
 
-You are using $v."
+if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} {
+       catch {wm withdraw .}
+       if {[tk_messageBox \
+               -icon warning \
+               -type yesno \
+               -default no \
+               -title "[appname]: warning" \
+               -message "Git version cannot be determined.
+
+$_git claims it is version '$_real_git_version'.
+
+[appname] requires at least Git 1.5.0 or later.
+
+Assume '$_real_git_version' is version 1.5.0?
+"] eq {yes}} {
+               set _git_version 1.5.0
+       } else {
                exit 1
        }
-} else {
+}
+unset _real_git_version
+
+proc git-version {args} {
+       global _git_version
+
+       switch [llength $args] {
+       0 {
+               return $_git_version
+       }
+
+       2 {
+               set op [lindex $args 0]
+               set vr [lindex $args 1]
+               set cm [package vcompare $_git_version $vr]
+               return [expr $cm $op 0]
+       }
+
+       4 {
+               set type [lindex $args 0]
+               set name [lindex $args 1]
+               set parm [lindex $args 2]
+               set body [lindex $args 3]
+
+               if {($type ne {proc} && $type ne {method})} {
+                       error "Invalid arguments to git-version"
+               }
+               if {[llength $body] < 2 || [lindex $body end-1] ne {default}} {
+                       error "Last arm of $type $name must be default"
+               }
+
+               foreach {op vr cb} [lrange $body 0 end-2] {
+                       if {[git-version $op $vr]} {
+                               return [uplevel [list $type $name $parm $cb]]
+                       }
+               }
+
+               return [uplevel [list $type $name $parm [lindex $body end]]]
+       }
+
+       default {
+               error "git-version >= x"
+       }
+
+       }
+}
+
+if {[git-version < 1.5]} {
        catch {wm withdraw .}
-       error_popup "Cannot parse Git version string:\n\n$v"
+       error_popup "[appname] requires Git 1.5.0 or later.
+
+You are using [git-version]:
+
+[git --version]"
        exit 1
 }
-unset -nocomplain v _junk act_maj act_min req_maj req_min
+
+######################################################################
+##
+## feature option selection
+
+if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
+       unset _junk
+} else {
+       set subcommand gui
+}
+if {$subcommand eq {gui.sh}} {
+       set subcommand gui
+}
+if {$subcommand eq {gui} && [llength $argv] > 0} {
+       set subcommand [lindex $argv 0]
+       set argv [lrange $argv 1 end]
+}
+
+enable_option multicommit
+enable_option branch
+enable_option transport
+disable_option bare
+
+switch -- $subcommand {
+browser -
+blame {
+       enable_option bare
+
+       disable_option multicommit
+       disable_option branch
+       disable_option transport
+}
+citool {
+       enable_option singlecommit
+
+       disable_option multicommit
+       disable_option branch
+       disable_option transport
+}
+}
 
 ######################################################################
 ##
@@ -329,19 +691,24 @@ if {![file isdirectory $_gitdir]} {
        error_popup "Git directory not found:\n\n$_gitdir"
        exit 1
 }
-if {[lindex [file split $_gitdir] end] ne {.git}} {
-       catch {wm withdraw .}
-       error_popup "Cannot use funny .git directory:\n\n$_gitdir"
-       exit 1
+if {![is_enabled bare]} {
+       if {[lindex [file split $_gitdir] end] ne {.git}} {
+               catch {wm withdraw .}
+               error_popup "Cannot use funny .git directory:\n\n$_gitdir"
+               exit 1
+       }
+       if {[catch {cd [file dirname $_gitdir]} err]} {
+               catch {wm withdraw .}
+               error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
+               exit 1
+       }
 }
-if {[catch {cd [file dirname $_gitdir]} err]} {
-       catch {wm withdraw .}
-       error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
-       exit 1
+set _reponame [file split [file normalize $_gitdir]]
+if {[lindex $_reponame end] eq {.git}} {
+       set _reponame [lindex $_reponame end-1]
+} else {
+       set _reponame [lindex $_reponame end]
 }
-set _reponame [lindex [file split \
-       [file normalize [file dirname $_gitdir]]] \
-       end]
 
 ######################################################################
 ##
@@ -350,7 +717,6 @@ set _reponame [lindex [file split \
 set current_diff_path {}
 set current_diff_side {}
 set diff_actions [list]
-set ui_status_value {Initializing...}
 
 set HEAD {}
 set PARENT {}
@@ -358,6 +724,7 @@ set MERGE_HEAD [list]
 set commit_type {}
 set empty_tree {}
 set current_branch {}
+set is_detached 0
 set current_diff_path {}
 set selected_commit_type new
 
@@ -407,15 +774,7 @@ proc repository_state {ctvar hdvar mhvar} {
 
        set mh [list]
 
-       if {[catch {set current_branch [git symbolic-ref HEAD]}]} {
-               set current_branch {}
-       } else {
-               regsub ^refs/((heads|tags|remotes)/)? \
-                       $current_branch \
-                       {} \
-                       current_branch
-       }
-
+       load_current_branch
        if {[catch {set hd [git rev-parse --verify HEAD]}]} {
                set hd {}
                set ct initial
@@ -451,7 +810,7 @@ proc PARENT {} {
 
 proc rescan {after {honor_trustmtime 1}} {
        global HEAD PARENT MERGE_HEAD commit_type
-       global ui_index ui_workdir ui_status_value ui_comm
+       global ui_index ui_workdir ui_comm
        global rescan_active file_states
        global repo_config
 
@@ -470,8 +829,9 @@ proc rescan {after {honor_trustmtime 1}} {
 
        array unset file_states
 
-       if {![$ui_comm edit modified]
-               || [string trim [$ui_comm get 0.0 end]] eq {}} {
+       if {!$::GITGUI_BCK_exists &&
+               (![$ui_comm edit modified]
+               || [string trim [$ui_comm get 0.0 end]] eq {})} {
                if {[string match amend* $commit_type]} {
                } elseif {[load_message GITGUI_MSG]} {
                } elseif {[load_message MERGE_MSG]} {
@@ -481,22 +841,17 @@ proc rescan {after {honor_trustmtime 1}} {
                $ui_comm edit modified false
        }
 
-       if {[is_enabled branch]} {
-               load_all_heads
-               populate_branch_menu
-       }
-
        if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} {
                rescan_stage2 {} $after
        } else {
                set rescan_active 1
-               set ui_status_value {Refreshing file status...}
-               set cmd [list git update-index]
-               lappend cmd -q
-               lappend cmd --unmerged
-               lappend cmd --ignore-missing
-               lappend cmd --refresh
-               set fd_rf [open "| $cmd" r]
+               ui_status {Refreshing file status...}
+               set fd_rf [git_read update-index \
+                       -q \
+                       --unmerged \
+                       --ignore-missing \
+                       --refresh \
+                       ]
                fconfigure $fd_rf -blocking 0 -translation binary
                fileevent $fd_rf readable \
                        [list rescan_stage2 $fd_rf $after]
@@ -504,7 +859,6 @@ proc rescan {after {honor_trustmtime 1}} {
 }
 
 proc rescan_stage2 {fd after} {
-       global ui_status_value
        global rescan_active buf_rdi buf_rdf buf_rlo
 
        if {$fd ne {}} {
@@ -513,22 +867,25 @@ proc rescan_stage2 {fd after} {
                close $fd
        }
 
-       set ls_others [list | git ls-files --others -z \
-               --exclude-per-directory=.gitignore]
+       set ls_others [list --exclude-per-directory=.gitignore]
        set info_exclude [gitdir info exclude]
        if {[file readable $info_exclude]} {
                lappend ls_others "--exclude-from=$info_exclude"
        }
+       set user_exclude [get_config core.excludesfile]
+       if {$user_exclude ne {} && [file readable $user_exclude]} {
+               lappend ls_others "--exclude-from=$user_exclude"
+       }
 
        set buf_rdi {}
        set buf_rdf {}
        set buf_rlo {}
 
        set rescan_active 3
-       set ui_status_value {Scanning for modified files ...}
-       set fd_di [open "| git diff-index --cached -z [PARENT]" r]
-       set fd_df [open "| git diff-files -z" r]
-       set fd_lo [open $ls_others r]
+       ui_status {Scanning for modified files ...}
+       set fd_di [git_read diff-index --cached -z [PARENT]]
+       set fd_df [git_read diff-files -z]
+       set fd_lo [eval git_read ls-files --others -z $ls_others]
 
        fconfigure $fd_di -blocking 0 -translation binary -encoding binary
        fconfigure $fd_df -blocking 0 -translation binary -encoding binary
@@ -546,6 +903,7 @@ proc load_message {file} {
                if {[catch {set fd [open $f r]}]} {
                        return 0
                }
+               fconfigure $fd -eofchar {}
                set content [string trim [read $fd]]
                close $fd
                regsub -all -line {[ \r\t]+$} $content {} content
@@ -685,6 +1043,14 @@ proc mapdesc {state path} {
        return $r
 }
 
+proc ui_status {msg} {
+       $::main_status show $msg
+}
+
+proc ui_ready {{test {}}} {
+       $::main_status show {Ready.} $test
+}
+
 proc escape_path {path} {
        regsub -all {\\} $path "\\\\" path
        regsub -all "\n" $path "\\n" path
@@ -930,32 +1296,6 @@ static unsigned char file_merge_bits[] = {
    0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
 } -maskdata $filemask
 
-set file_dir_data {
-#define file_width 18
-#define file_height 18
-static unsigned char file_bits[] = {
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00,
-  0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00,
-  0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00,
-  0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
-}
-image create bitmap file_dir -background white -foreground blue \
-       -data $file_dir_data -maskdata $file_dir_data
-unset file_dir_data
-
-set file_uplevel_data {
-#define up_width 15
-#define up_height 15
-static unsigned char up_bits[] = {
-  0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f,
-  0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01,
-  0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00};
-}
-image create bitmap file_uplevel -background white -foreground red \
-       -data $file_uplevel_data -maskdata $file_uplevel_data
-unset file_uplevel_data
-
 set ui_index .vpane.files.index.list
 set ui_workdir .vpane.files.workdir.list
 
@@ -1036,28 +1376,18 @@ proc incr_font_size {font {amt 1}} {
 set starting_gitk_msg {Starting gitk... please wait...}
 
 proc do_gitk {revs} {
-       global env ui_status_value starting_gitk_msg
-
        # -- Always start gitk through whatever we were loaded with.  This
        #    lets us bypass using shell process on Windows systems.
        #
-       set cmd [list [info nameofexecutable]]
-       set exe [gitexec gitk]
-       lappend cmd $exe
-       if {$revs ne {}} {
-               append cmd { }
-               append cmd $revs
-       }
-
+       set exe [file join [file dirname $::_git] gitk]
+       set cmd [list [info nameofexecutable] $exe]
        if {! [file exists $exe]} {
                error_popup "Unable to start gitk:\n\n$exe does not exist"
        } else {
-               eval exec $cmd &
-               set ui_status_value $starting_gitk_msg
+               eval exec $cmd $revs &
+               ui_status $::starting_gitk_msg
                after 10000 {
-                       if {$ui_status_value eq $starting_gitk_msg} {
-                               set ui_status_value {Ready.}
-                       }
+                       ui_ready $starting_gitk_msg
                }
        }
 }
@@ -1066,6 +1396,7 @@ set is_quitting 0
 
 proc do_quit {} {
        global ui_comm is_quitting repo_config commit_type
+       global GITGUI_BCK_exists GITGUI_BCK_i
 
        if {$is_quitting} return
        set is_quitting 1
@@ -1074,18 +1405,30 @@ proc do_quit {} {
                # -- Stash our current commit buffer.
                #
                set save [gitdir GITGUI_MSG]
-               set msg [string trim [$ui_comm get 0.0 end]]
-               regsub -all -line {[ \r\t]+$} $msg {} msg
-               if {(![string match amend* $commit_type]
-                       || [$ui_comm edit modified])
-                       && $msg ne {}} {
-                       catch {
-                               set fd [open $save w]
-                               puts -nonewline $fd $msg
-                               close $fd
-                       }
+               if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} {
+                       file rename -force [gitdir GITGUI_BCK] $save
+                       set GITGUI_BCK_exists 0
                } else {
-                       catch {file delete $save}
+                       set msg [string trim [$ui_comm get 0.0 end]]
+                       regsub -all -line {[ \r\t]+$} $msg {} msg
+                       if {(![string match amend* $commit_type]
+                               || [$ui_comm edit modified])
+                               && $msg ne {}} {
+                               catch {
+                                       set fd [open $save w]
+                                       puts -nonewline $fd $msg
+                                       close $fd
+                               }
+                       } else {
+                               catch {file delete $save}
+                       }
+               }
+
+               # -- Remove our editor backup, its not needed.
+               #
+               after cancel $GITGUI_BCK_i
+               if {$GITGUI_BCK_exists} {
+                       catch {file delete [gitdir GITGUI_BCK]}
                }
 
                # -- Stash our current window geometry into this repository.
@@ -1106,7 +1449,7 @@ proc do_quit {} {
 }
 
 proc do_rescan {} {
-       rescan {set ui_status_value {Ready.}}
+       rescan ui_ready
 }
 
 proc do_commit {} {
@@ -1141,12 +1484,12 @@ proc toggle_or_diff {w x y} {
                        update_indexinfo \
                                "Unstaging [short_path $path] from commit" \
                                [list $path] \
-                               [concat $after {set ui_status_value {Ready.}}]
+                               [concat $after [list ui_ready]]
                } elseif {$w eq $ui_workdir} {
                        update_index \
                                "Adding [short_path $path]" \
                                [list $path] \
-                               [concat $after {set ui_status_value {Ready.}}]
+                               [concat $after [list ui_ready]]
                }
        } else {
                show_diff $path $w $lno
@@ -1233,6 +1576,10 @@ foreach class {Button Checkbutton Entry Label
 }
 unset class
 
+if {[is_Windows] || [is_MacOSX]} {
+       option add *Menu.tearOff 0
+}
+
 if {[is_MacOSX]} {
        set M1B M1
        set M1T Cmd
@@ -1263,11 +1610,14 @@ proc apply_config {} {
        }
 }
 
+set default_config(merge.diffstat) true
 set default_config(merge.summary) false
 set default_config(merge.verbosity) 2
 set default_config(user.name) {}
 set default_config(user.email) {}
 
+set default_config(gui.matchtrackingbranch) false
+set default_config(gui.pruneduringfetch) false
 set default_config(gui.trustmtime) false
 set default_config(gui.diffcontext) 5
 set default_config(gui.newbranchtemplate) {}
@@ -1280,43 +1630,6 @@ set font_descs {
 load_config 0
 apply_config
 
-######################################################################
-##
-## feature option selection
-
-if {[regexp {^git-(.+)$} [appname] _junk subcommand]} {
-       unset _junk
-} else {
-       set subcommand gui
-}
-if {$subcommand eq {gui.sh}} {
-       set subcommand gui
-}
-if {$subcommand eq {gui} && [llength $argv] > 0} {
-       set subcommand [lindex $argv 0]
-       set argv [lrange $argv 1 end]
-}
-
-enable_option multicommit
-enable_option branch
-enable_option transport
-
-switch -- $subcommand {
-browser -
-blame {
-       disable_option multicommit
-       disable_option branch
-       disable_option transport
-}
-citool {
-       enable_option singlecommit
-
-       disable_option multicommit
-       disable_option branch
-       disable_option transport
-}
-}
-
 ######################################################################
 ##
 ## ui construction
@@ -1346,20 +1659,32 @@ if {[is_enabled transport]} {
 menu .mbar.repository
 
 .mbar.repository add command \
-       -label {Browse Current Branch} \
+       -label {Browse Current Branch's Files} \
        -command {browser::new $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
+set ui_browse_current [.mbar.repository index last]
+.mbar.repository add command \
+       -label {Browse Branch Files...} \
+       -command browser_open::dialog
 .mbar.repository add separator
 
 .mbar.repository add command \
-       -label {Visualize Current Branch} \
+       -label {Visualize Current Branch's History} \
        -command {do_gitk $current_branch}
-trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
+set ui_visualize_current [.mbar.repository index last]
 .mbar.repository add command \
-       -label {Visualize All Branches} \
+       -label {Visualize All Branch History} \
        -command {do_gitk --all}
 .mbar.repository add separator
 
+proc current_branch_write {args} {
+       global current_branch
+       .mbar.repository entryconf $::ui_browse_current \
+               -label "Browse $current_branch's Files"
+       .mbar.repository entryconf $::ui_visualize_current \
+               -label "Visualize $current_branch's History"
+}
+trace add variable current_branch write current_branch_write
+
 if {[is_enabled multicommit]} {
        .mbar.repository add command -label {Database Statistics} \
                -command do_stats
@@ -1424,13 +1749,24 @@ if {[is_enabled branch]} {
        menu .mbar.branch
 
        .mbar.branch add command -label {Create...} \
-               -command do_create_branch \
+               -command branch_create::dialog \
                -accelerator $M1T-N
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 
+       .mbar.branch add command -label {Checkout...} \
+               -command branch_checkout::dialog \
+               -accelerator $M1T-O
+       lappend disable_on_lock [list .mbar.branch entryconf \
+               [.mbar.branch index last] -state]
+
+       .mbar.branch add command -label {Rename...} \
+               -command branch_rename::dialog
+       lappend disable_on_lock [list .mbar.branch entryconf \
+               [.mbar.branch index last] -state]
+
        .mbar.branch add command -label {Delete...} \
-               -command do_delete_branch
+               -command branch_delete::dialog
        lappend disable_on_lock [list .mbar.branch entryconf \
                [.mbar.branch index last] -state]
 
@@ -1469,12 +1805,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
-       .mbar.commit add command -label {Add To Commit} \
+       .mbar.commit add command -label {Stage To Commit} \
                -command do_add_selection
        lappend disable_on_lock \
                [list .mbar.commit entryconf [.mbar.commit index last] -state]
 
-       .mbar.commit add command -label {Add Existing To Commit} \
+       .mbar.commit add command -label {Stage Changed Files To Commit} \
                -command do_add_all \
                -accelerator $M1T-I
        lappend disable_on_lock \
@@ -1508,14 +1844,14 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} {
 if {[is_enabled branch]} {
        menu .mbar.merge
        .mbar.merge add command -label {Local Merge...} \
-               -command merge::dialog
+               -command merge::dialog \
+               -accelerator $M1T-M
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
        .mbar.merge add command -label {Abort Merge...} \
                -command merge::reset_hard
        lappend disable_on_lock \
                [list .mbar.merge entryconf [.mbar.merge index last] -state]
-
 }
 
 # -- Transport Menu
@@ -1527,6 +1863,8 @@ if {[is_enabled transport]} {
        .mbar.push add command -label {Push...} \
                -command do_push_anywhere \
                -accelerator $M1T-P
+       .mbar.push add command -label {Delete...} \
+               -command remote_branch_delete::dialog
 }
 
 if {[is_MacOSX]} {
@@ -1545,34 +1883,6 @@ if {[is_MacOSX]} {
        .mbar.edit add separator
        .mbar.edit add command -label {Options...} \
                -command do_options
-
-       # -- Tools Menu
-       #
-       if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} {
-       proc do_miga {} {
-               global ui_status_value
-               if {![lock_index update]} return
-               set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""]
-               set miga_fd [open "|$cmd" r]
-               fconfigure $miga_fd -blocking 0
-               fileevent $miga_fd readable [list miga_done $miga_fd]
-               set ui_status_value {Running miga...}
-       }
-       proc miga_done {fd} {
-               read $fd 512
-               if {[eof $fd]} {
-                       close $fd
-                       unlock_index
-                       rescan [list set ui_status_value {Ready.}]
-               }
-       }
-       .mbar add cascade -label Tools -menu .mbar.tools
-       menu .mbar.tools
-       .mbar.tools add command -label "Migrate" \
-               -command do_miga
-       lappend disable_on_lock \
-               [list .mbar.tools entryconf [.mbar.tools index last] -state]
-       }
 }
 
 # -- Help Menu
@@ -1640,24 +1950,10 @@ proc usage {} {
 # -- Not a normal commit type invocation?  Do that instead!
 #
 switch -- $subcommand {
-browser {
-       set subcommand_args {rev?}
-       switch [llength $argv] {
-       0 {
-               set current_branch [git symbolic-ref HEAD]
-               regsub ^refs/((heads|tags|remotes)/)? \
-                       $current_branch {} current_branch
-       }
-       1 {
-               set current_branch [lindex $argv 0]
-       }
-       default usage
-       }
-       browser::new $current_branch
-       return
-}
+browser -
 blame {
-       set subcommand_args {rev? path?}
+       set subcommand_args {rev? path}
+       if {$argv eq {}} usage
        set head {}
        set path {}
        set is_path 0
@@ -1676,22 +1972,52 @@ blame {
                } elseif {$head eq {}} {
                        if {$head ne {}} usage
                        set head $a
+                       set is_path 1
                } else {
                        usage
                }
        }
        unset is_path
 
+       if {$head ne {} && $path eq {}} {
+               set path $_prefix$head
+               set head {}
+       }
+
        if {$head eq {}} {
-               set current_branch [git symbolic-ref HEAD]
-               regsub ^refs/((heads|tags|remotes)/)? \
-                       $current_branch {} current_branch
+               load_current_branch
        } else {
+               if {[regexp {^[0-9a-f]{1,39}$} $head]} {
+                       if {[catch {
+                                       set head [git rev-parse --verify $head]
+                               } err]} {
+                               puts stderr $err
+                               exit 1
+                       }
+               }
                set current_branch $head
        }
 
-       if {$path eq {}} usage
-       blame::new $head $path
+       switch -- $subcommand {
+       browser {
+               if {$head eq {}} {
+                       if {$path ne {} && [file isdirectory $path]} {
+                               set head $current_branch
+                       } else {
+                               set head $path
+                               set path {}
+                       }
+               }
+               browser::new $head $path
+       }
+       blame   {
+               if {$head eq {} && ![file exists $path]} {
+                       puts stderr "fatal: cannot stat path $path: No such file or directory"
+                       exit 1
+               }
+               blame::new $head $path
+       }
+       }
        return
 }
 citool -
@@ -1806,7 +2132,7 @@ pack .vpane.lower.commarea.buttons.rescan -side top -fill x
 lappend disable_on_lock \
        {.vpane.lower.commarea.buttons.rescan conf -state}
 
-button .vpane.lower.commarea.buttons.incall -text {Add Existing} \
+button .vpane.lower.commarea.buttons.incall -text {Stage Changed} \
        -command do_add_all
 pack .vpane.lower.commarea.buttons.incall -side top -fill x
 lappend disable_on_lock \
@@ -2080,26 +2406,31 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
 $ctxm add separator
 $ctxm add command -label {Options...} \
        -command do_options
-bind_button3 $ui_diff "
-       set cursorX %x
-       set cursorY %y
-       if {\$ui_index eq \$current_diff_side} {
-               $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit}
+proc popup_diff_menu {ctxm x y X Y} {
+       set ::cursorX $x
+       set ::cursorY $y
+       if {$::ui_index eq $::current_diff_side} {
+               $ctxm entryconf $::ui_diff_applyhunk \
+                       -state normal \
+                       -label {Unstage Hunk From Commit}
+       } elseif {{_O} eq [lindex $::file_states($::current_diff_path) 0]} {
+               $ctxm entryconf $::ui_diff_applyhunk \
+                       -state disabled \
+                       -label {Stage Hunk For Commit}
        } else {
-               $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
+               $ctxm entryconf $::ui_diff_applyhunk \
+                       -state normal \
+                       -label {Stage Hunk For Commit}
        }
-       tk_popup $ctxm %X %Y
-"
-unset ui_diff_applyhunk
+       tk_popup $ctxm $X $Y
+}
+bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y]
 
 # -- Status Bar
 #
-label .status -textvariable ui_status_value \
-       -anchor w \
-       -justify left \
-       -borderwidth 1 \
-       -relief sunken
+set main_status [::status_bar::new .status]
 pack .status -anchor w -side bottom -fill x
+$main_status show {Initializing...}
 
 # -- Load geometry
 #
@@ -2150,8 +2481,12 @@ bind $ui_diff <Control-Key-f> {catch {%W yview scroll  1 pages};break}
 bind $ui_diff <Button-1>   {focus %W}
 
 if {[is_enabled branch]} {
-       bind . <$M1B-Key-n> do_create_branch
-       bind . <$M1B-Key-N> do_create_branch
+       bind . <$M1B-Key-n> branch_create::dialog
+       bind . <$M1B-Key-N> branch_create::dialog
+       bind . <$M1B-Key-o> branch_checkout::dialog
+       bind . <$M1B-Key-O> branch_checkout::dialog
+       bind . <$M1B-Key-m> merge::dialog
+       bind . <$M1B-Key-M> merge::dialog
 }
 if {[is_enabled transport]} {
        bind . <$M1B-Key-p> do_push_anywhere
@@ -2238,31 +2573,69 @@ user.email settings into your personal
 #
 if {[is_enabled transport]} {
        load_all_remotes
-       load_all_heads
 
-       populate_branch_menu
        populate_fetch_menu
        populate_push_menu
 }
 
-# -- Only suggest a gc run if we are going to stay running.
-#
-if {[is_enabled multicommit]} {
-       set object_limit 2000
-       if {[is_Windows]} {set object_limit 200}
-       regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current
-       if {$objects_current >= $object_limit} {
-               if {[ask_popup \
-                       "This repository currently has $objects_current loose objects.
+if {[winfo exists $ui_comm]} {
+       set GITGUI_BCK_exists [load_message GITGUI_BCK]
+
+       # -- If both our backup and message files exist use the
+       #    newer of the two files to initialize the buffer.
+       #
+       if {$GITGUI_BCK_exists} {
+               set m [gitdir GITGUI_MSG]
+               if {[file isfile $m]} {
+                       if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} {
+                               catch {file delete [gitdir GITGUI_MSG]}
+                       } else {
+                               $ui_comm delete 0.0 end
+                               $ui_comm edit reset
+                               $ui_comm edit modified false
+                               catch {file delete [gitdir GITGUI_BCK]}
+                               set GITGUI_BCK_exists 0
+                       }
+               }
+               unset m
+       }
 
-To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
+       proc backup_commit_buffer {} {
+               global ui_comm GITGUI_BCK_exists
 
-Compress the database now?"] eq yes} {
-                       do_gc
+               set m [$ui_comm edit modified]
+               if {$m || $GITGUI_BCK_exists} {
+                       set msg [string trim [$ui_comm get 0.0 end]]
+                       regsub -all -line {[ \r\t]+$} $msg {} msg
+
+                       if {$msg eq {}} {
+                               if {$GITGUI_BCK_exists} {
+                                       catch {file delete [gitdir GITGUI_BCK]}
+                                       set GITGUI_BCK_exists 0
+                               }
+                       } elseif {$m} {
+                               catch {
+                                       set fd [open [gitdir GITGUI_BCK] w]
+                                       puts -nonewline $fd $msg
+                                       close $fd
+                                       set GITGUI_BCK_exists 1
+                               }
+                       }
+
+                       $ui_comm edit modified false
                }
+
+               set ::GITGUI_BCK_i [after 2000 backup_commit_buffer]
        }
-       unset object_limit _junk objects_current
+
+       backup_commit_buffer
 }
 
 lock_index begin-read
+if {![winfo ismapped .]} {
+       wm deiconify .
+}
 after 1 do_rescan
+if {[is_enabled multicommit]} {
+       after 1000 hint_gc
+}
index 1d2caac2835c27280e1d4b9ec58c51b746e2dd01..96072847a2ffeec814f499657744e5ed4f8988c0 100644 (file)
@@ -21,7 +21,7 @@ field w_amov     ; # text column: annotations + move tracking
 field w_asim     ; # text column: annotations (simple computation)
 field w_file     ; # text column: actual file data
 field w_cviewer  ; # pane showing commit message
-field status     ; # text variable bound to status bar
+field status     ; # status mega-widget instance
 field old_height ; # last known height of $w.file_pane
 
 # Tk UI colors
@@ -33,6 +33,13 @@ variable group_colors {
        #ececec
 }
 
+# Switches for original location detection
+#
+variable original_options [list -C -C]
+if {[git-version >= 1.5.3]} {
+       lappend original_options -w ; # ignore indentation changes
+}
+
 # Current blame data; cleared/reset on each load
 #
 field commit               ; # input commit to blame
@@ -235,14 +242,7 @@ constructor new {i_commit i_path} {
        pack $w.file_pane.cm.sbx -side bottom -fill x
        pack $w_cviewer -expand 1 -fill both
 
-       frame $w.status \
-               -borderwidth 1 \
-               -relief sunken
-       label $w.status.l \
-               -textvariable @status \
-               -anchor w \
-               -justify left
-       pack $w.status.l -side left
+       set status [::status_bar::new $w.status]
 
        menu $w.ctxm -tearoff 0
        $w.ctxm add command \
@@ -304,8 +304,9 @@ constructor new {i_commit i_path} {
 
        set req_w [winfo reqwidth  $top]
        set req_h [winfo reqheight $top]
+       set scr_h [expr {[winfo screenheight $top] - 100}]
        if {$req_w < 600} {set req_w 600}
-       if {$req_h < 400} {set req_h 400}
+       if {$req_h < $scr_h} {set req_h $scr_h}
        set g "${req_w}x${req_h}"
        wm geometry $top $g
        update
@@ -352,19 +353,6 @@ method _load {jump} {
                set total_lines 0
        }
 
-       if {[winfo exists $w.status.c]} {
-               $w.status.c coords bar 0 0 0 20
-       } else {
-               canvas $w.status.c \
-                       -width 100 \
-                       -height [expr {int([winfo reqheight $w.status.l] * 0.6)}] \
-                       -borderwidth 1 \
-                       -relief groove \
-                       -highlightt 0
-               $w.status.c create rectangle 0 0 0 20 -tags bar -fill navy
-               pack $w.status.c -side right
-       }
-
        if {$history eq {}} {
                $w_back conf -state disabled
        } else {
@@ -378,13 +366,13 @@ method _load {jump} {
        set amov_data [list [list]]
        set asim_data [list [list]]
 
-       set status "Loading $commit:[escape_path $path]..."
+       $status show "Reading $commit:[escape_path $path]..."
        $w_path conf -text [escape_path $path]
        if {$commit eq {}} {
                set fd [open $path r]
+               fconfigure $fd -eofchar {}
        } else {
-               set cmd [list git cat-file blob "$commit:$path"]
-               set fd [open "| $cmd" r]
+               set fd [git_read cat-file blob "$commit:$path"]
        }
        fconfigure $fd -blocking 0 -translation lf -encoding binary
        fileevent $fd readable [cb _read_file $fd $jump]
@@ -487,30 +475,28 @@ method _read_file {fd jump} {
 } ifdeleted { catch {close $fd} }
 
 method _exec_blame {cur_w cur_d options cur_s} {
-       set cmd [list]
-       if {![is_Windows] || [is_Cygwin]} {
-               lappend cmd nice
-       }
-       lappend cmd git blame
-       set cmd [concat $cmd $options]
-       lappend cmd --incremental
+       lappend options --incremental
        if {$commit eq {}} {
-               lappend cmd --contents $path
+               lappend options --contents $path
        } else {
-               lappend cmd $commit
+               lappend options $commit
        }
-       lappend cmd -- $path
-       set fd [open "| $cmd" r]
+       lappend options -- $path
+       set fd [eval git_read --nice blame $options]
        fconfigure $fd -blocking 0 -translation lf -encoding binary
-       fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d $cur_s]
+       fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d]
        set current_fd $fd
        set blame_lines 0
-       _status $this $cur_s
+
+       $status start \
+               "Loading$cur_s annotations..." \
+               {lines annotated}
 }
 
-method _read_blame {fd cur_w cur_d cur_s} {
+method _read_blame {fd cur_w cur_d} {
        upvar #0 $cur_d line_data
        variable group_colors
+       variable original_options
 
        if {$fd ne $current_fd} {
                catch {close $fd}
@@ -684,30 +670,17 @@ method _read_blame {fd cur_w cur_d cur_s} {
                close $fd
                if {$cur_w eq $w_asim} {
                        _exec_blame $this $w_amov @amov_data \
-                               [list -M -C -C] \
+                               $original_options \
                                { original location}
                } else {
                        set current_fd {}
-                       set status {Annotation complete.}
-                       destroy $w.status.c
+                       $status stop {Annotation complete.}
                }
        } else {
-               _status $this $cur_s
+               $status update $blame_lines $total_lines
        }
 } ifdeleted { catch {close $fd} }
 
-method _status {cur_s} {
-       set have  $blame_lines
-       set total $total_lines
-       set pdone 0
-       if {$total} {set pdone [expr {100 * $have / $total}]}
-
-       set status [format \
-               "Loading%s annotations... %i of %i lines annotated (%2i%%)" \
-               $cur_s $have $total $pdone]
-       $w.status.c coords bar 0 0 $pdone 20
-}
-
 method _click {cur_w pos} {
        set lno [lindex [split [$cur_w index $pos] .] 0]
        _showcommit $this $cur_w $lno
@@ -788,7 +761,7 @@ method _showcommit {cur_w lno} {
                if {[catch {set msg $header($cmit,message)}]} {
                        set msg {}
                        catch {
-                               set fd [open "| git cat-file commit $cmit" r]
+                               set fd [git_read cat-file commit $cmit]
                                fconfigure $fd -encoding binary -translation lf
                                if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
                                        set enc utf-8
@@ -798,15 +771,20 @@ method _showcommit {cur_w lno} {
                                                set enc [string tolower [string range $line 9 end]]
                                        }
                                }
-                               set msg [encoding convertfrom $enc [read $fd]]
-                               set msg [string trim $msg]
+                               set msg [read $fd]
                                close $fd
 
-                               set author_name [encoding convertfrom $enc $author_name]
-                               set committer_name [encoding convertfrom $enc $committer_name]
-
-                               set header($cmit,author) $author_name
-                               set header($cmit,committer) $committer_name
+                               set enc [tcl_encoding $enc]
+                               if {$enc ne {}} {
+                                       set msg [encoding convertfrom $enc $msg]
+                                       set author_name [encoding convertfrom $enc $author_name]
+                                       set committer_name [encoding convertfrom $enc $committer_name]
+                                       set header($cmit,author) $author_name
+                                       set header($cmit,committer) $committer_name
+                                       set header($cmit,summary) \
+                                       [encoding convertfrom $enc $header($cmit,summary)]
+                               }
+                               set msg [string trim $msg]
                        }
                        set header($cmit,message) $msg
                }
@@ -901,6 +879,11 @@ method _open_tooltip {cur_w} {
                set org [lindex $amov_data $lno]
        }
 
+       if {$dat eq {}} {
+               _hide_tooltip $this
+               return
+       }
+
        set cmit [lindex $dat 0]
        set tooltip_commit [list $cmit]
 
index 4f648b2bc7a52e965ee62bcce7687c0e356f3f31..777eeb79c1355ec49ce175cc5c33a13df6e41c97 100644 (file)
 # Copyright (C) 2006, 2007 Shawn Pearce
 
 proc load_all_heads {} {
-       global all_heads
+       global some_heads_tracking
 
+       set rh refs/heads
+       set rh_len [expr {[string length $rh] + 1}]
        set all_heads [list]
-       set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
+       set fd [git_read for-each-ref --format=%(refname) $rh]
        while {[gets $fd line] > 0} {
-               if {[is_tracking_branch $line]} continue
-               if {![regsub ^refs/heads/ $line {} name]} continue
-               lappend all_heads $name
+               if {!$some_heads_tracking || ![is_tracking_branch $line]} {
+                       lappend all_heads [string range $line $rh_len end]
+               }
        }
        close $fd
 
-       set all_heads [lsort $all_heads]
+       return [lsort $all_heads]
 }
 
 proc load_all_tags {} {
        set all_tags [list]
-       set fd [open "| git for-each-ref --format=%(refname) refs/tags" r]
+       set fd [git_read for-each-ref \
+               --sort=-taggerdate \
+               --format=%(refname) \
+               refs/tags]
        while {[gets $fd line] > 0} {
                if {![regsub ^refs/tags/ $line {} name]} continue
                lappend all_tags $name
        }
        close $fd
-
-       return [lsort $all_tags]
-}
-
-proc populate_branch_menu {} {
-       global all_heads disable_on_lock
-
-       set m .mbar.branch
-       set last [$m index last]
-       for {set i 0} {$i <= $last} {incr i} {
-               if {[$m type $i] eq {separator}} {
-                       $m delete $i last
-                       set new_dol [list]
-                       foreach a $disable_on_lock {
-                               if {[lindex $a 0] ne $m || [lindex $a 2] < $i} {
-                                       lappend new_dol $a
-                               }
-                       }
-                       set disable_on_lock $new_dol
-                       break
-               }
-       }
-
-       if {$all_heads ne {}} {
-               $m add separator
-       }
-       foreach b $all_heads {
-               $m add radiobutton \
-                       -label $b \
-                       -command [list switch_branch $b] \
-                       -variable current_branch \
-                       -value $b
-               lappend disable_on_lock \
-                       [list $m entryconf [$m index last] -state]
-       }
-}
-
-proc do_create_branch_action {w} {
-       global all_heads null_sha1 repo_config
-       global create_branch_checkout create_branch_revtype
-       global create_branch_head create_branch_trackinghead
-       global create_branch_name create_branch_revexp
-       global create_branch_tag
-
-       set newbranch $create_branch_name
-       if {$newbranch eq {}
-               || $newbranch eq $repo_config(gui.newbranchtemplate)} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Please supply a branch name."
-               focus $w.desc.name_t
-               return
-       }
-       if {![catch {git show-ref --verify -- "refs/heads/$newbranch"}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Branch '$newbranch' already exists."
-               focus $w.desc.name_t
-               return
-       }
-       if {[catch {git check-ref-format "heads/$newbranch"}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "We do not like '$newbranch' as a branch name."
-               focus $w.desc.name_t
-               return
-       }
-
-       set rev {}
-       switch -- $create_branch_revtype {
-       head {set rev $create_branch_head}
-       tracking {set rev $create_branch_trackinghead}
-       tag {set rev $create_branch_tag}
-       expression {set rev $create_branch_revexp}
-       }
-       if {[catch {set cmt [git rev-parse --verify "${rev}^0"]}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Invalid starting revision: $rev"
-               return
-       }
-       if {[catch {
-                       git update-ref \
-                               -m "branch: Created from $rev" \
-                               "refs/heads/$newbranch" \
-                               $cmt \
-                               $null_sha1
-               } err]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Failed to create '$newbranch'.\n\n$err"
-               return
-       }
-
-       lappend all_heads $newbranch
-       set all_heads [lsort $all_heads]
-       populate_branch_menu
-       destroy $w
-       if {$create_branch_checkout} {
-               switch_branch $newbranch
-       }
+       return $all_tags
 }
 
 proc radio_selector {varname value args} {
        upvar #0 $varname var
        set var $value
 }
-
-trace add variable create_branch_head write \
-       [list radio_selector create_branch_revtype head]
-trace add variable create_branch_trackinghead write \
-       [list radio_selector create_branch_revtype tracking]
-trace add variable create_branch_tag write \
-       [list radio_selector create_branch_revtype tag]
-
-trace add variable delete_branch_head write \
-       [list radio_selector delete_branch_checktype head]
-trace add variable delete_branch_trackinghead write \
-       [list radio_selector delete_branch_checktype tracking]
-
-proc do_create_branch {} {
-       global all_heads current_branch repo_config
-       global create_branch_checkout create_branch_revtype
-       global create_branch_head create_branch_trackinghead
-       global create_branch_name create_branch_revexp
-       global create_branch_tag
-
-       set w .branch_editor
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text {Create New Branch} \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.create -text Create \
-               -default active \
-               -command [list do_create_branch_action $w]
-       pack $w.buttons.create -side right
-       button $w.buttons.cancel -text {Cancel} \
-               -command [list destroy $w]
-       pack $w.buttons.cancel -side right -padx 5
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       labelframe $w.desc -text {Branch Description}
-       label $w.desc.name_l -text {Name:}
-       entry $w.desc.name_t \
-               -borderwidth 1 \
-               -relief sunken \
-               -width 40 \
-               -textvariable create_branch_name \
-               -validate key \
-               -validatecommand {
-                       if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
-                       return 1
-               }
-       grid $w.desc.name_l $w.desc.name_t -sticky we -padx {0 5}
-       grid columnconfigure $w.desc 1 -weight 1
-       pack $w.desc -anchor nw -fill x -pady 5 -padx 5
-
-       labelframe $w.from -text {Starting Revision}
-       if {$all_heads ne {}} {
-               radiobutton $w.from.head_r \
-                       -text {Local Branch:} \
-                       -value head \
-                       -variable create_branch_revtype
-               eval tk_optionMenu $w.from.head_m create_branch_head $all_heads
-               grid $w.from.head_r $w.from.head_m -sticky w
-       }
-       set all_trackings [all_tracking_branches]
-       if {$all_trackings ne {}} {
-               set create_branch_trackinghead [lindex $all_trackings 0]
-               radiobutton $w.from.tracking_r \
-                       -text {Tracking Branch:} \
-                       -value tracking \
-                       -variable create_branch_revtype
-               eval tk_optionMenu $w.from.tracking_m \
-                       create_branch_trackinghead \
-                       $all_trackings
-               grid $w.from.tracking_r $w.from.tracking_m -sticky w
-       }
-       set all_tags [load_all_tags]
-       if {$all_tags ne {}} {
-               set create_branch_tag [lindex $all_tags 0]
-               radiobutton $w.from.tag_r \
-                       -text {Tag:} \
-                       -value tag \
-                       -variable create_branch_revtype
-               eval tk_optionMenu $w.from.tag_m create_branch_tag $all_tags
-               grid $w.from.tag_r $w.from.tag_m -sticky w
-       }
-       radiobutton $w.from.exp_r \
-               -text {Revision Expression:} \
-               -value expression \
-               -variable create_branch_revtype
-       entry $w.from.exp_t \
-               -borderwidth 1 \
-               -relief sunken \
-               -width 50 \
-               -textvariable create_branch_revexp \
-               -validate key \
-               -validatecommand {
-                       if {%d == 1 && [regexp {\s} %S]} {return 0}
-                       if {%d == 1 && [string length %S] > 0} {
-                               set create_branch_revtype expression
-                       }
-                       return 1
-               }
-       grid $w.from.exp_r $w.from.exp_t -sticky we -padx {0 5}
-       grid columnconfigure $w.from 1 -weight 1
-       pack $w.from -anchor nw -fill x -pady 5 -padx 5
-
-       labelframe $w.postActions -text {Post Creation Actions}
-       checkbutton $w.postActions.checkout \
-               -text {Checkout after creation} \
-               -variable create_branch_checkout
-       pack $w.postActions.checkout -anchor nw
-       pack $w.postActions -anchor nw -fill x -pady 5 -padx 5
-
-       set create_branch_checkout 1
-       set create_branch_head $current_branch
-       set create_branch_revtype head
-       set create_branch_name $repo_config(gui.newbranchtemplate)
-       set create_branch_revexp {}
-
-       bind $w <Visibility> "
-               grab $w
-               $w.desc.name_t icursor end
-               focus $w.desc.name_t
-       "
-       bind $w <Key-Escape> "destroy $w"
-       bind $w <Key-Return> "do_create_branch_action $w;break"
-       wm title $w "[appname] ([reponame]): Create Branch"
-       tkwait window $w
-}
-
-proc do_delete_branch_action {w} {
-       global all_heads
-       global delete_branch_checktype delete_branch_head delete_branch_trackinghead
-
-       set check_rev {}
-       switch -- $delete_branch_checktype {
-       head {set check_rev $delete_branch_head}
-       tracking {set check_rev $delete_branch_trackinghead}
-       always {set check_rev {:none}}
-       }
-       if {$check_rev eq {:none}} {
-               set check_cmt {}
-       } elseif {[catch {set check_cmt [git rev-parse --verify "${check_rev}^0"]}]} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Invalid check revision: $check_rev"
-               return
-       }
-
-       set to_delete [list]
-       set not_merged [list]
-       foreach i [$w.list.l curselection] {
-               set b [$w.list.l get $i]
-               if {[catch {set o [git rev-parse --verify $b]}]} continue
-               if {$check_cmt ne {}} {
-                       if {$b eq $check_rev} continue
-                       if {[catch {set m [git merge-base $o $check_cmt]}]} continue
-                       if {$o ne $m} {
-                               lappend not_merged $b
-                               continue
-                       }
-               }
-               lappend to_delete [list $b $o]
-       }
-       if {$not_merged ne {}} {
-               set msg "The following branches are not completely merged into $check_rev:
-
- - [join $not_merged "\n - "]"
-               tk_messageBox \
-                       -icon info \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message $msg
-       }
-       if {$to_delete eq {}} return
-       if {$delete_branch_checktype eq {always}} {
-               set msg {Recovering deleted branches is difficult.
-
-Delete the selected branches?}
-               if {[tk_messageBox \
-                       -icon warning \
-                       -type yesno \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message $msg] ne yes} {
-                       return
-               }
-       }
-
-       set failed {}
-       foreach i $to_delete {
-               set b [lindex $i 0]
-               set o [lindex $i 1]
-               if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
-                       append failed " - $b: $err\n"
-               } else {
-                       set x [lsearch -sorted -exact $all_heads $b]
-                       if {$x >= 0} {
-                               set all_heads [lreplace $all_heads $x $x]
-                       }
-               }
-       }
-
-       if {$failed ne {}} {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Failed to delete branches:\n$failed"
-       }
-
-       set all_heads [lsort $all_heads]
-       populate_branch_menu
-       destroy $w
-}
-
-proc do_delete_branch {} {
-       global all_heads tracking_branches current_branch
-       global delete_branch_checktype delete_branch_head delete_branch_trackinghead
-
-       set w .branch_editor
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
-
-       label $w.header -text {Delete Local Branch} \
-               -font font_uibold
-       pack $w.header -side top -fill x
-
-       frame $w.buttons
-       button $w.buttons.create -text Delete \
-               -command [list do_delete_branch_action $w]
-       pack $w.buttons.create -side right
-       button $w.buttons.cancel -text {Cancel} \
-               -command [list destroy $w]
-       pack $w.buttons.cancel -side right -padx 5
-       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
-
-       labelframe $w.list -text {Local Branches}
-       listbox $w.list.l \
-               -height 10 \
-               -width 70 \
-               -selectmode extended \
-               -yscrollcommand [list $w.list.sby set]
-       foreach h $all_heads {
-               if {$h ne $current_branch} {
-                       $w.list.l insert end $h
-               }
-       }
-       scrollbar $w.list.sby -command [list $w.list.l yview]
-       pack $w.list.sby -side right -fill y
-       pack $w.list.l -side left -fill both -expand 1
-       pack $w.list -fill both -expand 1 -pady 5 -padx 5
-
-       labelframe $w.validate -text {Delete Only If}
-       radiobutton $w.validate.head_r \
-               -text {Merged Into Local Branch:} \
-               -value head \
-               -variable delete_branch_checktype
-       eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads
-       grid $w.validate.head_r $w.validate.head_m -sticky w
-       set all_trackings [all_tracking_branches]
-       if {$all_trackings ne {}} {
-               set delete_branch_trackinghead [lindex $all_trackings 0]
-               radiobutton $w.validate.tracking_r \
-                       -text {Merged Into Tracking Branch:} \
-                       -value tracking \
-                       -variable delete_branch_checktype
-               eval tk_optionMenu $w.validate.tracking_m \
-                       delete_branch_trackinghead \
-                       $all_trackings
-               grid $w.validate.tracking_r $w.validate.tracking_m -sticky w
-       }
-       radiobutton $w.validate.always_r \
-               -text {Always (Do not perform merge checks)} \
-               -value always \
-               -variable delete_branch_checktype
-       grid $w.validate.always_r -columnspan 2 -sticky w
-       grid columnconfigure $w.validate 1 -weight 1
-       pack $w.validate -anchor nw -fill x -pady 5 -padx 5
-
-       set delete_branch_head $current_branch
-       set delete_branch_checktype head
-
-       bind $w <Visibility> "grab $w; focus $w"
-       bind $w <Key-Escape> "destroy $w"
-       wm title $w "[appname] ([reponame]): Delete Branch"
-       tkwait window $w
-}
-
-proc switch_branch {new_branch} {
-       global HEAD commit_type current_branch repo_config
-
-       if {![lock_index switch]} return
-
-       # -- Our in memory state should match the repository.
-       #
-       repository_state curType curHEAD curMERGE_HEAD
-       if {[string match amend* $commit_type]
-               && $curType eq {normal}
-               && $curHEAD eq $HEAD} {
-       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
-               info_popup {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.
-
-The rescan will be automatically started now.
-}
-               unlock_index
-               rescan {set ui_status_value {Ready.}}
-               return
-       }
-
-       # -- Don't do a pointless switch.
-       #
-       if {$current_branch eq $new_branch} {
-               unlock_index
-               return
-       }
-
-       if {$repo_config(gui.trustmtime) eq {true}} {
-               switch_branch_stage2 {} $new_branch
-       } else {
-               set ui_status_value {Refreshing file status...}
-               set cmd [list git update-index]
-               lappend cmd -q
-               lappend cmd --unmerged
-               lappend cmd --ignore-missing
-               lappend cmd --refresh
-               set fd_rf [open "| $cmd" r]
-               fconfigure $fd_rf -blocking 0 -translation binary
-               fileevent $fd_rf readable \
-                       [list switch_branch_stage2 $fd_rf $new_branch]
-       }
-}
-
-proc switch_branch_stage2 {fd_rf new_branch} {
-       global ui_status_value HEAD
-
-       if {$fd_rf ne {}} {
-               read $fd_rf
-               if {![eof $fd_rf]} return
-               close $fd_rf
-       }
-
-       set ui_status_value "Updating working directory to '$new_branch'..."
-       set cmd [list git read-tree]
-       lappend cmd -m
-       lappend cmd -u
-       lappend cmd --exclude-per-directory=.gitignore
-       lappend cmd $HEAD
-       lappend cmd $new_branch
-       set fd_rt [open "| $cmd" r]
-       fconfigure $fd_rt -blocking 0 -translation binary
-       fileevent $fd_rt readable \
-               [list switch_branch_readtree_wait $fd_rt $new_branch]
-}
-
-proc switch_branch_readtree_wait {fd_rt new_branch} {
-       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
-       global current_branch
-       global ui_comm ui_status_value
-
-       # -- We never get interesting output on stdout; only stderr.
-       #
-       read $fd_rt
-       fconfigure $fd_rt -blocking 1
-       if {![eof $fd_rt]} {
-               fconfigure $fd_rt -blocking 0
-               return
-       }
-
-       # -- The working directory wasn't in sync with the index and
-       #    we'd have to overwrite something to make the switch. A
-       #    merge is required.
-       #
-       if {[catch {close $fd_rt} err]} {
-               regsub {^fatal: } $err {} err
-               warn_popup "File level merge required.
-
-$err
-
-Staying on branch '$current_branch'."
-               set ui_status_value "Aborted checkout of '$new_branch' (file level merging is required)."
-               unlock_index
-               return
-       }
-
-       # -- Update the symbolic ref.  Core git doesn't even check for failure
-       #    here, it Just Works(tm).  If it doesn't we are in some really ugly
-       #    state that is difficult to recover from within git-gui.
-       #
-       if {[catch {git symbolic-ref HEAD "refs/heads/$new_branch"} err]} {
-               error_popup "Failed to set current branch.
-
-This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
-
-This should not have occurred.  [appname] will now close and give up.
-
-$err"
-               do_quit
-               return
-       }
-
-       # -- 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.
-       #
-       unlock_index
-       set selected_commit_type new
-       if {[string match amend* $commit_type]} {
-               $ui_comm delete 0.0 end
-               $ui_comm edit reset
-               $ui_comm edit modified false
-               rescan {set ui_status_value "Checked out branch '$current_branch'."}
-       } else {
-               repository_state commit_type HEAD MERGE_HEAD
-               set PARENT $HEAD
-               set ui_status_value "Checked out branch '$current_branch'."
-       }
-}
diff --git a/git-gui/lib/branch_checkout.tcl b/git-gui/lib/branch_checkout.tcl
new file mode 100644 (file)
index 0000000..72c45b4
--- /dev/null
@@ -0,0 +1,89 @@
+# git-gui branch checkout support
+# Copyright (C) 2007 Shawn Pearce
+
+class branch_checkout {
+
+field w              ; # widget path
+field w_rev          ; # mega-widget to pick the initial revision
+
+field opt_fetch     1; # refetch tracking branch if used?
+field opt_detach    0; # force a detached head case?
+
+constructor dialog {} {
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Checkout Branch"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       label $w.header -text {Checkout Branch} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.create -text Checkout \
+               -default active \
+               -command [cb _checkout]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       set w_rev [::choose_rev::new $w.rev {Revision}]
+       $w_rev bind_listbox <Double-Button-1> [cb _checkout]
+       pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
+
+       labelframe $w.options -text {Options}
+
+       checkbutton $w.options.fetch \
+               -text {Fetch Tracking Branch} \
+               -variable @opt_fetch
+       pack $w.options.fetch -anchor nw
+
+       checkbutton $w.options.detach \
+               -text {Detach From Local Branch} \
+               -variable @opt_detach
+       pack $w.options.detach -anchor nw
+
+       pack $w.options -anchor nw -fill x -pady 5 -padx 5
+
+       bind $w <Visibility> [cb _visible]
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Key-Return> [cb _checkout]\;break
+       tkwait window $w
+}
+
+method _checkout {} {
+       set spec [$w_rev get_tracking_branch]
+       if {$spec ne {} && $opt_fetch} {
+               set new {}
+       } elseif {[catch {set new [$w_rev commit_or_die]}]} {
+               return
+       }
+
+       if {$opt_detach} {
+               set ref {}
+       } else {
+               set ref [$w_rev get_local_branch]
+       }
+
+       set co [::checkout_op::new [$w_rev get] $new $ref]
+       $co parent $w
+       $co enable_checkout 1
+       if {$spec ne {} && $opt_fetch} {
+               $co enable_fetch $spec
+       }
+
+       if {[$co run]} {
+               destroy $w
+       } else {
+               $w_rev focus_filter
+       }
+}
+
+method _visible {} {
+       grab $w
+       $w_rev focus_filter
+}
+
+}
diff --git a/git-gui/lib/branch_create.tcl b/git-gui/lib/branch_create.tcl
new file mode 100644 (file)
index 0000000..def615d
--- /dev/null
@@ -0,0 +1,220 @@
+# git-gui branch create support
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+class branch_create {
+
+field w              ; # widget path
+field w_rev          ; # mega-widget to pick the initial revision
+field w_name         ; # new branch name widget
+
+field name         {}; # name of the branch the user has chosen
+field name_type  user; # type of branch name to use
+
+field opt_merge    ff; # type of merge to apply to existing branch
+field opt_checkout  1; # automatically checkout the new branch?
+field opt_fetch     1; # refetch tracking branch if used?
+field reset_ok      0; # did the user agree to reset?
+
+constructor dialog {} {
+       global repo_config
+
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Create Branch"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       label $w.header -text {Create New Branch} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.create -text Create \
+               -default active \
+               -command [cb _create]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.desc -text {Branch Name}
+       radiobutton $w.desc.name_r \
+               -anchor w \
+               -text {Name:} \
+               -value user \
+               -variable @name_type
+       set w_name $w.desc.name_t
+       entry $w_name \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 40 \
+               -textvariable @name \
+               -validate key \
+               -validatecommand [cb _validate %d %S]
+       grid $w.desc.name_r $w_name -sticky we -padx {0 5}
+
+       radiobutton $w.desc.match_r \
+               -anchor w \
+               -text {Match Tracking Branch Name} \
+               -value match \
+               -variable @name_type
+       grid $w.desc.match_r -sticky we -padx {0 5} -columnspan 2
+
+       grid columnconfigure $w.desc 1 -weight 1
+       pack $w.desc -anchor nw -fill x -pady 5 -padx 5
+
+       set w_rev [::choose_rev::new $w.rev {Starting Revision}]
+       pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
+
+       labelframe $w.options -text {Options}
+
+       frame $w.options.merge
+       label $w.options.merge.l -text {Update Existing Branch:}
+       pack $w.options.merge.l -side left
+       radiobutton $w.options.merge.no \
+               -text No \
+               -value none \
+               -variable @opt_merge
+       pack $w.options.merge.no -side left
+       radiobutton $w.options.merge.ff \
+               -text {Fast Forward Only} \
+               -value ff \
+               -variable @opt_merge
+       pack $w.options.merge.ff -side left
+       radiobutton $w.options.merge.reset \
+               -text {Reset} \
+               -value reset \
+               -variable @opt_merge
+       pack $w.options.merge.reset -side left
+       pack $w.options.merge -anchor nw
+
+       checkbutton $w.options.fetch \
+               -text {Fetch Tracking Branch} \
+               -variable @opt_fetch
+       pack $w.options.fetch -anchor nw
+
+       checkbutton $w.options.checkout \
+               -text {Checkout After Creation} \
+               -variable @opt_checkout
+       pack $w.options.checkout -anchor nw
+       pack $w.options -anchor nw -fill x -pady 5 -padx 5
+
+       trace add variable @name_type write [cb _select]
+
+       set name $repo_config(gui.newbranchtemplate)
+       if {[is_config_true gui.matchtrackingbranch]} {
+               set name_type match
+       }
+
+       bind $w <Visibility> [cb _visible]
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Key-Return> [cb _create]\;break
+       tkwait window $w
+}
+
+method _create {} {
+       global repo_config
+       global M1B
+
+       set spec [$w_rev get_tracking_branch]
+       switch -- $name_type {
+       user {
+               set newbranch $name
+       }
+       match {
+               if {$spec eq {}} {
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [wm title $w] \
+                               -parent $w \
+                               -message "Please select a tracking branch."
+                       return
+               }
+               if {![regsub ^refs/heads/ [lindex $spec 2] {} newbranch]} {
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [wm title $w] \
+                               -parent $w \
+                               -message "Tracking branch [$w get] is not a branch in the remote repository."
+                       return
+               }
+       }
+       }
+
+       if {$newbranch eq {}
+               || $newbranch eq $repo_config(gui.newbranchtemplate)} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Please supply a branch name."
+               focus $w_name
+               return
+       }
+
+       if {[catch {git check-ref-format "heads/$newbranch"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "'$newbranch' is not an acceptable branch name."
+               focus $w_name
+               return
+       }
+
+       if {$spec ne {} && $opt_fetch} {
+               set new {}
+       } elseif {[catch {set new [$w_rev commit_or_die]}]} {
+               return
+       }
+
+       set co [::checkout_op::new \
+               [$w_rev get] \
+               $new \
+               refs/heads/$newbranch]
+       $co parent $w
+       $co enable_create   1
+       $co enable_merge    $opt_merge
+       $co enable_checkout $opt_checkout
+       if {$spec ne {} && $opt_fetch} {
+               $co enable_fetch $spec
+       }
+
+       if {[$co run]} {
+               destroy $w
+       } else {
+               focus $w_name
+       }
+}
+
+method _validate {d S} {
+       if {$d == 1} {
+               if {[regexp {[~^:?*\[\0- ]} $S]} {
+                       return 0
+               }
+               if {[string length $S] > 0} {
+                       set name_type user
+               }
+       }
+       return 1
+}
+
+method _select {args} {
+       if {$name_type eq {match}} {
+               $w_rev pick_tracking_branch
+       }
+}
+
+method _visible {} {
+       grab $w
+       if {$name_type eq {user}} {
+               $w_name icursor end
+               focus $w_name
+       }
+}
+
+}
diff --git a/git-gui/lib/branch_delete.tcl b/git-gui/lib/branch_delete.tcl
new file mode 100644 (file)
index 0000000..c7573c6
--- /dev/null
@@ -0,0 +1,149 @@
+# git-gui branch delete support
+# Copyright (C) 2007 Shawn Pearce
+
+class branch_delete {
+
+field w               ; # widget path
+field w_heads         ; # listbox of local head names
+field w_check         ; # revision picker for merge test
+field w_delete        ; # delete button
+
+constructor dialog {} {
+       global current_branch
+
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Delete Branch"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       label $w.header -text {Delete Local Branch} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       set w_delete $w.buttons.delete
+       button $w_delete \
+               -text Delete \
+               -default active \
+               -state disabled \
+               -command [cb _delete]
+       pack $w_delete -side right
+       button $w.buttons.cancel \
+               -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.list -text {Local Branches}
+       set w_heads $w.list.l
+       listbox $w_heads \
+               -height 10 \
+               -width 70 \
+               -selectmode extended \
+               -exportselection false \
+               -yscrollcommand [list $w.list.sby set]
+       scrollbar $w.list.sby -command [list $w.list.l yview]
+       pack $w.list.sby -side right -fill y
+       pack $w.list.l -side left -fill both -expand 1
+       pack $w.list -fill both -expand 1 -pady 5 -padx 5
+
+       set w_check [choose_rev::new \
+               $w.check \
+               {Delete Only If Merged Into} \
+               ]
+       $w_check none {Always (Do not perform merge test.)}
+       pack $w.check -anchor nw -fill x -pady 5 -padx 5
+
+       foreach h [load_all_heads] {
+               if {$h ne $current_branch} {
+                       $w_heads insert end $h
+               }
+       }
+
+       bind $w_heads <<ListboxSelect>> [cb _select]
+       bind $w <Visibility> "
+               grab $w
+               focus $w
+       "
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Key-Return> [cb _delete]\;break
+       tkwait window $w
+}
+
+method _select {} {
+       if {[$w_heads curselection] eq {}} {
+               $w_delete configure -state disabled
+       } else {
+               $w_delete configure -state normal
+       }
+}
+
+method _delete {} {
+       if {[catch {set check_cmt [$w_check commit_or_die]}]} {
+               return
+       }
+
+       set to_delete [list]
+       set not_merged [list]
+       foreach i [$w_heads curselection] {
+               set b [$w_heads get $i]
+               if {[catch {
+                       set o [git rev-parse --verify "refs/heads/$b"]
+               }]} continue
+               if {$check_cmt ne {}} {
+                       if {[catch {set m [git merge-base $o $check_cmt]}]} continue
+                       if {$o ne $m} {
+                               lappend not_merged $b
+                               continue
+                       }
+               }
+               lappend to_delete [list $b $o]
+       }
+       if {$not_merged ne {}} {
+               set msg "The following branches are not completely merged into [$w_check get]:
+
+ - [join $not_merged "\n - "]"
+               tk_messageBox \
+                       -icon info \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message $msg
+       }
+       if {$to_delete eq {}} return
+       if {$check_cmt eq {}} {
+               set msg {Recovering deleted branches is difficult.
+
+Delete the selected branches?}
+               if {[tk_messageBox \
+                       -icon warning \
+                       -type yesno \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message $msg] ne yes} {
+                       return
+               }
+       }
+
+       set failed {}
+       foreach i $to_delete {
+               set b [lindex $i 0]
+               set o [lindex $i 1]
+               if {[catch {git update-ref -d "refs/heads/$b" $o} err]} {
+                       append failed " - $b: $err\n"
+               }
+       }
+
+       if {$failed ne {}} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Failed to delete branches:\n$failed"
+       }
+
+       destroy $w
+}
+
+}
diff --git a/git-gui/lib/branch_rename.tcl b/git-gui/lib/branch_rename.tcl
new file mode 100644 (file)
index 0000000..1cadc31
--- /dev/null
@@ -0,0 +1,128 @@
+# git-gui branch rename support
+# Copyright (C) 2007 Shawn Pearce
+
+class branch_rename {
+
+field w
+field oldname
+field newname
+
+constructor dialog {} {
+       global current_branch
+
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Rename Branch"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       set oldname $current_branch
+       set newname [get_config gui.newbranchtemplate]
+
+       label $w.header -text {Rename Branch} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.rename -text Rename \
+               -default active \
+               -command [cb _rename]
+       pack $w.buttons.rename -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       frame $w.rename
+       label $w.rename.oldname_l -text {Branch:}
+       eval tk_optionMenu $w.rename.oldname_m @oldname [load_all_heads]
+
+       label $w.rename.newname_l -text {New Name:}
+       entry $w.rename.newname_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 40 \
+               -textvariable @newname \
+               -validate key \
+               -validatecommand {
+                       if {%d == 1 && [regexp {[~^:?*\[\0- ]} %S]} {return 0}
+                       return 1
+               }
+
+       grid $w.rename.oldname_l $w.rename.oldname_m -sticky w  -padx {0 5}
+       grid $w.rename.newname_l $w.rename.newname_t -sticky we -padx {0 5}
+       grid columnconfigure $w.rename 1 -weight 1
+       pack $w.rename -anchor nw -fill x -pady 5 -padx 5
+
+       bind $w <Key-Return> [cb _rename]
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Visibility> "
+               grab $w
+               $w.rename.newname_t icursor end
+               focus $w.rename.newname_t
+       "
+       tkwait window $w
+}
+
+method _rename {} {
+       global current_branch
+
+       if {$oldname eq {}} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Please select a branch to rename."
+               focus $w.rename.oldname_m
+               return
+       }
+       if {$newname eq {}
+               || $newname eq [get_config gui.newbranchtemplate]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Please supply a branch name."
+               focus $w.rename.newname_t
+               return
+       }
+       if {![catch {git show-ref --verify -- "refs/heads/$newname"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Branch '$newname' already exists."
+               focus $w.rename.newname_t
+               return
+       }
+       if {[catch {git check-ref-format "heads/$newname"}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "We do not like '$newname' as a branch name."
+               focus $w.rename.newname_t
+               return
+       }
+
+       if {[catch {git branch -m $oldname $newname} err]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Failed to rename '$oldname'.\n\n$err"
+               return
+       }
+
+       if {$current_branch eq $oldname} {
+               set current_branch $newname
+       }
+
+       destroy $w
+}
+
+}
index e612247c9eca4287f372c317260d82383133e419..888db3c889fabbeb221aa9d8c6aab044105f039b 100644 (file)
@@ -3,6 +3,13 @@
 
 class browser {
 
+image create photo ::browser::img_parent  -data {R0lGODlhEAAQAIUAAPwCBBxSHBxOHMTSzNzu3KzCtBRGHCSKFIzCjLzSxBQ2FAxGHDzCLCyeHBQ+FHSmfAwuFBxKLDSCNMzizISyjJzOnDSyLAw+FAQSDAQeDBxWJAwmDAQOBKzWrDymNAQaDAQODAwaDDyKTFSyXFTGTEy6TAQCBAQKDAwiFBQyHAwSFAwmHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAZ1QIBwSCwaj0hiQCBICpcDQsFgGAaIguhhi0gohIsrQEDYMhiNrRfgeAQC5fMCAolIDhD2hFI5WC4YRBkaBxsOE2l/RxsHHA4dHmkfRyAbIQ4iIyQlB5NFGCAACiakpSZEJyinTgAcKSesACorgU4mJ6uxR35BACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=}
+image create photo ::browser::img_rblob   -data {R0lGODlhEAAQAIUAAPwCBFxaXNze3Ly2rJSWjPz+/Ozq7GxqbJyanPT29HRydMzOzDQyNIyKjERCROTi3Pz69PTy7Pzy7PTu5Ozm3LyqlJyWlJSSjJSOhOzi1LyulPz27PTq3PTm1OzezLyqjIyKhJSKfOzaxPz29OzizLyidIyGdIyCdOTOpLymhOzavOTStMTCtMS+rMS6pMSynMSulLyedAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAaQQIAQECgajcNkQMBkDgKEQFK4LFgLhkMBIVUKroWEYlEgMLxbBKLQUBwc52HgAQ4LBo049atWQyIPA3pEdFcQEhMUFYNVagQWFxgZGoxfYRsTHB0eH5UJCJAYICEinUoPIxIcHCQkIiIllQYEGCEhJicoKYwPmiQeKisrKLFKLCwtLi8wHyUlMYwM0tPUDH5BACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=}
+image create photo ::browser::img_xblob   -data {R0lGODlhEAAQAIYAAPwCBFRWVFxaXNza3OTi3Nze3Ly2tJyanPz+/Ozq7GxubNzSxMzOzMTGxHRybDQyNLy+vHRydHx6fKSipISChIyKjGxqbERCRCwuLLy6vGRiZExKTCQiJAwKDLSytLy2rJSSlHx+fDw6PKyqrBQWFPTu5Ozm3LyulLS2tCQmJAQCBPTq3Ozi1MSynCwqLAQGBOTazOzizOzezLyqjBweHNzSvOzaxKyurHRuZNzOtLymhDw+PIyCdOzWvOTOpLyidNzKtOTStLyifMTCtMS+rLyedAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAfZgACCAAEChYeGg4oCAwQFjgYBBwGKggEECJkICQoIkwADCwwNDY2mDA4Lng8QDhESsLARExQVDhYXGBkWExIaGw8cHR4SCQQfFQ8eFgUgIQEiwiMSBMYfGB4atwEXDyQd0wQlJicPKAHoFyIpJCoeDgMrLC0YKBsX6i4kL+4OMDEyZijr5oLGNxUqUCioEcPGDAwjPNyI6MEDChQjcOSwsUDHgw07RIgI4KCkAgs8cvTw8eOBogAxQtXIASTISiEuBwUYMoRIixYnZggpUgTDywdIkWJIitRPIAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+image create photo ::browser::img_tree    -data {R0lGODlhEAAQAIYAAPwCBAQCBExKTBwWHMzKzOzq7ERCRExGTCwqLARqnAQ+ZHR2dKyqrNTOzHx2fCQiJMTi9NTu9HzC3AxmnAQ+XPTm7Dy67DymzITC3IzG5AxypHRydKymrMzOzOzu7BweHByy9AyGtFyy1IzG3NTu/ARupFRSVByazBR6rAyGvFyuzJTK3MTm9BR+tAxWhHS61MTi7Pz+/IymvCxulBRelAx2rHS63Pz6/PTy9PTu9Nza3ISitBRupFSixNTS1CxqnDQyNMzGzOTi5MTCxMTGxGxubGxqbLy2vLSutGRiZLy6vLSytKyurDQuNFxaXKSipDw6PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAfDgACCAAECg4eIAAMEBQYHCImDBgkKCwwNBQIBBw4Bhw8QERITFJYEFQUFnoIPFhcYoRkaFBscHR4Ggh8gIRciEiMQJBkltCa6JyUoKSkXKhIrLCQYuQAPLS4TEyUhKb0qLzDVAjEFMjMuNBMoNcw21QY3ODkFOjs82RM1PfDzFRU3fOggcM7Fj2pAgggRokOHDx9DhhAZUqQaISBGhjwMEvEIkiIHEgUAkgSJkiNLmFSMJChAEydPGBSBwvJQgAc0/QQCACH+aENyZWF0ZWQgYnkgQk1QVG9HSUYgUHJvIHZlcnNpb24gMi41DQqpIERldmVsQ29yIDE5OTcsMTk5OC4gQWxsIHJpZ2h0cyByZXNlcnZlZC4NCmh0dHA6Ly93d3cuZGV2ZWxjb3IuY29tADs=}
+image create photo ::browser::img_symlink -data {R0lGODlhEAAQAIQAAPwCBCwqLLSytLy+vERGRFRWVDQ2NKSmpAQCBKyurMTGxISChJyanHR2dIyKjGxubHRydGRmZIyOjFxeXHx6fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAVbICACwWieY1CibCCsrBkMb0zchSEcNYskCtqBBzshFkOGQFk0IRqOxqPBODRHCMhCQKteRc9FI/KQWGOIyFYgkDC+gPR4snCcfRGKOIKIgSMQE31+f4OEYCZ+IQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+image create photo ::browser::img_unknown -data {R0lGODlhEAAQAIUAAPwCBFxaXIyKjNTW1Nze3LS2tJyanER2RGS+VPz+/PTu5GxqbPz69BQ6BCxeLFSqRPT29HRydMzOzDQyNERmPKSypCRWHIyKhERCRDyGPKz2nESiLBxGHCyCHGxubPz6/PTy7Ozi1Ly2rKSipOzm3LyqlKSWhCRyFOzizLymhNTKtNzOvOzaxOTStPz27OzWvOTOpLSupLyedMS+rMS6pMSulLyqjLymfLyifAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAamQIAQECgajcOkYEBoDgoBQyAJOCCuiENCsWBIh9aGw9F4HCARiXciRDQoBUnlYRlcIgsMG5CxXAgMGhscBRAEBRd7AB0eBBoIgxUfICEiikSPgyMMIAokJZcBkBybJgomIaBJAZoMpyCmqkMBFCcVCrgKKAwpoSorKqchKCwtvasIFBIhLiYvLzDHsxQNMcMKLDAwMqEz3jQ1NTY3ONyrE+jp6hN+QQAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+
 field w
 field browser_commit
 field browser_path
@@ -13,13 +20,13 @@ field browser_busy   1
 
 field ls_buf     {}; # Buffered record output from ls-tree
 
-constructor new {commit} {
+constructor new {commit {path {}}} {
        global cursor_ptr M1B
        make_toplevel top w
        wm title $top "[appname] ([reponame]): File Browser"
 
        set browser_commit $commit
-       set browser_path $browser_commit:
+       set browser_path $browser_commit:$path
 
        label $w.path \
                -textvariable @browser_path \
@@ -73,7 +80,11 @@ constructor new {commit} {
 
        bind $w_list <Visibility> [list focus $w_list]
        set w $w_list
-       _ls $this $browser_commit
+       if {$path ne {}} {
+               _ls $this $browser_commit:$path $path
+       } else {
+               _ls $this $browser_commit $path
+       }
        return $this
 }
 
@@ -173,15 +184,14 @@ method _ls {tree_id {name {}}} {
                $w image create end \
                        -align center -padx 5 -pady 1 \
                        -name icon0 \
-                       -image file_uplevel
+                       -image ::browser::img_parent
                $w insert end {[Up To Parent]}
                lappend browser_files parent
        }
        lappend browser_stack [list $tree_id $name]
        $w conf -state disabled
 
-       set cmd [list git ls-tree -z $tree_id]
-       set fd [open "| $cmd" r]
+       set fd [git_read ls-tree -z $tree_id]
        fconfigure $fd -blocking 0 -translation binary -encoding binary
        fileevent $fd readable [cb _read $fd]
 }
@@ -204,14 +214,21 @@ method _read {fd} {
 
                switch -- $type {
                blob {
-                       set image file_mod
+                       scan [lindex $info 0] %o mode
+                       if {$mode == 0120000} {
+                               set image ::browser::img_symlink
+                       } elseif {($mode & 0100) != 0} {
+                               set image ::browser::img_xblob
+                       } else {
+                               set image ::browser::img_rblob
+                       }
                }
                tree {
-                       set image file_dir
+                       set image ::browser::img_tree
                        append path /
                }
                default {
-                       set image file_question
+                       set image ::browser::img_unknown
                }
                }
 
@@ -240,3 +257,56 @@ method _read {fd} {
 }
 
 }
+
+class browser_open {
+
+field w              ; # widget path
+field w_rev          ; # mega-widget to pick the initial revision
+
+constructor dialog {} {
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Browse Branch Files"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       label $w.header \
+               -text {Browse Branch Files} \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.browse -text Browse \
+               -default active \
+               -command [cb _open]
+       pack $w.buttons.browse -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       set w_rev [::choose_rev::new $w.rev {Revision}]
+       $w_rev bind_listbox <Double-Button-1> [cb _open]
+       pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
+
+       bind $w <Visibility> [cb _visible]
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Key-Return> [cb _open]\;break
+       tkwait window $w
+}
+
+method _open {} {
+       if {[catch {$w_rev commit_or_die} err]} {
+               return
+       }
+       set name [$w_rev get]
+       destroy $w
+       browser::new $name
+}
+
+method _visible {} {
+       grab $w
+       $w_rev focus_filter
+}
+
+}
diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl
new file mode 100644 (file)
index 0000000..170f737
--- /dev/null
@@ -0,0 +1,588 @@
+# git-gui commit checkout support
+# Copyright (C) 2007 Shawn Pearce
+
+class checkout_op {
+
+field w        {}; # our window (if we have one)
+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 parent_w      .; # window that started us
+field merge_type none; # type of merge to apply to existing branch
+field merge_base   {}; # merge base if we have another ref involved
+field fetch_spec   {}; # refetch tracking branch if used?
+field checkout      1; # actually checkout the branch?
+field create        0; # create the branch if it doesn't exist?
+
+field reset_ok      0; # did the user agree to reset?
+field fetch_ok      0; # did the fetch succeed?
+
+field readtree_d   {}; # buffered output from read-tree
+field update_old   {}; # was the update-ref call deferred?
+field reflog_msg   {}; # log message for the update-ref call
+
+constructor new {expr hash {ref {}}} {
+       set new_expr $expr
+       set new_hash $hash
+       set new_ref  $ref
+
+       return $this
+}
+
+method parent {path} {
+       set parent_w [winfo toplevel $path]
+}
+
+method enable_merge {type} {
+       set merge_type $type
+}
+
+method enable_fetch {spec} {
+       set fetch_spec $spec
+}
+
+method enable_checkout {co} {
+       set checkout $co
+}
+
+method enable_create {co} {
+       set create $co
+}
+
+method run {} {
+       if {$fetch_spec ne {}} {
+               global M1B
+
+               # We were asked to refresh a single tracking branch
+               # before we get to work.  We should do that before we
+               # consider any ref updating.
+               #
+               set fetch_ok 0
+               set l_trck [lindex $fetch_spec 0]
+               set remote [lindex $fetch_spec 1]
+               set r_head [lindex $fetch_spec 2]
+               regsub ^refs/heads/ $r_head {} r_name
+
+               set cmd [list git fetch $remote]
+               if {$l_trck ne {}} {
+                       lappend cmd +$r_head:$l_trck
+               } else {
+                       lappend cmd $r_head
+               }
+
+               _toplevel $this {Refreshing Tracking Branch}
+               set w_cons [::console::embed \
+                       $w.console \
+                       "Fetching $r_name from $remote"]
+               pack $w.console -fill both -expand 1
+               $w_cons exec $cmd [cb _finish_fetch]
+
+               bind $w <$M1B-Key-w> break
+               bind $w <$M1B-Key-W> break
+               bind $w <Visibility> "
+                       [list grab $w]
+                       [list focus $w]
+               "
+               wm protocol $w WM_DELETE_WINDOW [cb _noop]
+               tkwait window $w
+
+               if {!$fetch_ok} {
+                       delete_this
+                       return 0
+               }
+       }
+
+       if {$new_ref ne {}} {
+               # If we have a ref we need to update it before we can
+               # proceed with a checkout (if one was enabled).
+               #
+               if {![_update_ref $this]} {
+                       delete_this
+                       return 0
+               }
+       }
+
+       if {$checkout} {
+               _checkout $this
+               return 1
+       }
+
+       delete_this
+       return 1
+}
+
+method _noop {} {}
+
+method _finish_fetch {ok} {
+       if {$ok} {
+               set l_trck [lindex $fetch_spec 0]
+               if {$l_trck eq {}} {
+                       set l_trck FETCH_HEAD
+               }
+               if {[catch {set new_hash [git rev-parse --verify "$l_trck^0"]} err]} {
+                       set ok 0
+                       $w_cons insert "fatal: Cannot resolve $l_trck"
+                       $w_cons insert $err
+               }
+       }
+
+       $w_cons done $ok
+       set w_cons {}
+       wm protocol $w WM_DELETE_WINDOW {}
+
+       if {$ok} {
+               destroy $w
+               set w {}
+       } else {
+               button $w.close -text Close -command [list destroy $w]
+               pack $w.close -side bottom -anchor e -padx 10 -pady 10
+       }
+
+       set fetch_ok $ok
+}
+
+method _update_ref {} {
+       global null_sha1 current_branch
+
+       set ref $new_ref
+       set new $new_hash
+
+       set is_current 0
+       set rh refs/heads/
+       set rn [string length $rh]
+       if {[string equal -length $rn $rh $ref]} {
+               set newbranch [string range $ref $rn end]
+               if {$current_branch eq $newbranch} {
+                       set is_current 1
+               }
+       } else {
+               set newbranch $ref
+       }
+
+       if {[catch {set cur [git rev-parse --verify "$ref^0"]}]} {
+               # Assume it does not exist, and that is what the error was.
+               #
+               if {!$create} {
+                       _error $this "Branch '$newbranch' does not exist."
+                       return 0
+               }
+
+               set reflog_msg "branch: Created from $new_expr"
+               set cur $null_sha1
+       } elseif {$create && $merge_type eq {none}} {
+               # We were told to create it, but not do a merge.
+               # Bad.  Name shouldn't have existed.
+               #
+               _error $this "Branch '$newbranch' already exists."
+               return 0
+       } elseif {!$create && $merge_type eq {none}} {
+               # We aren't creating, it exists and we don't merge.
+               # We are probably just a simple branch switch.
+               # Use whatever value we just read.
+               #
+               set new      $cur
+               set new_hash $cur
+       } elseif {$new eq $cur} {
+               # No merge would be required, don't compute anything.
+               #
+       } else {
+               catch {set merge_base [git merge-base $new $cur]}
+               if {$merge_base eq $cur} {
+                       # The current branch is older.
+                       #
+                       set reflog_msg "merge $new_expr: Fast-forward"
+               } else {
+                       switch -- $merge_type {
+                       ff {
+                               if {$merge_base eq $new} {
+                                       # The current branch is actually newer.
+                                       #
+                                       set new $cur
+                                       set new_hash $cur
+                               } else {
+                                       _error $this "Branch '$newbranch' already exists.\n\nIt cannot fast-forward to $new_expr.\nA merge is required."
+                                       return 0
+                               }
+                       }
+                       reset {
+                               # The current branch will lose things.
+                               #
+                               if {[_confirm_reset $this $cur]} {
+                                       set reflog_msg "reset $new_expr"
+                               } else {
+                                       return 0
+                               }
+                       }
+                       default {
+                               _error $this "Merge strategy '$merge_type' not supported."
+                               return 0
+                       }
+                       }
+               }
+       }
+
+       if {$new ne $cur} {
+               if {$is_current} {
+                       # No so fast.  We should defer this in case
+                       # we cannot update the working directory.
+                       #
+                       set update_old $cur
+                       return 1
+               }
+
+               if {[catch {
+                               git update-ref -m $reflog_msg $ref $new $cur
+                       } err]} {
+                       _error $this "Failed to update '$newbranch'.\n\n$err"
+                       return 0
+               }
+       }
+
+       return 1
+}
+
+method _checkout {} {
+       if {[lock_index checkout_op]} {
+               after idle [cb _start_checkout]
+       } else {
+               _error $this "Staging area (index) is already locked."
+               delete_this
+       }
+}
+
+method _start_checkout {} {
+       global HEAD commit_type
+
+       # -- Our in memory state should match the repository.
+       #
+       repository_state curType curHEAD curMERGE_HEAD
+       if {[string match amend* $commit_type]
+               && $curType eq {normal}
+               && $curHEAD eq $HEAD} {
+       } elseif {$commit_type ne $curType || $HEAD ne $curHEAD} {
+               info_popup {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.
+
+The rescan will be automatically started now.
+}
+               unlock_index
+               rescan ui_ready
+               delete_this
+               return
+       }
+
+       if {$curHEAD eq $new_hash} {
+               _after_readtree $this
+       } elseif {[is_config_true gui.trustmtime]} {
+               _readtree $this
+       } else {
+               ui_status {Refreshing file status...}
+               set fd [git_read update-index \
+                       -q \
+                       --unmerged \
+                       --ignore-missing \
+                       --refresh \
+                       ]
+               fconfigure $fd -blocking 0 -translation binary
+               fileevent $fd readable [cb _refresh_wait $fd]
+       }
+}
+
+method _refresh_wait {fd} {
+       read $fd
+       if {[eof $fd]} {
+               close $fd
+               _readtree $this
+       }
+}
+
+method _name {} {
+       if {$new_ref eq {}} {
+               return [string range $new_hash 0 7]
+       }
+
+       set rh refs/heads/
+       set rn [string length $rh]
+       if {[string equal -length $rn $rh $new_ref]} {
+               return [string range $new_ref $rn end]
+       } else {
+               return $new_ref
+       }
+}
+
+method _readtree {} {
+       global HEAD
+
+       set readtree_d {}
+       $::main_status start \
+               "Updating working directory to '[_name $this]'..." \
+               {files checked out}
+
+       set fd [git_read --stderr read-tree \
+               -m \
+               -u \
+               -v \
+               --exclude-per-directory=.gitignore \
+               $HEAD \
+               $new_hash \
+               ]
+       fconfigure $fd -blocking 0 -translation binary
+       fileevent $fd readable [cb _readtree_wait $fd]
+}
+
+method _readtree_wait {fd} {
+       global current_branch
+
+       set buf [read $fd]
+       $::main_status update_meter $buf
+       append readtree_d $buf
+
+       fconfigure $fd -blocking 1
+       if {![eof $fd]} {
+               fconfigure $fd -blocking 0
+               return
+       }
+
+       if {[catch {close $fd}]} {
+               set err $readtree_d
+               regsub {^fatal: } $err {} err
+               $::main_status stop "Aborted checkout of '[_name $this]' (file level merging is required)."
+               warn_popup "File level merge required.
+
+$err
+
+Staying on branch '$current_branch'."
+               unlock_index
+               delete_this
+               return
+       }
+
+       $::main_status stop
+       _after_readtree $this
+}
+
+method _after_readtree {} {
+       global selected_commit_type commit_type HEAD MERGE_HEAD PARENT
+       global current_branch is_detached
+       global ui_comm
+
+       set name [_name $this]
+       set log "checkout: moving"
+       if {!$is_detached} {
+               append log " from $current_branch"
+       }
+
+       # -- Move/create HEAD as a symbolic ref.  Core git does not
+       #    even check for failure here, it Just Works(tm).  If it
+       #    doesn't we are in some really ugly state that is difficult
+       #    to recover from within git-gui.
+       #
+       set rh refs/heads/
+       set rn [string length $rh]
+       if {[string equal -length $rn $rh $new_ref]} {
+               set new_branch [string range $new_ref $rn end]
+               if {$is_detached || $current_branch ne $new_branch} {
+                       append log " to $new_branch"
+                       if {[catch {
+                                       git symbolic-ref -m $log HEAD $new_ref
+                               } err]} {
+                               _fatal $this $err
+                       }
+                       set current_branch $new_branch
+                       set is_detached 0
+               }
+       } else {
+               if {$new_hash ne $HEAD} {
+                       append log " to $new_expr"
+                       if {[catch {
+                                       _detach_HEAD $log $new_hash
+                               } err]} {
+                               _fatal $this $err
+                       }
+               }
+               set current_branch HEAD
+               set is_detached 1
+       }
+
+       # -- We had to defer updating the branch itself until we
+       #    knew the working directory would update.  So now we
+       #    need to finish that work.  If it fails we're in big
+       #    trouble.
+       #
+       if {$update_old ne {}} {
+               if {[catch {
+                               git update-ref \
+                                       -m $reflog_msg \
+                                       $new_ref \
+                                       $new_hash \
+                                       $update_old
+                       } err]} {
+                       _fatal $this $err
+               }
+       }
+
+       if {$is_detached} {
+               info_popup "You are no longer on a local branch.
+
+If you wanted to be on a branch, create one now starting from 'This Detached Checkout'."
+       }
+
+       # -- 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.
+       #
+       unlock_index
+       set selected_commit_type new
+       if {[string match amend* $commit_type]} {
+               $ui_comm delete 0.0 end
+               $ui_comm edit reset
+               $ui_comm edit modified false
+               rescan [list ui_status "Checked out '$name'."]
+       } else {
+               repository_state commit_type HEAD MERGE_HEAD
+               set PARENT $HEAD
+               ui_status "Checked out '$name'."
+       }
+       delete_this
+}
+
+git-version proc _detach_HEAD {log new} {
+       >= 1.5.3 {
+               git update-ref --no-deref -m $log HEAD $new
+       }
+       default {
+               set p [gitdir HEAD]
+               file delete $p
+               set fd [open $p w]
+               fconfigure $fd -translation lf -encoding utf-8
+               puts $fd $new
+               close $fd
+       }
+}
+
+method _confirm_reset {cur} {
+       set reset_ok 0
+       set name [_name $this]
+       set gitk [list do_gitk [list $cur ^$new_hash]]
+
+       _toplevel $this {Confirm Branch Reset}
+       pack [label $w.msg1 \
+               -anchor w \
+               -justify left \
+               -text "Resetting '$name' to $new_expr will lose the following commits:" \
+               ] -anchor w
+
+       set list $w.list.l
+       frame $w.list
+       text $list \
+               -font font_diff \
+               -width 80 \
+               -height 10 \
+               -wrap none \
+               -xscrollcommand [list $w.list.sbx set] \
+               -yscrollcommand [list $w.list.sby set]
+       scrollbar $w.list.sbx -orient h -command [list $list xview]
+       scrollbar $w.list.sby -orient v -command [list $list yview]
+       pack $w.list.sbx -fill x -side bottom
+       pack $w.list.sby -fill y -side right
+       pack $list -fill both -expand 1
+       pack $w.list -fill both -expand 1 -padx 5 -pady 5
+
+       pack [label $w.msg2 \
+               -anchor w \
+               -justify left \
+               -text {Recovering lost commits may not be easy.} \
+               ]
+       pack [label $w.msg3 \
+               -anchor w \
+               -justify left \
+               -text "Reset '$name'?" \
+               ]
+
+       frame $w.buttons
+       button $w.buttons.visualize \
+               -text Visualize \
+               -command $gitk
+       pack $w.buttons.visualize -side left
+       button $w.buttons.reset \
+               -text Reset \
+               -command "
+                       set @reset_ok 1
+                       destroy $w
+               "
+       pack $w.buttons.reset -side right
+       button $w.buttons.cancel \
+               -default active \
+               -text Cancel \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       set fd [git_read rev-list --pretty=oneline $cur ^$new_hash]
+       while {[gets $fd line] > 0} {
+               set abbr [string range $line 0 7]
+               set subj [string range $line 41 end]
+               $list insert end "$abbr  $subj\n"
+       }
+       close $fd
+       $list configure -state disabled
+
+       bind $w    <Key-v> $gitk
+       bind $w <Visibility> "
+               grab $w
+               focus $w.buttons.cancel
+       "
+       bind $w <Key-Return> [list destroy $w]
+       bind $w <Key-Escape> [list destroy $w]
+       tkwait window $w
+       return $reset_ok
+}
+
+method _error {msg} {
+       if {[winfo ismapped $parent_w]} {
+               set p $parent_w
+       } else {
+               set p .
+       }
+
+       tk_messageBox \
+               -icon error \
+               -type ok \
+               -title [wm title $p] \
+               -parent $p \
+               -message $msg
+}
+
+method _toplevel {title} {
+       regsub -all {::} $this {__} w
+       set w .$w
+
+       if {[winfo ismapped $parent_w]} {
+               set p $parent_w
+       } else {
+               set p .
+       }
+
+       toplevel $w
+       wm title $w $title
+       wm geometry $w "+[winfo rootx $p]+[winfo rooty $p]"
+}
+
+method _fatal {err} {
+       error_popup "Failed to set current branch.
+
+This working directory is only partially switched.  We successfully updated your files, but failed to update an internal Git file.
+
+This should not have occurred.  [appname] will now close and give up.
+
+$err"
+       exit 1
+}
+
+}
diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl
new file mode 100644 (file)
index 0000000..ec064b3
--- /dev/null
@@ -0,0 +1,627 @@
+# git-gui revision chooser
+# Copyright (C) 2006, 2007 Shawn Pearce
+
+class choose_rev {
+
+image create photo ::choose_rev::img_find -data {R0lGODlhEAAQAIYAAPwCBCQmJDw+PBQSFAQCBMza3NTm5MTW1HyChOT29Ozq7MTq7Kze5Kzm7Oz6/NTy9Iza5GzGzKzS1Nzy9Nz29Kzq9HTGzHTK1Lza3AwKDLzu9JTi7HTW5GTCzITO1Mzq7Hza5FTK1ESyvHzKzKzW3DQyNDyqtDw6PIzW5HzGzAT+/Dw+RKyurNTOzMTGxMS+tJSGdATCxHRydLSqpLymnLSijBweHERCRNze3Pz69PTy9Oze1OTSxOTGrMSqlLy+vPTu5OzSvMymjNTGvNS+tMy2pMyunMSefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAe4gACCAAECA4OIiAIEBQYHBAKJgwIICQoLDA0IkZIECQ4PCxARCwSSAxITFA8VEBYXGBmJAQYLGhUbHB0eH7KIGRIMEBAgISIjJKaIJQQLFxERIialkieUGigpKRoIBCqJKyyLBwvJAioEyoICLS4v6QQwMQQyLuqLli8zNDU2BCf1lN3AkUPHDh49fAQAAEnGD1MCCALZEaSHkIUMBQS8wWMIkSJGhBzBmFEGgRsBUqpMiSgdAD+BAAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
+
+field w               ; # our megawidget path
+field w_list          ; # list of currently filtered specs
+field w_filter        ; # filter entry for $w_list
+
+field c_expr        {}; # current revision expression
+field filter          ; # current filter string
+field revtype     head; # type of revision chosen
+field cur_specs [list]; # list of specs for $revtype
+field spec_head       ; # list of all head specs
+field spec_trck       ; # list of all tracking branch specs
+field spec_tag        ; # list of all tag specs
+field tip_data        ; # array of tip commit info by refname
+field log_last        ; # array of reflog date by refname
+
+field tooltip_wm        {} ; # Current tooltip toplevel, if open
+field tooltip_t         {} ; # Text widget in $tooltip_wm
+field tooltip_timer     {} ; # Current timer event for our tooltip
+
+proc new {path {title {}}} {
+       return [_new $path 0 $title]
+}
+
+proc new_unmerged {path {title {}}} {
+       return [_new $path 1 $title]
+}
+
+constructor _new {path unmerged_only title} {
+       global current_branch is_detached
+
+       if {![info exists ::all_remotes]} {
+               load_all_remotes
+       }
+
+       set w $path
+
+       if {$title ne {}} {
+               labelframe $w -text $title
+       } else {
+               frame $w
+       }
+       bind $w <Destroy> [cb _delete %W]
+
+       if {$is_detached} {
+               radiobutton $w.detachedhead_r \
+                       -anchor w \
+                       -text {This Detached Checkout} \
+                       -value HEAD \
+                       -variable @revtype
+               grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2
+       }
+
+       radiobutton $w.expr_r \
+               -text {Revision Expression:} \
+               -value expr \
+               -variable @revtype
+       entry $w.expr_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 50 \
+               -textvariable @c_expr \
+               -validate key \
+               -validatecommand [cb _validate %d %S]
+       grid $w.expr_r $w.expr_t -sticky we -padx {0 5}
+
+       frame $w.types
+       radiobutton $w.types.head_r \
+               -text {Local Branch} \
+               -value head \
+               -variable @revtype
+       pack $w.types.head_r -side left
+       radiobutton $w.types.trck_r \
+               -text {Tracking Branch} \
+               -value trck \
+               -variable @revtype
+       pack $w.types.trck_r -side left
+       radiobutton $w.types.tag_r \
+               -text {Tag} \
+               -value tag \
+               -variable @revtype
+       pack $w.types.tag_r -side left
+       set w_filter $w.types.filter
+       entry $w_filter \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 12 \
+               -textvariable @filter \
+               -validate key \
+               -validatecommand [cb _filter %P]
+       pack $w_filter -side right
+       pack [label $w.types.filter_icon \
+               -image ::choose_rev::img_find \
+               ] -side right
+       grid $w.types -sticky we -padx {0 5} -columnspan 2
+
+       frame $w.list
+       set w_list $w.list.l
+       listbox $w_list \
+               -font font_diff \
+               -width 50 \
+               -height 10 \
+               -selectmode browse \
+               -exportselection false \
+               -xscrollcommand [cb _sb_set $w.list.sbx h] \
+               -yscrollcommand [cb _sb_set $w.list.sby v]
+       pack $w_list -fill both -expand 1
+       grid $w.list -sticky nswe -padx {20 5} -columnspan 2
+       bind $w_list <Any-Motion>  [cb _show_tooltip @%x,%y]
+       bind $w_list <Any-Enter>   [cb _hide_tooltip]
+       bind $w_list <Any-Leave>   [cb _hide_tooltip]
+       bind $w_list <Destroy>     [cb _hide_tooltip]
+
+       grid columnconfigure $w 1 -weight 1
+       if {$is_detached} {
+               grid rowconfigure $w 3 -weight 1
+       } else {
+               grid rowconfigure $w 2 -weight 1
+       }
+
+       trace add variable @revtype write [cb _select]
+       bind $w_filter <Key-Return> [list focus $w_list]\;break
+       bind $w_filter <Key-Down>   [list focus $w_list]
+
+       set fmt list
+       append fmt { %(refname)}
+       append fmt { [list}
+       append fmt { %(objecttype)}
+       append fmt { %(objectname)}
+       append fmt { [concat %(taggername) %(authorname)]}
+       append fmt { [concat %(taggerdate) %(authordate)]}
+       append fmt { %(subject)}
+       append fmt {] [list}
+       append fmt { %(*objecttype)}
+       append fmt { %(*objectname)}
+       append fmt { %(*authorname)}
+       append fmt { %(*authordate)}
+       append fmt { %(*subject)}
+       append fmt {]}
+       set all_refn [list]
+       set fr_fd [git_read for-each-ref \
+               --tcl \
+               --sort=-taggerdate \
+               --format=$fmt \
+               refs/heads \
+               refs/remotes \
+               refs/tags \
+               ]
+       fconfigure $fr_fd -translation lf -encoding utf-8
+       while {[gets $fr_fd line] > 0} {
+               set line [eval $line]
+               if {[lindex $line 1 0] eq {tag}} {
+                       if {[lindex $line 2 0] eq {commit}} {
+                               set sha1 [lindex $line 2 1]
+                       } else {
+                               continue
+                       }
+               } elseif {[lindex $line 1 0] eq {commit}} {
+                       set sha1 [lindex $line 1 1]
+               } else {
+                       continue
+               }
+               set refn [lindex $line 0]
+               set tip_data($refn) [lrange $line 1 end]
+               lappend cmt_refn($sha1) $refn
+               lappend all_refn $refn
+       }
+       close $fr_fd
+
+       if {$unmerged_only} {
+               set fr_fd [git_read rev-list --all ^$::HEAD]
+               while {[gets $fr_fd sha1] > 0} {
+                       if {[catch {set rlst $cmt_refn($sha1)}]} continue
+                       foreach refn $rlst {
+                               set inc($refn) 1
+                       }
+               }
+               close $fr_fd
+       } else {
+               foreach refn $all_refn {
+                       set inc($refn) 1
+               }
+       }
+
+       set spec_head [list]
+       foreach name [load_all_heads] {
+               set refn refs/heads/$name
+               if {[info exists inc($refn)]} {
+                       lappend spec_head [list $name $refn]
+               }
+       }
+
+       set spec_trck [list]
+       foreach spec [all_tracking_branches] {
+               set refn [lindex $spec 0]
+               if {[info exists inc($refn)]} {
+                       regsub ^refs/(heads|remotes)/ $refn {} name
+                       lappend spec_trck [concat $name $spec]
+               }
+       }
+
+       set spec_tag [list]
+       foreach name [load_all_tags] {
+               set refn refs/tags/$name
+               if {[info exists inc($refn)]} {
+                       lappend spec_tag [list $name $refn]
+               }
+       }
+
+                 if {$is_detached}             { set revtype HEAD
+       } elseif {[llength $spec_head] > 0} { set revtype head
+       } elseif {[llength $spec_trck] > 0} { set revtype trck
+       } elseif {[llength $spec_tag ] > 0} { set revtype tag
+       } else {                              set revtype expr
+       }
+
+       if {$revtype eq {head} && $current_branch ne {}} {
+               set i 0
+               foreach spec $spec_head {
+                       if {[lindex $spec 0] eq $current_branch} {
+                               $w_list selection clear 0 end
+                               $w_list selection set $i
+                               break
+                       }
+                       incr i
+               }
+       }
+
+       return $this
+}
+
+method none {text} {
+       if {![winfo exists $w.none_r]} {
+               radiobutton $w.none_r \
+                       -anchor w \
+                       -value none \
+                       -variable @revtype
+               grid $w.none_r -sticky we -padx {0 5} -columnspan 2
+       }
+       $w.none_r configure -text $text
+}
+
+method get {} {
+       switch -- $revtype {
+       head -
+       trck -
+       tag  {
+               set i [$w_list curselection]
+               if {$i ne {}} {
+                       return [lindex $cur_specs $i 0]
+               } else {
+                       return {}
+               }
+       }
+
+       HEAD { return HEAD                     }
+       expr { return $c_expr                  }
+       none { return {}                       }
+       default { error "unknown type of revision" }
+       }
+}
+
+method pick_tracking_branch {} {
+       set revtype trck
+}
+
+method focus_filter {} {
+       if {[$w_filter cget -state] eq {normal}} {
+               focus $w_filter
+       }
+}
+
+method bind_listbox {event script}  {
+       bind $w_list $event $script
+}
+
+method get_local_branch {} {
+       if {$revtype eq {head}} {
+               return [_expr $this]
+       } else {
+               return {}
+       }
+}
+
+method get_tracking_branch {} {
+       set i [$w_list curselection]
+       if {$i eq {} || $revtype ne {trck}} {
+               return {}
+       }
+       return [lrange [lindex $cur_specs $i] 1 end]
+}
+
+method get_commit {} {
+       set e [_expr $this]
+       if {$e eq {}} {
+               return {}
+       }
+       return [git rev-parse --verify "$e^0"]
+}
+
+method commit_or_die {} {
+       if {[catch {set new [get_commit $this]} err]} {
+
+               # Cleanup the not-so-friendly error from rev-parse.
+               #
+               regsub {^fatal:\s*} $err {} err
+               if {$err eq {Needed a single revision}} {
+                       set err {}
+               }
+
+               set top [winfo toplevel $w]
+               set msg "Invalid revision: [get $this]\n\n$err"
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $top] \
+                       -parent $top \
+                       -message $msg
+               error $msg
+       }
+       return $new
+}
+
+method _expr {} {
+       switch -- $revtype {
+       head -
+       trck -
+       tag  {
+               set i [$w_list curselection]
+               if {$i ne {}} {
+                       return [lindex $cur_specs $i 1]
+               } else {
+                       error "No revision selected."
+               }
+       }
+
+       expr {
+               if {$c_expr ne {}} {
+                       return $c_expr
+               } else {
+                       error "Revision expression is empty."
+               }
+       }
+       HEAD { return HEAD                     }
+       none { return {}                       }
+       default { error "unknown type of revision"      }
+       }
+}
+
+method _validate {d S} {
+       if {$d == 1} {
+               if {[regexp {\s} $S]} {
+                       return 0
+               }
+               if {[string length $S] > 0} {
+                       set revtype expr
+               }
+       }
+       return 1
+}
+
+method _filter {P} {
+       if {[regexp {\s} $P]} {
+               return 0
+       }
+       _rebuild $this $P
+       return 1
+}
+
+method _select {args} {
+       _rebuild $this $filter
+       focus_filter $this
+}
+
+method _rebuild {pat} {
+       set ste normal
+       switch -- $revtype {
+       head { set new $spec_head }
+       trck { set new $spec_trck }
+       tag  { set new $spec_tag  }
+       expr -
+       HEAD -
+       none {
+               set new [list]
+               set ste disabled
+       }
+       }
+
+       if {[$w_list cget -state] eq {disabled}} {
+               $w_list configure -state normal
+       }
+       $w_list delete 0 end
+
+       if {$pat ne {}} {
+               set pat *${pat}*
+       }
+       set cur_specs [list]
+       foreach spec $new {
+               set txt [lindex $spec 0]
+               if {$pat eq {} || [string match $pat $txt]} {
+                       lappend cur_specs $spec
+                       $w_list insert end $txt
+               }
+       }
+       if {$cur_specs ne {}} {
+               $w_list selection clear 0 end
+               $w_list selection set 0
+       }
+
+       if {[$w_filter cget -state] ne $ste} {
+               $w_list   configure -state $ste
+               $w_filter configure -state $ste
+       }
+}
+
+method _delete {current} {
+       if {$current eq $w} {
+               delete_this
+       }
+}
+
+method _sb_set {sb orient first last} {
+       set old_focus [focus -lastfor $w]
+
+       if {$first == 0 && $last == 1} {
+               if {[winfo exists $sb]} {
+                       destroy $sb
+                       if {$old_focus ne {}} {
+                               update
+                               focus $old_focus
+                       }
+               }
+               return
+       }
+
+       if {![winfo exists $sb]} {
+               if {$orient eq {h}} {
+                       scrollbar $sb -orient h -command [list $w_list xview]
+                       pack $sb -fill x -side bottom -before $w_list
+               } else {
+                       scrollbar $sb -orient v -command [list $w_list yview]
+                       pack $sb -fill y -side right -before $w_list
+               }
+               if {$old_focus ne {}} {
+                       update
+                       focus $old_focus
+               }
+       }
+       $sb set $first $last
+}
+
+method _show_tooltip {pos} {
+       if {$tooltip_wm ne {}} {
+               _open_tooltip $this
+       } elseif {$tooltip_timer eq {}} {
+               set tooltip_timer [after 1000 [cb _open_tooltip]]
+       }
+}
+
+method _open_tooltip {} {
+       global remote_url
+
+       set tooltip_timer {}
+       set pos_x [winfo pointerx $w_list]
+       set pos_y [winfo pointery $w_list]
+       if {[winfo containing $pos_x $pos_y] ne $w_list} {
+               _hide_tooltip $this
+               return
+       }
+
+       set pos @[join [list \
+               [expr {$pos_x - [winfo rootx $w_list]}] \
+               [expr {$pos_y - [winfo rooty $w_list]}]] ,]
+       set lno [$w_list index $pos]
+       if {$lno eq {}} {
+               _hide_tooltip $this
+               return
+       }
+
+       set spec [lindex $cur_specs $lno]
+       set refn [lindex $spec 1]
+       if {$refn eq {}} {
+               _hide_tooltip $this
+               return
+       }
+
+       if {$tooltip_wm eq {}} {
+               set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1]
+               wm overrideredirect $tooltip_wm 1
+               wm transient $tooltip_wm [winfo toplevel $w_list]
+               set tooltip_t $tooltip_wm.label
+               text $tooltip_t \
+                       -takefocus 0 \
+                       -highlightthickness 0 \
+                       -relief flat \
+                       -borderwidth 0 \
+                       -wrap none \
+                       -background lightyellow \
+                       -foreground black
+               $tooltip_t tag conf section_header -font font_uibold
+               bind $tooltip_wm <Escape> [cb _hide_tooltip]
+               pack $tooltip_t
+       } else {
+               $tooltip_t conf -state normal
+               $tooltip_t delete 0.0 end
+       }
+
+       set data $tip_data($refn)
+       if {[lindex $data 0 0] eq {tag}} {
+               set tag  [lindex $data 0]
+               if {[lindex $data 1 0] eq {commit}} {
+                       set cmit [lindex $data 1]
+               } else {
+                       set cmit {}
+               }
+       } elseif {[lindex $data 0 0] eq {commit}} {
+               set tag  {}
+               set cmit [lindex $data 0]
+       }
+
+       $tooltip_t insert end [lindex $spec 0]
+       set last [_reflog_last $this [lindex $spec 1]]
+       if {$last ne {}} {
+               $tooltip_t insert end "\n"
+               $tooltip_t insert end "updated"
+               $tooltip_t insert end " $last"
+       }
+       $tooltip_t insert end "\n"
+
+       if {$tag ne {}} {
+               $tooltip_t insert end "\n"
+               $tooltip_t insert end "tag" section_header
+               $tooltip_t insert end "  [lindex $tag 1]\n"
+               $tooltip_t insert end [lindex $tag 2]
+               $tooltip_t insert end " ([lindex $tag 3])\n"
+               $tooltip_t insert end [lindex $tag 4]
+               $tooltip_t insert end "\n"
+       }
+
+       if {$cmit ne {}} {
+               $tooltip_t insert end "\n"
+               $tooltip_t insert end "commit" section_header
+               $tooltip_t insert end "  [lindex $cmit 1]\n"
+               $tooltip_t insert end [lindex $cmit 2]
+               $tooltip_t insert end " ([lindex $cmit 3])\n"
+               $tooltip_t insert end [lindex $cmit 4]
+       }
+
+       if {[llength $spec] > 2} {
+               $tooltip_t insert end "\n"
+               $tooltip_t insert end "remote" section_header
+               $tooltip_t insert end "  [lindex $spec 2]\n"
+               $tooltip_t insert end "url"
+               $tooltip_t insert end " $remote_url([lindex $spec 2])\n"
+               $tooltip_t insert end "branch"
+               $tooltip_t insert end " [lindex $spec 3]"
+       }
+
+       $tooltip_t conf -state disabled
+       _position_tooltip $this
+}
+
+method _reflog_last {name} {
+       if {[info exists reflog_last($name)]} {
+               return reflog_last($name)
+       }
+
+       set last {}
+       if {[catch {set last [file mtime [gitdir $name]]}]
+       && ![catch {set g [open [gitdir logs $name] r]}]} {
+               fconfigure $g -translation binary
+               while {[gets $g line] >= 0} {
+                       if {[regexp {> ([1-9][0-9]*) } $line line when]} {
+                               set last $when
+                       }
+               }
+               close $g
+       }
+
+       if {$last ne {}} {
+               set last [clock format $last -format {%a %b %e %H:%M:%S %Y}]
+       }
+       set reflog_last($name) $last
+       return $last
+}
+
+method _position_tooltip {} {
+       set max_h [lindex [split [$tooltip_t index end] .] 0]
+       set max_w 0
+       for {set i 1} {$i <= $max_h} {incr i} {
+               set c [lindex [split [$tooltip_t index "$i.0 lineend"] .] 1]
+               if {$c > $max_w} {set max_w $c}
+       }
+       $tooltip_t conf -width $max_w -height $max_h
+
+       set req_w [winfo reqwidth  $tooltip_t]
+       set req_h [winfo reqheight $tooltip_t]
+       set pos_x [expr {[winfo pointerx .] +  5}]
+       set pos_y [expr {[winfo pointery .] + 10}]
+
+       set g "${req_w}x${req_h}"
+       if {$pos_x >= 0} {append g +}
+       append g $pos_x
+       if {$pos_y >= 0} {append g +}
+       append g $pos_y
+
+       wm geometry $tooltip_wm $g
+       raise $tooltip_wm
+}
+
+method _hide_tooltip {} {
+       if {$tooltip_wm ne {}} {
+               destroy $tooltip_wm
+               set tooltip_wm {}
+       }
+       if {$tooltip_timer ne {}} {
+               after cancel $tooltip_timer
+               set tooltip_timer {}
+       }
+}
+
+}
index 9d298d0dcc7d305eded58911c3c0758e94bb7ab6..24e8cecea46d3da6d94b04917a2776e541c234f3 100644 (file)
@@ -5,7 +5,7 @@ proc class {class body} {
        if {[namespace exists $class]} {
                error "class $class already declared"
        }
-       namespace eval $class {
+       namespace eval $class "
                variable __nextid     0
                variable __sealed     0
                variable __field_list {}
@@ -13,10 +13,9 @@ proc class {class body} {
 
                proc cb {name args} {
                        upvar this this
-                       set args [linsert $args 0 $name $this]
-                       return [uplevel [list namespace code $args]]
+                       concat \[list ${class}::\$name \$this\] \$args
                }
-       }
+       "
        namespace eval $class $body
 }
 
@@ -51,15 +50,16 @@ proc constructor {name params body} {
        set mbodyc {}
 
        append mbodyc {set this } $class
-       append mbodyc {::__o[incr } $class {::__nextid]} \;
-       append mbodyc {namespace eval $this {}} \;
+       append mbodyc {::__o[incr } $class {::__nextid]::__d} \;
+       append mbodyc {create_this } $class \;
+       append mbodyc {set __this [namespace qualifiers $this]} \;
 
        if {$__field_list ne {}} {
                append mbodyc {upvar #0}
                foreach n $__field_list {
                        set n [lindex $n 0]
-                       append mbodyc { ${this}::} $n { } $n
-                       regsub -all @$n\\M $body "\${this}::$n" body
+                       append mbodyc { ${__this}::} $n { } $n
+                       regsub -all @$n\\M $body "\${__this}::$n" body
                }
                append mbodyc \;
                foreach n $__field_list {
@@ -80,10 +80,12 @@ proc method {name params body {deleted {}} {del_body {}}} {
        set params [linsert $params 0 this]
        set mbodyc {}
 
+       append mbodyc {set __this [namespace qualifiers $this]} \;
+
        switch $deleted {
        {} {}
        ifdeleted {
-               append mbodyc {if {![namespace exists $this]} }
+               append mbodyc {if {![namespace exists $__this]} }
                append mbodyc \{ $del_body \; return \} \;
        }
        default {
@@ -98,10 +100,12 @@ proc method {name params body {deleted {}} {del_body {}}} {
                        if {   [regexp -all -- $n\\M $body] == 1
                                && [regexp -all -- \\\$$n\\M $body] == 1
                                && [regexp -all -- \\\$$n\\( $body] == 0} {
-                               regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body
+                               regsub -all \
+                                       \\\$$n\\M $body \
+                                       "\[set \${__this}::$n\]" body
                        } else {
-                               append decl { ${this}::} $n { } $n
-                               regsub -all @$n\\M $body "\${this}::$n" body
+                               append decl { ${__this}::} $n { } $n
+                               regsub -all @$n\\M $body "\${__this}::$n" body
                        }
                }
        }
@@ -112,11 +116,21 @@ proc method {name params body {deleted {}} {del_body {}}} {
        namespace eval $class [list proc $name $params $mbodyc]
 }
 
+proc create_this {class} {
+       upvar this this
+       namespace eval [namespace qualifiers $this] [list proc \
+               [namespace tail $this] \
+               [list name args] \
+               "eval \[list ${class}::\$name $this\] \$args" \
+       ]
+}
+
 proc delete_this {{t {}}} {
        if {$t eq {}} {
                upvar this this
                set t $this
        }
+       set t [namespace qualifiers $t]
        if {[namespace exists $t]} {namespace delete $t}
 }
 
index e139f4da2b84718d74dce0e9286dedd3cd8465c5..f857a2ff5bb3d0e151ea471a16a1b233117afc70 100644 (file)
@@ -25,7 +25,7 @@ You are currently in the middle of a merge that has not been fully completed.  Y
        set msg {}
        set parents [list]
        if {[catch {
-                       set fd [open "| git cat-file commit $curHEAD" r]
+                       set fd [git_read cat-file commit $curHEAD]
                        fconfigure $fd -encoding binary -translation lf
                        if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
                                set enc utf-8
@@ -37,9 +37,14 @@ You are currently in the middle of a merge that has not been fully completed.  Y
                                        set enc [string tolower [string range $line 9 end]]
                                }
                        }
-                       set msg [encoding convertfrom $enc [read $fd]]
-                       set msg [string trim $msg]
+                       set msg [read $fd]
                        close $fd
+
+                       set enc [tcl_encoding $enc]
+                       if {$enc ne {}} {
+                               set msg [encoding convertfrom $enc $msg]
+                       }
+                       set msg [string trim $msg]
                } err]} {
                error_popup "Error loading commit data for amend:\n\n$err"
                return
@@ -58,7 +63,7 @@ You are currently in the middle of a merge that has not been fully completed.  Y
        $ui_comm insert end $msg
        $ui_comm edit reset
        $ui_comm edit modified false
-       rescan {set ui_status_value {Ready.}}
+       rescan ui_ready
 }
 
 set GIT_COMMITTER_IDENT {}
@@ -108,12 +113,12 @@ proc create_new_commit {} {
        $ui_comm delete 0.0 end
        $ui_comm edit reset
        $ui_comm edit modified false
-       rescan {set ui_status_value {Ready.}}
+       rescan ui_ready
 }
 
 proc commit_tree {} {
        global HEAD commit_type file_states ui_comm repo_config
-       global ui_status_value pch_error
+       global pch_error
 
        if {[committer_ident] eq {}} return
        if {![lock_index update]} return
@@ -132,7 +137,7 @@ Another Git program has modified this repository since the last scan.  A rescan
 The rescan will be automatically started now.
 }
                unlock_index
-               rescan {set ui_status_value {Ready.}}
+               rescan ui_ready
                return
        }
 
@@ -148,7 +153,7 @@ The rescan will be automatically started now.
                U? {
                        error_popup "Unmerged files cannot be committed.
 
-File [short_path $path] has merge conflicts.  You must resolve them and add the file before committing.
+File [short_path $path] has merge conflicts.  You must resolve them and stage the file before committing.
 "
                        unlock_index
                        return
@@ -164,7 +169,7 @@ File [short_path $path] cannot be committed by this program.
        if {!$files_ready && ![string match *merge $curType]} {
                info_popup {No changes to commit.
 
-You must add at least 1 file before you can commit.
+You must stage at least 1 file before you can commit.
 }
                unlock_index
                return
@@ -206,22 +211,22 @@ A good commit message has the following format:
                return
        }
 
-       set ui_status_value {Calling pre-commit hook...}
+       ui_status {Calling pre-commit hook...}
        set pch_error {}
        set fd_ph [open "| $pchook" r]
-       fconfigure $fd_ph -blocking 0 -translation binary
+       fconfigure $fd_ph -blocking 0 -translation binary -eofchar {}
        fileevent $fd_ph readable \
                [list commit_prehook_wait $fd_ph $curHEAD $msg]
 }
 
 proc commit_prehook_wait {fd_ph curHEAD msg} {
-       global pch_error ui_status_value
+       global pch_error
 
        append pch_error [read $fd_ph]
        fconfigure $fd_ph -blocking 1
        if {[eof $fd_ph]} {
                if {[catch {close $fd_ph}]} {
-                       set ui_status_value {Commit declined by pre-commit hook.}
+                       ui_status {Commit declined by pre-commit hook.}
                        hook_failed_popup pre-commit $pch_error
                        unlock_index
                } else {
@@ -234,25 +239,23 @@ proc commit_prehook_wait {fd_ph curHEAD msg} {
 }
 
 proc commit_writetree {curHEAD msg} {
-       global ui_status_value
-
-       set ui_status_value {Committing changes...}
-       set fd_wt [open "| git write-tree" r]
+       ui_status {Committing changes...}
+       set fd_wt [git_read write-tree]
        fileevent $fd_wt readable \
                [list commit_committree $fd_wt $curHEAD $msg]
 }
 
 proc commit_committree {fd_wt curHEAD msg} {
        global HEAD PARENT MERGE_HEAD commit_type
-       global all_heads current_branch
-       global ui_status_value ui_comm selected_commit_type
+       global current_branch
+       global ui_comm selected_commit_type
        global file_states selected_paths rescan_active
        global repo_config
 
        gets $fd_wt tree_id
        if {$tree_id eq {} || [catch {close $fd_wt} err]} {
                error_popup "write-tree failed:\n\n$err"
-               set ui_status_value {Commit failed.}
+               ui_status {Commit failed.}
                unlock_index
                return
        }
@@ -260,7 +263,7 @@ proc commit_committree {fd_wt curHEAD msg} {
        # -- Verify this wasn't an empty change.
        #
        if {$commit_type eq {normal}} {
-               set fd_ot [open "| git cat-file commit $PARENT" r]
+               set fd_ot [git_read cat-file commit $PARENT]
                fconfigure $fd_ot -encoding binary -translation lf
                set old_tree [gets $fd_ot]
                close $fd_ot
@@ -280,7 +283,7 @@ No files were modified by this commit and it was not a merge commit.
 A rescan will be automatically started now.
 }
                        unlock_index
-                       rescan {set ui_status_value {No changes to commit.}}
+                       rescan {ui_status {No changes to commit.}}
                        return
                }
        }
@@ -289,11 +292,18 @@ A rescan will be automatically started now.
        #
        set msg_p [gitdir COMMIT_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
        }
-       fconfigure $msg_wt -encoding binary -translation binary
-       puts -nonewline $msg_wt [encoding convertto $enc $msg]
+       set use_enc [tcl_encoding $enc]
+       if {$use_enc ne {}} {
+               fconfigure $msg_wt -encoding $use_enc
+       } else {
+               puts stderr "warning: Tcl does not support encoding '$enc'."
+               fconfigure $msg_wt -encoding utf-8
+       }
+       puts -nonewline $msg_wt $msg
        close $msg_wt
 
        # -- Create the commit.
@@ -305,7 +315,7 @@ A rescan will be automatically started now.
        lappend cmd <$msg_p
        if {[catch {set cmt_id [eval git $cmd]} err]} {
                error_popup "commit-tree failed:\n\n$err"
-               set ui_status_value {Commit failed.}
+               ui_status {Commit failed.}
                unlock_index
                return
        }
@@ -327,7 +337,7 @@ A rescan will be automatically started now.
                        git update-ref -m $reflogm HEAD $cmt_id $curHEAD
                } err]} {
                error_popup "update-ref failed:\n\n$err"
-               set ui_status_value {Commit failed.}
+               ui_status {Commit failed.}
                unlock_index
                return
        }
@@ -342,7 +352,12 @@ A rescan will be automatically started now.
 
        # -- Let rerere do its thing.
        #
-       if {[file isdirectory [gitdir rr-cache]]} {
+       if {[get_config rerere.enabled] eq {}} {
+               set rerere [file isdirectory [gitdir rr-cache]]
+       } else {
+               set rerere [is_config_true rerere.enabled]
+       }
+       if {$rerere} {
                catch {git rerere}
        }
 
@@ -364,17 +379,13 @@ A rescan will be automatically started now.
        $ui_comm delete 0.0 end
        $ui_comm edit reset
        $ui_comm edit modified false
+       if {$::GITGUI_BCK_exists} {
+               catch {file delete [gitdir GITGUI_BCK]}
+               set ::GITGUI_BCK_exists 0
+       }
 
        if {[is_enabled singlecommit]} do_quit
 
-       # -- Make sure our current branch exists.
-       #
-       if {$commit_type eq {initial}} {
-               lappend all_heads $current_branch
-               set all_heads [lsort -unique $all_heads]
-               populate_branch_menu
-       }
-
        # -- Update in memory status
        #
        set selected_commit_type new
@@ -416,6 +427,5 @@ A rescan will be automatically started now.
        display_all_files
        unlock_index
        reshow_diff
-       set ui_status_value \
-               "Created commit [string range $cmt_id 0 7]: $subject"
+       ui_status "Created commit [string range $cmt_id 0 7]: $subject"
 }
index 34de5d48593be91fed3b97e66f1a8d9dc06acfdf..6f718fbac3277daed7d0d4e10c76241490c4c216 100644 (file)
@@ -7,6 +7,7 @@ field t_short
 field t_long
 field w
 field console_cr
+field is_toplevel    1; # are we our own window?
 
 constructor new {short_title long_title} {
        set t_short $short_title
@@ -15,10 +16,25 @@ constructor new {short_title long_title} {
        return $this
 }
 
+constructor embed {path title} {
+       set t_short {}
+       set t_long $title
+       set w $path
+       set is_toplevel 0
+       _init $this
+       return $this
+}
+
 method _init {} {
        global M1B
-       make_toplevel top w -autodelete 0
-       wm title $top "[appname] ([reponame]): $t_short"
+
+       if {$is_toplevel} {
+               make_toplevel top w -autodelete 0
+               wm title $top "[appname] ([reponame]): $t_short"
+       } else {
+               frame $w
+       }
+
        set console_cr 1.0
 
        frame $w.m
@@ -61,31 +77,26 @@ method _init {} {
                        $w.m.t tag remove sel 0.0 end
                "
 
-       button $w.ok -text {Close} \
-               -state disabled \
-               -command "destroy $w"
-       pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+       if {$is_toplevel} {
+               button $w.ok -text {Close} \
+                       -state disabled \
+                       -command [list destroy $w]
+               pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+               bind $w <Visibility> [list focus $w]
+       }
 
        bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y"
        bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break"
        bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break"
-       bind $w <Visibility> "focus $w"
 }
 
 method exec {cmd {after {}}} {
-       # -- Cygwin's Tcl tosses the enviroment when we exec our child.
-       #    But most users need that so we have to relogin. :-(
-       #
-       if {[is_Cygwin]} {
-               set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"]
+       if {[lindex $cmd 0] eq {git}} {
+               set fd_f [eval git_read --stderr [lrange $cmd 1 end]]
+       } else {
+               lappend cmd 2>@1
+               set fd_f [_open_stdout_stderr $cmd]
        }
-
-       # -- Tcl won't let us redirect both stdout and stderr to
-       #    the same pipe.  So pass it through cat...
-       #
-       set cmd [concat | $cmd |& cat]
-
-       set fd_f [open $cmd r]
        fconfigure $fd_f -blocking 0 -translation binary
        fileevent $fd_f readable [cb _read $fd_f $after]
 }
@@ -159,20 +170,32 @@ method chain {cmdlist {ok 1}} {
        }
 }
 
+method insert {txt} {
+       if {![winfo exists $w.m.t]} {_init $this}
+       $w.m.t conf -state normal
+       $w.m.t insert end "$txt\n"
+       set console_cr [$w.m.t index {end -1c}]
+       $w.m.t conf -state disabled
+}
+
 method done {ok} {
        if {$ok} {
                if {[winfo exists $w.m.s]} {
                        $w.m.s conf -background green -text {Success}
-                       $w.ok conf -state normal
-                       focus $w.ok
+                       if {$is_toplevel} {
+                               $w.ok conf -state normal
+                               focus $w.ok
+                       }
                }
        } else {
                if {![winfo exists $w.m.s]} {
                        _init $this
                }
                $w.m.s conf -background red -text {Error: Command Failed}
-               $w.ok conf -state normal
-               focus $w.ok
+               if {$is_toplevel} {
+                       $w.ok conf -state normal
+                       focus $w.ok
+               }
        }
        delete_this
 }
index 43e4a289bba9c265d6652ce404d75967a57f7ca3..0657cc2245cec67bbb6d3399935a40247bd0c402 100644 (file)
@@ -2,7 +2,7 @@
 # Copyright (C) 2006, 2007 Shawn Pearce
 
 proc do_stats {} {
-       set fd [open "| git count-objects -v" r]
+       set fd [git_read count-objects -v]
        while {[gets $fd line] > 0} {
                if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
                        set stats($name) $value
@@ -87,3 +87,30 @@ proc do_fsck_objects {} {
        lappend cmd --strict
        console::exec $w $cmd
 }
+
+proc hint_gc {} {
+       set object_limit 8
+       if {[is_Windows]} {
+               set object_limit 1
+       }
+
+       set objects_current [llength [glob \
+               -directory [gitdir objects 42] \
+               -nocomplain \
+               -tails \
+               -- \
+               *]]
+
+       if {$objects_current >= $object_limit} {
+               set objects_current [expr {$objects_current * 256}]
+               set object_limit    [expr {$object_limit    * 256}]
+               if {[ask_popup \
+                       "This repository currently has approximately $objects_current loose objects.
+
+To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist.
+
+Compress the database now?"] eq yes} {
+                       do_gc
+               }
+       }
+}
index 29436b50cb351f88b1ebde20620729f210c37933..e09e1257e1be299caceb0c6f0074c1b43b566974 100644 (file)
@@ -17,7 +17,7 @@ proc clear_diff {} {
 }
 
 proc reshow_diff {} {
-       global ui_status_value file_states file_lists
+       global file_states file_lists
        global current_diff_path current_diff_side
 
        set p $current_diff_path
@@ -49,13 +49,13 @@ A rescan will be automatically started to find other files which may have the sa
 
        clear_diff
        display_file $path __
-       rescan {set ui_status_value {Ready.}} 0
+       rescan ui_ready 0
 }
 
 proc show_diff {path w {lno {}}} {
        global file_states file_lists
        global is_3way_diff diff_active repo_config
-       global ui_diff ui_status_value ui_index ui_workdir
+       global ui_diff ui_index ui_workdir
        global current_diff_path current_diff_side current_diff_header
 
        if {$diff_active || ![lock_index read]} return
@@ -78,7 +78,7 @@ proc show_diff {path w {lno {}}} {
        set current_diff_path $path
        set current_diff_side $w
        set current_diff_header {}
-       set ui_status_value "Loading diff of [escape_path $path]..."
+       ui_status "Loading diff of [escape_path $path]..."
 
        # - Git won't give us the diff, there's nothing to compare to!
        #
@@ -86,13 +86,14 @@ proc show_diff {path w {lno {}}} {
                set max_sz [expr {128 * 1024}]
                if {[catch {
                                set fd [open $path r]
+                               fconfigure $fd -eofchar {}
                                set content [read $fd $max_sz]
                                close $fd
                                set sz [file size $path]
                        } err ]} {
                        set diff_active 0
                        unlock_index
-                       set ui_status_value "Unable to display [escape_path $path]"
+                       ui_status "Unable to display [escape_path $path]"
                        error_popup "Error loading file:\n\n$err"
                        return
                }
@@ -127,11 +128,11 @@ proc show_diff {path w {lno {}}} {
                $ui_diff conf -state disabled
                set diff_active 0
                unlock_index
-               set ui_status_value {Ready.}
+               ui_ready
                return
        }
 
-       set cmd [list | git]
+       set cmd [list]
        if {$w eq $ui_index} {
                lappend cmd diff-index
                lappend cmd --cached
@@ -154,10 +155,10 @@ proc show_diff {path w {lno {}}} {
        lappend cmd --
        lappend cmd $path
 
-       if {[catch {set fd [open $cmd r]} err]} {
+       if {[catch {set fd [eval git_read --nice $cmd]} err]} {
                set diff_active 0
                unlock_index
-               set ui_status_value "Unable to display [escape_path $path]"
+               ui_status "Unable to display [escape_path $path]"
                error_popup "Error loading diff:\n\n$err"
                return
        }
@@ -170,7 +171,7 @@ proc show_diff {path w {lno {}}} {
 }
 
 proc read_diff {fd} {
-       global ui_diff ui_status_value diff_active
+       global ui_diff diff_active
        global is_3way_diff current_diff_header
 
        $ui_diff conf -state normal
@@ -256,7 +257,7 @@ proc read_diff {fd} {
                close $fd
                set diff_active 0
                unlock_index
-               set ui_status_value {Ready.}
+               ui_ready
 
                if {[$ui_diff index end] eq {2.0}} {
                        handle_empty_diff
@@ -271,7 +272,7 @@ proc apply_hunk {x y} {
        if {$current_diff_path eq {} || $current_diff_header eq {}} return
        if {![lock_index apply_hunk]} return
 
-       set apply_cmd {git apply --cached --whitespace=nowarn}
+       set apply_cmd {apply --cached --whitespace=nowarn}
        set mi [lindex $file_states($current_diff_path) 0]
        if {$current_diff_side eq $ui_index} {
                set mode unstage
@@ -301,7 +302,7 @@ proc apply_hunk {x y} {
        }
 
        if {[catch {
-               set p [open "| $apply_cmd" w]
+               set p [eval git_write $apply_cmd]
                fconfigure $p -translation binary -encoding binary
                puts -nonewline $p $current_diff_header
                puts -nonewline $p [$ui_diff get $s_lno $e_lno]
diff --git a/git-gui/lib/encoding.tcl b/git-gui/lib/encoding.tcl
new file mode 100644 (file)
index 0000000..7f06b0d
--- /dev/null
@@ -0,0 +1,276 @@
+# git-gui encoding support
+# Copyright (C) 2005 Paul Mackerras <paulus@samba.org>
+# (Copied from gitk, commit fd8ccbec4f0161)
+
+# This list of encoding names and aliases is distilled from
+# http://www.iana.org/assignments/character-sets.
+# Not all of them are supported by Tcl.
+set encoding_aliases {
+    { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
+      ISO646-US US-ASCII us IBM367 cp367 csASCII }
+    { ISO-10646-UTF-1 csISO10646UTF1 }
+    { ISO_646.basic:1983 ref csISO646basic1983 }
+    { INVARIANT csINVARIANT }
+    { ISO_646.irv:1983 iso-ir-2 irv csISO2IntlRefVersion }
+    { BS_4730 iso-ir-4 ISO646-GB gb uk csISO4UnitedKingdom }
+    { NATS-SEFI iso-ir-8-1 csNATSSEFI }
+    { NATS-SEFI-ADD iso-ir-8-2 csNATSSEFIADD }
+    { NATS-DANO iso-ir-9-1 csNATSDANO }
+    { NATS-DANO-ADD iso-ir-9-2 csNATSDANOADD }
+    { SEN_850200_B iso-ir-10 FI ISO646-FI ISO646-SE se csISO10Swedish }
+    { SEN_850200_C iso-ir-11 ISO646-SE2 se2 csISO11SwedishForNames }
+    { KS_C_5601-1987 iso-ir-149 KS_C_5601-1989 KSC_5601 korean csKSC56011987 }
+    { ISO-2022-KR csISO2022KR }
+    { EUC-KR csEUCKR }
+    { ISO-2022-JP csISO2022JP }
+    { ISO-2022-JP-2 csISO2022JP2 }
+    { JIS_C6220-1969-jp JIS_C6220-1969 iso-ir-13 katakana x0201-7
+      csISO13JISC6220jp }
+    { JIS_C6220-1969-ro iso-ir-14 jp ISO646-JP csISO14JISC6220ro }
+    { IT iso-ir-15 ISO646-IT csISO15Italian }
+    { PT iso-ir-16 ISO646-PT csISO16Portuguese }
+    { ES iso-ir-17 ISO646-ES csISO17Spanish }
+    { greek7-old iso-ir-18 csISO18Greek7Old }
+    { latin-greek iso-ir-19 csISO19LatinGreek }
+    { DIN_66003 iso-ir-21 de ISO646-DE csISO21German }
+    { NF_Z_62-010_(1973) iso-ir-25 ISO646-FR1 csISO25French }
+    { Latin-greek-1 iso-ir-27 csISO27LatinGreek1 }
+    { ISO_5427 iso-ir-37 csISO5427Cyrillic }
+    { JIS_C6226-1978 iso-ir-42 csISO42JISC62261978 }
+    { BS_viewdata iso-ir-47 csISO47BSViewdata }
+    { INIS iso-ir-49 csISO49INIS }
+    { INIS-8 iso-ir-50 csISO50INIS8 }
+    { INIS-cyrillic iso-ir-51 csISO51INISCyrillic }
+    { ISO_5427:1981 iso-ir-54 ISO5427Cyrillic1981 }
+    { ISO_5428:1980 iso-ir-55 csISO5428Greek }
+    { GB_1988-80 iso-ir-57 cn ISO646-CN csISO57GB1988 }
+    { GB_2312-80 iso-ir-58 chinese csISO58GB231280 }
+    { NS_4551-1 iso-ir-60 ISO646-NO no csISO60DanishNorwegian
+      csISO60Norwegian1 }
+    { NS_4551-2 ISO646-NO2 iso-ir-61 no2 csISO61Norwegian2 }
+    { NF_Z_62-010 iso-ir-69 ISO646-FR fr csISO69French }
+    { videotex-suppl iso-ir-70 csISO70VideotexSupp1 }
+    { PT2 iso-ir-84 ISO646-PT2 csISO84Portuguese2 }
+    { ES2 iso-ir-85 ISO646-ES2 csISO85Spanish2 }
+    { MSZ_7795.3 iso-ir-86 ISO646-HU hu csISO86Hungarian }
+    { JIS_C6226-1983 iso-ir-87 x0208 JIS_X0208-1983 csISO87JISX0208 }
+    { greek7 iso-ir-88 csISO88Greek7 }
+    { ASMO_449 ISO_9036 arabic7 iso-ir-89 csISO89ASMO449 }
+    { iso-ir-90 csISO90 }
+    { JIS_C6229-1984-a iso-ir-91 jp-ocr-a csISO91JISC62291984a }
+    { JIS_C6229-1984-b iso-ir-92 ISO646-JP-OCR-B jp-ocr-b
+      csISO92JISC62991984b }
+    { JIS_C6229-1984-b-add iso-ir-93 jp-ocr-b-add csISO93JIS62291984badd }
+    { JIS_C6229-1984-hand iso-ir-94 jp-ocr-hand csISO94JIS62291984hand }
+    { JIS_C6229-1984-hand-add iso-ir-95 jp-ocr-hand-add
+      csISO95JIS62291984handadd }
+    { JIS_C6229-1984-kana iso-ir-96 csISO96JISC62291984kana }
+    { ISO_2033-1983 iso-ir-98 e13b csISO2033 }
+    { ANSI_X3.110-1983 iso-ir-99 CSA_T500-1983 NAPLPS csISO99NAPLPS }
+    { ISO_8859-1:1987 iso-ir-100 ISO_8859-1 ISO-8859-1 latin1 l1 IBM819
+      CP819 csISOLatin1 }
+    { ISO_8859-2:1987 iso-ir-101 ISO_8859-2 ISO-8859-2 latin2 l2 csISOLatin2 }
+    { T.61-7bit iso-ir-102 csISO102T617bit }
+    { T.61-8bit T.61 iso-ir-103 csISO103T618bit }
+    { ISO_8859-3:1988 iso-ir-109 ISO_8859-3 ISO-8859-3 latin3 l3 csISOLatin3 }
+    { ISO_8859-4:1988 iso-ir-110 ISO_8859-4 ISO-8859-4 latin4 l4 csISOLatin4 }
+    { ECMA-cyrillic iso-ir-111 KOI8-E csISO111ECMACyrillic }
+    { CSA_Z243.4-1985-1 iso-ir-121 ISO646-CA csa7-1 ca csISO121Canadian1 }
+    { CSA_Z243.4-1985-2 iso-ir-122 ISO646-CA2 csa7-2 csISO122Canadian2 }
+    { CSA_Z243.4-1985-gr iso-ir-123 csISO123CSAZ24341985gr }
+    { ISO_8859-6:1987 iso-ir-127 ISO_8859-6 ISO-8859-6 ECMA-114 ASMO-708
+      arabic csISOLatinArabic }
+    { ISO_8859-6-E csISO88596E ISO-8859-6-E }
+    { ISO_8859-6-I csISO88596I ISO-8859-6-I }
+    { ISO_8859-7:1987 iso-ir-126 ISO_8859-7 ISO-8859-7 ELOT_928 ECMA-118
+      greek greek8 csISOLatinGreek }
+    { T.101-G2 iso-ir-128 csISO128T101G2 }
+    { ISO_8859-8:1988 iso-ir-138 ISO_8859-8 ISO-8859-8 hebrew
+      csISOLatinHebrew }
+    { ISO_8859-8-E csISO88598E ISO-8859-8-E }
+    { ISO_8859-8-I csISO88598I ISO-8859-8-I }
+    { CSN_369103 iso-ir-139 csISO139CSN369103 }
+    { JUS_I.B1.002 iso-ir-141 ISO646-YU js yu csISO141JUSIB1002 }
+    { ISO_6937-2-add iso-ir-142 csISOTextComm }
+    { IEC_P27-1 iso-ir-143 csISO143IECP271 }
+    { ISO_8859-5:1988 iso-ir-144 ISO_8859-5 ISO-8859-5 cyrillic
+      csISOLatinCyrillic }
+    { JUS_I.B1.003-serb iso-ir-146 serbian csISO146Serbian }
+    { JUS_I.B1.003-mac macedonian iso-ir-147 csISO147Macedonian }
+    { ISO_8859-9:1989 iso-ir-148 ISO_8859-9 ISO-8859-9 latin5 l5 csISOLatin5 }
+    { greek-ccitt iso-ir-150 csISO150 csISO150GreekCCITT }
+    { NC_NC00-10:81 cuba iso-ir-151 ISO646-CU csISO151Cuba }
+    { ISO_6937-2-25 iso-ir-152 csISO6937Add }
+    { GOST_19768-74 ST_SEV_358-88 iso-ir-153 csISO153GOST1976874 }
+    { ISO_8859-supp iso-ir-154 latin1-2-5 csISO8859Supp }
+    { ISO_10367-box iso-ir-155 csISO10367Box }
+    { ISO-8859-10 iso-ir-157 l6 ISO_8859-10:1992 csISOLatin6 latin6 }
+    { latin-lap lap iso-ir-158 csISO158Lap }
+    { JIS_X0212-1990 x0212 iso-ir-159 csISO159JISX02121990 }
+    { DS_2089 DS2089 ISO646-DK dk csISO646Danish }
+    { us-dk csUSDK }
+    { dk-us csDKUS }
+    { JIS_X0201 X0201 csHalfWidthKatakana }
+    { KSC5636 ISO646-KR csKSC5636 }
+    { ISO-10646-UCS-2 csUnicode }
+    { ISO-10646-UCS-4 csUCS4 }
+    { DEC-MCS dec csDECMCS }
+    { hp-roman8 roman8 r8 csHPRoman8 }
+    { macintosh mac csMacintosh }
+    { IBM037 cp037 ebcdic-cp-us ebcdic-cp-ca ebcdic-cp-wt ebcdic-cp-nl
+      csIBM037 }
+    { IBM038 EBCDIC-INT cp038 csIBM038 }
+    { IBM273 CP273 csIBM273 }
+    { IBM274 EBCDIC-BE CP274 csIBM274 }
+    { IBM275 EBCDIC-BR cp275 csIBM275 }
+    { IBM277 EBCDIC-CP-DK EBCDIC-CP-NO csIBM277 }
+    { IBM278 CP278 ebcdic-cp-fi ebcdic-cp-se csIBM278 }
+    { IBM280 CP280 ebcdic-cp-it csIBM280 }
+    { IBM281 EBCDIC-JP-E cp281 csIBM281 }
+    { IBM284 CP284 ebcdic-cp-es csIBM284 }
+    { IBM285 CP285 ebcdic-cp-gb csIBM285 }
+    { IBM290 cp290 EBCDIC-JP-kana csIBM290 }
+    { IBM297 cp297 ebcdic-cp-fr csIBM297 }
+    { IBM420 cp420 ebcdic-cp-ar1 csIBM420 }
+    { IBM423 cp423 ebcdic-cp-gr csIBM423 }
+    { IBM424 cp424 ebcdic-cp-he csIBM424 }
+    { IBM437 cp437 437 csPC8CodePage437 }
+    { IBM500 CP500 ebcdic-cp-be ebcdic-cp-ch csIBM500 }
+    { IBM775 cp775 csPC775Baltic }
+    { IBM850 cp850 850 csPC850Multilingual }
+    { IBM851 cp851 851 csIBM851 }
+    { IBM852 cp852 852 csPCp852 }
+    { IBM855 cp855 855 csIBM855 }
+    { IBM857 cp857 857 csIBM857 }
+    { IBM860 cp860 860 csIBM860 }
+    { IBM861 cp861 861 cp-is csIBM861 }
+    { IBM862 cp862 862 csPC862LatinHebrew }
+    { IBM863 cp863 863 csIBM863 }
+    { IBM864 cp864 csIBM864 }
+    { IBM865 cp865 865 csIBM865 }
+    { IBM866 cp866 866 csIBM866 }
+    { IBM868 CP868 cp-ar csIBM868 }
+    { IBM869 cp869 869 cp-gr csIBM869 }
+    { IBM870 CP870 ebcdic-cp-roece ebcdic-cp-yu csIBM870 }
+    { IBM871 CP871 ebcdic-cp-is csIBM871 }
+    { IBM880 cp880 EBCDIC-Cyrillic csIBM880 }
+    { IBM891 cp891 csIBM891 }
+    { IBM903 cp903 csIBM903 }
+    { IBM904 cp904 904 csIBBM904 }
+    { IBM905 CP905 ebcdic-cp-tr csIBM905 }
+    { IBM918 CP918 ebcdic-cp-ar2 csIBM918 }
+    { IBM1026 CP1026 csIBM1026 }
+    { EBCDIC-AT-DE csIBMEBCDICATDE }
+    { EBCDIC-AT-DE-A csEBCDICATDEA }
+    { EBCDIC-CA-FR csEBCDICCAFR }
+    { EBCDIC-DK-NO csEBCDICDKNO }
+    { EBCDIC-DK-NO-A csEBCDICDKNOA }
+    { EBCDIC-FI-SE csEBCDICFISE }
+    { EBCDIC-FI-SE-A csEBCDICFISEA }
+    { EBCDIC-FR csEBCDICFR }
+    { EBCDIC-IT csEBCDICIT }
+    { EBCDIC-PT csEBCDICPT }
+    { EBCDIC-ES csEBCDICES }
+    { EBCDIC-ES-A csEBCDICESA }
+    { EBCDIC-ES-S csEBCDICESS }
+    { EBCDIC-UK csEBCDICUK }
+    { EBCDIC-US csEBCDICUS }
+    { UNKNOWN-8BIT csUnknown8BiT }
+    { MNEMONIC csMnemonic }
+    { MNEM csMnem }
+    { VISCII csVISCII }
+    { VIQR csVIQR }
+    { KOI8-R csKOI8R }
+    { IBM00858 CCSID00858 CP00858 PC-Multilingual-850+euro }
+    { IBM00924 CCSID00924 CP00924 ebcdic-Latin9--euro }
+    { IBM01140 CCSID01140 CP01140 ebcdic-us-37+euro }
+    { IBM01141 CCSID01141 CP01141 ebcdic-de-273+euro }
+    { IBM01142 CCSID01142 CP01142 ebcdic-dk-277+euro ebcdic-no-277+euro }
+    { IBM01143 CCSID01143 CP01143 ebcdic-fi-278+euro ebcdic-se-278+euro }
+    { IBM01144 CCSID01144 CP01144 ebcdic-it-280+euro }
+    { IBM01145 CCSID01145 CP01145 ebcdic-es-284+euro }
+    { IBM01146 CCSID01146 CP01146 ebcdic-gb-285+euro }
+    { IBM01147 CCSID01147 CP01147 ebcdic-fr-297+euro }
+    { IBM01148 CCSID01148 CP01148 ebcdic-international-500+euro }
+    { IBM01149 CCSID01149 CP01149 ebcdic-is-871+euro }
+    { IBM1047 IBM-1047 }
+    { PTCP154 csPTCP154 PT154 CP154 Cyrillic-Asian }
+    { Amiga-1251 Ami1251 Amiga1251 Ami-1251 }
+    { UNICODE-1-1 csUnicode11 }
+    { CESU-8 csCESU-8 }
+    { BOCU-1 csBOCU-1 }
+    { UNICODE-1-1-UTF-7 csUnicode11UTF7 }
+    { ISO-8859-14 iso-ir-199 ISO_8859-14:1998 ISO_8859-14 latin8 iso-celtic
+      l8 }
+    { ISO-8859-15 ISO_8859-15 Latin-9 }
+    { ISO-8859-16 iso-ir-226 ISO_8859-16:2001 ISO_8859-16 latin10 l10 }
+    { GBK CP936 MS936 windows-936 }
+    { JIS_Encoding csJISEncoding }
+    { Shift_JIS MS_Kanji csShiftJIS }
+    { Extended_UNIX_Code_Packed_Format_for_Japanese csEUCPkdFmtJapanese
+      EUC-JP }
+    { Extended_UNIX_Code_Fixed_Width_for_Japanese csEUCFixWidJapanese }
+    { ISO-10646-UCS-Basic csUnicodeASCII }
+    { ISO-10646-Unicode-Latin1 csUnicodeLatin1 ISO-10646 }
+    { ISO-Unicode-IBM-1261 csUnicodeIBM1261 }
+    { ISO-Unicode-IBM-1268 csUnicodeIBM1268 }
+    { ISO-Unicode-IBM-1276 csUnicodeIBM1276 }
+    { ISO-Unicode-IBM-1264 csUnicodeIBM1264 }
+    { ISO-Unicode-IBM-1265 csUnicodeIBM1265 }
+    { ISO-8859-1-Windows-3.0-Latin-1 csWindows30Latin1 }
+    { ISO-8859-1-Windows-3.1-Latin-1 csWindows31Latin1 }
+    { ISO-8859-2-Windows-Latin-2 csWindows31Latin2 }
+    { ISO-8859-9-Windows-Latin-5 csWindows31Latin5 }
+    { Adobe-Standard-Encoding csAdobeStandardEncoding }
+    { Ventura-US csVenturaUS }
+    { Ventura-International csVenturaInternational }
+    { PC8-Danish-Norwegian csPC8DanishNorwegian }
+    { PC8-Turkish csPC8Turkish }
+    { IBM-Symbols csIBMSymbols }
+    { IBM-Thai csIBMThai }
+    { HP-Legal csHPLegal }
+    { HP-Pi-font csHPPiFont }
+    { HP-Math8 csHPMath8 }
+    { Adobe-Symbol-Encoding csHPPSMath }
+    { HP-DeskTop csHPDesktop }
+    { Ventura-Math csVenturaMath }
+    { Microsoft-Publishing csMicrosoftPublishing }
+    { Windows-31J csWindows31J }
+    { GB2312 csGB2312 }
+    { Big5 csBig5 }
+}
+
+proc tcl_encoding {enc} {
+    global encoding_aliases
+    set names [encoding names]
+    set lcnames [string tolower $names]
+    set enc [string tolower $enc]
+    set i [lsearch -exact $lcnames $enc]
+    if {$i < 0} {
+       # look for "isonnn" instead of "iso-nnn" or "iso_nnn"
+       if {[regsub {^iso[-_]} $enc iso encx]} {
+           set i [lsearch -exact $lcnames $encx]
+       }
+    }
+    if {$i < 0} {
+       foreach l $encoding_aliases {
+           set ll [string tolower $l]
+           if {[lsearch -exact $ll $enc] < 0} continue
+           # look through the aliases for one that tcl knows about
+           foreach e $ll {
+               set i [lsearch -exact $lcnames $e]
+               if {$i < 0} {
+                   if {[regsub {^iso[-_]} $e iso ex]} {
+                       set i [lsearch -exact $lcnames $ex]
+                   }
+               }
+               if {$i >= 0} break
+           }
+           break
+       }
+    }
+    if {$i >= 0} {
+       return [lindex $names $i]
+    }
+    return {}
+}
index d0253ae2ffa72e491784434ff6ad98ed675ff10c..16a22187b26760963069bef14673b1791b311c12 100644 (file)
@@ -51,12 +51,15 @@ proc ask_popup {msg} {
        if {[reponame] ne {}} {
                append title " ([reponame])"
        }
-       return [tk_messageBox \
-               -parent . \
+       set cmd [list tk_messageBox \
                -icon question \
                -type yesno \
                -title $title \
                -message $msg]
+       if {[winfo ismapped .]} {
+               lappend cmd -parent .
+       }
+       eval $cmd
 }
 
 proc hook_failed_popup {hook msg} {
index 42742850eef627262844d2414a593a6e8952d08a..f47f9290c8f82b2c899c839c26d9921e85b9fa0d 100644 (file)
@@ -2,7 +2,7 @@
 # Copyright (C) 2006, 2007 Shawn Pearce
 
 proc update_indexinfo {msg pathList after} {
-       global update_index_cp ui_status_value
+       global update_index_cp
 
        if {![lock_index update]} return
 
@@ -12,12 +12,12 @@ proc update_indexinfo {msg pathList after} {
        set batch [expr {int($totalCnt * .01) + 1}]
        if {$batch > 25} {set batch 25}
 
-       set ui_status_value [format \
+       ui_status [format \
                "$msg... %i/%i files (%.2f%%)" \
                $update_index_cp \
                $totalCnt \
                0.0]
-       set fd [open "| git update-index -z --index-info" w]
+       set fd [git_write update-index -z --index-info]
        fconfigure $fd \
                -blocking 0 \
                -buffering full \
@@ -36,7 +36,7 @@ proc update_indexinfo {msg pathList after} {
 }
 
 proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
-       global update_index_cp ui_status_value
+       global update_index_cp
        global file_states current_diff_path
 
        if {$update_index_cp >= $totalCnt} {
@@ -67,7 +67,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
                display_file $path $new
        }
 
-       set ui_status_value [format \
+       ui_status [format \
                "$msg... %i/%i files (%.2f%%)" \
                $update_index_cp \
                $totalCnt \
@@ -75,7 +75,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
 }
 
 proc update_index {msg pathList after} {
-       global update_index_cp ui_status_value
+       global update_index_cp
 
        if {![lock_index update]} return
 
@@ -85,12 +85,12 @@ proc update_index {msg pathList after} {
        set batch [expr {int($totalCnt * .01) + 1}]
        if {$batch > 25} {set batch 25}
 
-       set ui_status_value [format \
+       ui_status [format \
                "$msg... %i/%i files (%.2f%%)" \
                $update_index_cp \
                $totalCnt \
                0.0]
-       set fd [open "| git update-index --add --remove -z --stdin" w]
+       set fd [git_write update-index --add --remove -z --stdin]
        fconfigure $fd \
                -blocking 0 \
                -buffering full \
@@ -109,7 +109,7 @@ proc update_index {msg pathList after} {
 }
 
 proc write_update_index {fd pathList totalCnt batch msg after} {
-       global update_index_cp ui_status_value
+       global update_index_cp
        global file_states current_diff_path
 
        if {$update_index_cp >= $totalCnt} {
@@ -144,7 +144,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} {
                display_file $path $new
        }
 
-       set ui_status_value [format \
+       ui_status [format \
                "$msg... %i/%i files (%.2f%%)" \
                $update_index_cp \
                $totalCnt \
@@ -152,7 +152,7 @@ proc write_update_index {fd pathList totalCnt batch msg after} {
 }
 
 proc checkout_index {msg pathList after} {
-       global update_index_cp ui_status_value
+       global update_index_cp
 
        if {![lock_index update]} return
 
@@ -162,18 +162,18 @@ proc checkout_index {msg pathList after} {
        set batch [expr {int($totalCnt * .01) + 1}]
        if {$batch > 25} {set batch 25}
 
-       set ui_status_value [format \
+       ui_status [format \
                "$msg... %i/%i files (%.2f%%)" \
                $update_index_cp \
                $totalCnt \
                0.0]
-       set cmd [list git checkout-index]
-       lappend cmd --index
-       lappend cmd --quiet
-       lappend cmd --force
-       lappend cmd -z
-       lappend cmd --stdin
-       set fd [open "| $cmd " w]
+       set fd [git_write checkout-index \
+               --index \
+               --quiet \
+               --force \
+               -z \
+               --stdin \
+               ]
        fconfigure $fd \
                -blocking 0 \
                -buffering full \
@@ -192,7 +192,7 @@ proc checkout_index {msg pathList after} {
 }
 
 proc write_checkout_index {fd pathList totalCnt batch msg after} {
-       global update_index_cp ui_status_value
+       global update_index_cp
        global file_states current_diff_path
 
        if {$update_index_cp >= $totalCnt} {
@@ -217,7 +217,7 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} {
                }
        }
 
-       set ui_status_value [format \
+       ui_status [format \
                "$msg... %i/%i files (%.2f%%)" \
                $update_index_cp \
                $totalCnt \
@@ -249,7 +249,7 @@ proc unstage_helper {txt paths} {
                update_indexinfo \
                        $txt \
                        $pathList \
-                       [concat $after {set ui_status_value {Ready.}}]
+                       [concat $after [list ui_ready]]
        }
 }
 
@@ -293,7 +293,7 @@ proc add_helper {txt paths} {
                update_index \
                        $txt \
                        $pathList \
-                       [concat $after {set ui_status_value {Ready to commit.}}]
+                       [concat $after {ui_status {Ready to commit.}}]
        }
 }
 
@@ -360,7 +360,7 @@ proc revert_helper {txt paths} {
                "[appname] ([reponame])" \
                "Revert changes in $s?
 
-Any unadded changes will be permanently lost by the revert." \
+Any unstaged changes will be permanently lost by the revert." \
                question \
                1 \
                {Do Nothing} \
@@ -370,7 +370,7 @@ Any unadded changes will be permanently lost by the revert." \
                checkout_index \
                        $txt \
                        $pathList \
-                       [concat $after {set ui_status_value {Ready.}}]
+                       [concat $after [list ui_ready]]
        } else {
                unlock_index
        }
index 889182f5454ac0f636fc57c31a16331c097b0c71..5de0d82b1486ef6ff8ce0eaea4221fa393a87d5b 100644 (file)
@@ -1,9 +1,12 @@
 # git-gui branch merge support
 # Copyright (C) 2006, 2007 Shawn Pearce
 
-namespace eval merge {
+class merge {
+
+field w         ; # top level window
+field w_rev     ; # mega-widget to pick the revision to merge
 
-proc _can_merge {} {
+method _can_merge {} {
        global HEAD commit_type file_states
 
        if {[string match amend* $commit_type]} {
@@ -28,7 +31,7 @@ Another Git program has modified this repository since the last scan.  A rescan
 The rescan will be automatically started now.
 }
                unlock_index
-               rescan {set ui_status_value {Ready.}}
+               rescan ui_ready
                return 0
        }
 
@@ -42,7 +45,7 @@ The rescan will be automatically started now.
 
 File [short_path $path] has merge conflicts.
 
-You must resolve them, add the file, and commit to complete the current merge.  Only then can you begin another merge.
+You must resolve them, stage the file, and commit to complete the current merge.  Only then can you begin another merge.
 "
                        unlock_index
                        return 0
@@ -63,145 +66,93 @@ You should complete the current commit before starting a merge.  Doing so will h
        return 1
 }
 
-proc _refs {w list} {
-       set r {}
-       foreach i [$w.source.l curselection] {
-               lappend r [lindex [lindex $list $i] 0]
+method _rev {} {
+       if {[catch {$w_rev commit_or_die}]} {
+               return {}
        }
-       return $r
+       return [$w_rev get]
 }
 
-proc _visualize {w list} {
-       set revs [_refs $w $list]
-       if {$revs eq {}} return
-       lappend revs --not HEAD
-       do_gitk $revs
+method _visualize {} {
+       set rev [_rev $this]
+       if {$rev ne {}} {
+               do_gitk [list $rev --not HEAD]
+       }
 }
 
-proc _start {w list} {
-       global HEAD ui_status_value current_branch
+method _start {} {
+       global HEAD current_branch remote_url
 
-       set cmd [list git merge]
-       set names [_refs $w $list]
-       set revcnt [llength $names]
-       append cmd { } $names
-
-       if {$revcnt == 0} {
+       set name [_rev $this]
+       if {$name eq {}} {
                return
-       } elseif {$revcnt == 1} {
-               set unit branch
-       } elseif {$revcnt <= 15} {
-               set unit branches
-
-               if {[tk_dialog \
-               $w.confirm_octopus \
-               [wm title $w] \
-               "Use octopus merge strategy?
-
-You are merging $revcnt branches at once.  This requires using the octopus merge driver, which may not succeed if there are file-level conflicts.
-" \
-               question \
-               0 \
-               {Cancel} \
-               {Use octopus} \
-               ] != 1} return
-       } else {
-               tk_messageBox \
-                       -icon error \
-                       -type ok \
-                       -title [wm title $w] \
-                       -parent $w \
-                       -message "Too many branches selected.
+       }
 
-You have requested to merge $revcnt branches in an octopus merge.  This exceeds Git's internal limit of 15 branches per merge.
+       set spec [$w_rev get_tracking_branch]
+       set cmit [$w_rev get_commit]
 
-Please select fewer branches.  To merge more than 15 branches, merge the branches in batches.
-"
-               return
+       set fh [open [gitdir FETCH_HEAD] w]
+       fconfigure $fh -translation lf
+       if {$spec eq {}} {
+               set remote .
+               set branch $name
+               set stitle $branch
+       } else {
+               set remote $remote_url([lindex $spec 1])
+               if {[regexp {^[^:@]*@[^:]*:/} $remote]} {
+                       regsub {^[^:@]*@} $remote {} remote
+               }
+               set branch [lindex $spec 2]
+               set stitle "$branch of $remote"
        }
-
-       set msg "Merging $current_branch, [join $names {, }]"
-       set ui_status_value "$msg..."
-       set cons [console::new "Merge" $msg]
-       console::exec $cons $cmd \
-               [namespace code [list _finish $revcnt $cons]]
+       regsub ^refs/heads/ $branch {} branch
+       puts $fh "$cmit\t\tbranch '$branch' of $remote"
+       close $fh
+
+       set cmd [list git]
+       lappend cmd merge
+       lappend cmd --strategy=recursive
+       lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]]
+       lappend cmd HEAD
+       lappend cmd $cmit
+
+       set msg "Merging $current_branch and $stitle"
+       ui_status "$msg..."
+       set cons [console::new "Merge" "merge $stitle"]
+       console::exec $cons $cmd [cb _finish $cons]
 
        wm protocol $w WM_DELETE_WINDOW {}
        destroy $w
 }
 
-proc _finish {revcnt w ok} {
-       console::done $w $ok
+method _finish {cons ok} {
+       console::done $cons $ok
        if {$ok} {
                set msg {Merge completed successfully.}
        } else {
-               if {$revcnt != 1} {
-                       info_popup "Octopus merge failed.
-
-Your merge of $revcnt branches has failed.
-
-There are file-level conflicts between the branches which must be resolved manually.
-
-The working directory will now be reset.
-
-You can attempt this merge again by merging only one branch at a time." $w
-
-                       set fd [open "| git read-tree --reset -u HEAD" r]
-                       fconfigure $fd -blocking 0 -translation binary
-                       fileevent $fd readable \
-                               [namespace code [list _reset_wait $fd]]
-                       set ui_status_value {Aborting... please wait...}
-                       return
-               }
-
                set msg {Merge failed.  Conflict resolution is required.}
        }
        unlock_index
-       rescan [list set ui_status_value $msg]
+       rescan [list ui_status $msg]
+       delete_this
 }
 
-proc dialog {} {
+constructor dialog {} {
        global current_branch
        global M1B
 
-       if {![_can_merge]} return
-
-       set fmt {list %(objectname) %(*objectname) %(refname) %(subject)}
-       set cmd [list git for-each-ref --tcl --format=$fmt]
-       lappend cmd refs/heads
-       lappend cmd refs/remotes
-       lappend cmd refs/tags
-       set fr_fd [open "| $cmd" r]
-       fconfigure $fr_fd -translation binary
-       while {[gets $fr_fd line] > 0} {
-               set line [eval $line]
-               set ref [lindex $line 2]
-               regsub ^refs/(heads|remotes|tags)/ $ref {} ref
-               set subj($ref) [lindex $line 3]
-               lappend sha1([lindex $line 0]) $ref
-               if {[lindex $line 1] ne {}} {
-                       lappend sha1([lindex $line 1]) $ref
-               }
-       }
-       close $fr_fd
-
-       set to_show {}
-       set fr_fd [open "| git rev-list --all --not HEAD"]
-       while {[gets $fr_fd line] > 0} {
-               if {[catch {set ref $sha1($line)}]} continue
-               foreach n $ref {
-                       lappend to_show [list $n $line]
-               }
+       if {![_can_merge $this]} {
+               delete_this
+               return
        }
-       close $fr_fd
-       set to_show [lsort -unique $to_show]
 
-       set w .merge_setup
-       toplevel $w
-       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Merge"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
 
-       set _visualize [namespace code [list _visualize $w $to_show]]
-       set _start [namespace code [list _start $w $to_show]]
+       set _start [cb _start]
 
        label $w.header \
                -text "Merge Into $current_branch" \
@@ -209,55 +160,51 @@ proc dialog {} {
        pack $w.header -side top -fill x
 
        frame $w.buttons
-       button $w.buttons.visualize -text Visualize -command $_visualize
+       button $w.buttons.visualize \
+               -text Visualize \
+               -command [cb _visualize]
        pack $w.buttons.visualize -side left
-       button $w.buttons.create -text Merge -command $_start
-       pack $w.buttons.create -side right
+       button $w.buttons.merge \
+               -text Merge \
+               -command $_start
+       pack $w.buttons.merge -side right
        button $w.buttons.cancel \
                -text {Cancel} \
-               -command "unlock_index;destroy $w"
+               -command [cb _cancel]
        pack $w.buttons.cancel -side right -padx 5
        pack $w.buttons -side bottom -fill x -pady 10 -padx 10
 
-       labelframe $w.source -text {Source Branches}
-       listbox $w.source.l \
-               -height 10 \
-               -width 70 \
-               -font font_diff \
-               -selectmode extended \
-               -yscrollcommand [list $w.source.sby set]
-       scrollbar $w.source.sby -command [list $w.source.l yview]
-       pack $w.source.sby -side right -fill y
-       pack $w.source.l -side left -fill both -expand 1
-       pack $w.source -fill both -expand 1 -pady 5 -padx 5
-
-       foreach ref $to_show {
-               set n [lindex $ref 0]
-               if {[string length $n] > 20} {
-                       set n "[string range $n 0 16]..."
-               }
-               $w.source.l insert end [format {%s %-20s %s} \
-                       [string range [lindex $ref 1] 0 5] \
-                       $n \
-                       $subj([lindex $ref 0])]
-       }
-
-       bind $w.source.l <Key-K> [list event generate %W <Shift-Key-Up>]
-       bind $w.source.l <Key-J> [list event generate %W <Shift-Key-Down>]
-       bind $w.source.l <Key-k> [list event generate %W <Key-Up>]
-       bind $w.source.l <Key-j> [list event generate %W <Key-Down>]
-       bind $w.source.l <Key-h> [list event generate %W <Key-Left>]
-       bind $w.source.l <Key-l> [list event generate %W <Key-Right>]
-       bind $w.source.l <Key-v> $_visualize
+       set w_rev [::choose_rev::new_unmerged $w.rev {Revision To Merge}]
+       pack $w.rev -anchor nw -fill both -expand 1 -pady 5 -padx 5
 
        bind $w <$M1B-Key-Return> $_start
-       bind $w <Visibility> "grab $w; focus $w.source.l"
-       bind $w <Key-Escape> "unlock_index;destroy $w"
-       wm protocol $w WM_DELETE_WINDOW "unlock_index;destroy $w"
-       wm title $w "[appname] ([reponame]): Merge"
+       bind $w <Key-Return> $_start
+       bind $w <Key-Escape> [cb _cancel]
+       wm protocol $w WM_DELETE_WINDOW [cb _cancel]
+
+       bind $w.buttons.merge <Visibility> [cb _visible]
        tkwait window $w
 }
 
+method _visible {} {
+       grab $w
+       if {[is_config_true gui.matchtrackingbranch]} {
+               $w_rev pick_tracking_branch
+       }
+       $w_rev focus_filter
+}
+
+method _cancel {} {
+       wm protocol $w WM_DELETE_WINDOW {}
+       unlock_index
+       destroy $w
+       delete_this
+}
+
+}
+
+namespace eval merge {
+
 proc reset_hard {} {
        global HEAD commit_type file_states
 
@@ -272,20 +219,24 @@ You must finish amending this commit.
        if {![lock_index abort]} return
 
        if {[string match *merge* $commit_type]} {
-               set op merge
+               set op_question "Abort merge?
+
+Aborting the current merge will cause *ALL* uncommitted changes to be lost.
+
+Continue with aborting the current merge?"
        } else {
-               set op commit
-       }
+               set op_question "Reset changes?
 
-       if {[ask_popup "Abort $op?
+Resetting the changes will cause *ALL* uncommitted changes to be lost.
 
-Aborting the current $op will cause *ALL* uncommitted changes to be lost.
+Continue with resetting the current changes?"
+       }
 
-Continue with aborting the current $op?"] eq {yes}} {
-               set fd [open "| git read-tree --reset -u HEAD" r]
+       if {[ask_popup $op_question] eq {yes}} {
+               set fd [git_read --stderr read-tree --reset -u -v HEAD]
                fconfigure $fd -blocking 0 -translation binary
                fileevent $fd readable [namespace code [list _reset_wait $fd]]
-               set ui_status_value {Aborting... please wait...}
+               $::main_status start {Aborting} {files reset}
        } else {
                unlock_index
        }
@@ -294,9 +245,12 @@ Continue with aborting the current $op?"] eq {yes}} {
 proc _reset_wait {fd} {
        global ui_comm
 
-       read $fd
+       $::main_status update_meter [read $fd]
+
+       fconfigure $fd -blocking 1
        if {[eof $fd]} {
-               close $fd
+               set fail [catch {close $fd} err]
+               $::main_status stop
                unlock_index
 
                $ui_comm delete 0.0 end
@@ -308,7 +262,12 @@ proc _reset_wait {fd} {
                catch {file delete [gitdir MERGE_MSG]}
                catch {file delete [gitdir GITGUI_MSG]}
 
-               rescan {set ui_status_value {Abort completed.  Ready.}}
+               if {$fail} {
+                       warn_popup "Abort failed.\n\n$err"
+               }
+               rescan {ui_status {Abort completed.  Ready.}}
+       } else {
+               fconfigure $fd -blocking 0
        }
 }
 
index b29e14e64dd305922f82556e8e445be42a9fbb38..aa9f783afd3c848eb3460c8ee1ff6840478637db 100644 (file)
@@ -55,7 +55,7 @@ proc save_config {} {
 }
 
 proc do_about {} {
-       global appvers copyright
+       global appvers copyright oguilib
        global tcl_patchLevel tk_patchLevel
 
        set w .about_dialog
@@ -94,6 +94,11 @@ $copyright" \
                append v ", Tk version $tk_patchLevel"
        }
 
+       set d {}
+       append d "git wrapper: $::_git\n"
+       append d "git exec dir: [gitexec]\n"
+       append d "git-gui lib: $oguilib"
+
        label $w.vers \
                -text $v \
                -padx 5 -pady 5 \
@@ -103,6 +108,15 @@ $copyright" \
                -relief solid
        pack $w.vers -side top -fill x -padx 5 -pady 5
 
+       label $w.dirs \
+               -text $d \
+               -padx 5 -pady 5 \
+               -justify left \
+               -anchor w \
+               -borderwidth 1 \
+               -relief solid
+       pack $w.dirs -side top -fill x -padx 5 -pady 5
+
        menu $w.ctxm -tearoff 0
        $w.ctxm add command \
                -label {Copy} \
@@ -174,8 +188,11 @@ proc do_options {} {
 
                {b merge.summary {Summarize Merge Commits}}
                {i-1..5 merge.verbosity {Merge Verbosity}}
+               {b merge.diffstat {Show Diffstat After Merge}}
 
                {b gui.trustmtime  {Trust File Modification Timestamps}}
+               {b gui.pruneduringfetch {Prune Tracking Branches During Fetch}}
+               {b gui.matchtrackingbranch {Match Tracking Branches}}
                {i-0..99 gui.diffcontext {Number of Diff Context Lines}}
                {t gui.newbranchtemplate {New Branch Name Template}}
                } {
index 99f353ed7d793ca9accf2c5d246938c8a51fea02..cf9b9d582959e62c805a92a86c33e0f3ae7f304e 100644 (file)
@@ -1,14 +1,13 @@
 # git-gui remote management
 # Copyright (C) 2006, 2007 Shawn Pearce
 
+set some_heads_tracking 0;  # assume not
+
 proc is_tracking_branch {name} {
        global tracking_branches
-
-       if {![catch {set info $tracking_branches($name)}]} {
-               return 1
-       }
-       foreach t [array names tracking_branches] {
-               if {[string match {*/\*} $t] && [string match $t $name]} {
+       foreach spec $tracking_branches {
+               set t [lindex $spec 0]
+               if {$t eq $name || [string match $t $name]} {
                        return 1
                }
        }
@@ -18,36 +17,54 @@ proc is_tracking_branch {name} {
 proc all_tracking_branches {} {
        global tracking_branches
 
-       set all_trackings {}
-       set cmd {}
-       foreach name [array names tracking_branches] {
-               if {[regsub {/\*$} $name {} name]} {
-                       lappend cmd $name
+       set all [list]
+       set pat [list]
+       set cmd [list]
+
+       foreach spec $tracking_branches {
+               set dst [lindex $spec 0]
+               if {[string range $dst end-1 end] eq {/*}} {
+                       lappend pat $spec
+                       lappend cmd [string range $dst 0 end-2]
                } else {
-                       regsub ^refs/(heads|remotes)/ $name {} name
-                       lappend all_trackings $name
+                       lappend all $spec
                }
        }
 
-       if {$cmd ne {}} {
-               set fd [open "| git for-each-ref --format=%(refname) $cmd" r]
-               while {[gets $fd name] > 0} {
-                       regsub ^refs/(heads|remotes)/ $name {} name
-                       lappend all_trackings $name
+       if {$pat ne {}} {
+               set fd [eval git_read for-each-ref --format=%(refname) $cmd]
+               while {[gets $fd n] > 0} {
+                       foreach spec $pat {
+                               set dst [string range [lindex $spec 0] 0 end-2]
+                               set len [string length $dst]
+                               if {[string equal -length $len $dst $n]} {
+                                       set src [string range [lindex $spec 2] 0 end-2]
+                                       set spec [list \
+                                               $n \
+                                               [lindex $spec 1] \
+                                               $src[string range $n $len end] \
+                                               ]
+                                       lappend all $spec
+                               }
+                       }
                }
                close $fd
        }
 
-       return [lsort -unique $all_trackings]
+       return [lsort -index 0 -unique $all]
 }
 
 proc load_all_remotes {} {
        global repo_config
-       global all_remotes tracking_branches
+       global all_remotes tracking_branches some_heads_tracking
+       global remote_url
 
+       set some_heads_tracking 0
        set all_remotes [list]
-       array unset tracking_branches
+       set trck [list]
 
+       set rh_str refs/heads/
+       set rh_len [string length $rh_str]
        set rm_dir [gitdir remotes]
        if {[file isdirectory $rm_dir]} {
                set all_remotes [glob \
@@ -60,12 +77,25 @@ proc load_all_remotes {} {
                        catch {
                                set fd [open [file join $rm_dir $name] r]
                                while {[gets $fd line] >= 0} {
+                                       if {[regexp {^URL:[     ]*(.+)$} $line line url]} {
+                                               set remote_url($name) $url
+                                               continue
+                                       }
                                        if {![regexp {^Pull:[   ]*([^:]+):(.+)$} \
                                                $line line src dst]} continue
-                                       if {![regexp ^refs/ $dst]} {
-                                               set dst "refs/heads/$dst"
+                                       if {[string index $src 0] eq {+}} {
+                                               set src [string range $src 1 end]
+                                       }
+                                       if {![string equal -length 5 refs/ $src]} {
+                                               set src $rh_str$src
                                        }
-                                       set tracking_branches($dst) [list $name $src]
+                                       if {![string equal -length 5 refs/ $dst]} {
+                                               set dst $rh_str$dst
+                                       }
+                                       if {[string equal -length $rh_len $rh_str $dst]} {
+                                               set some_heads_tracking 1
+                                       }
+                                       lappend trck [list $dst $name $src]
                                }
                                close $fd
                        }
@@ -75,19 +105,30 @@ proc load_all_remotes {} {
        foreach line [array names repo_config remote.*.url] {
                if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
                lappend all_remotes $name
+               set remote_url($name) $repo_config(remote.$name.url)
 
                if {[catch {set fl $repo_config(remote.$name.fetch)}]} {
                        set fl {}
                }
                foreach line $fl {
                        if {![regexp {^([^:]+):(.+)$} $line line src dst]} continue
-                       if {![regexp ^refs/ $dst]} {
-                               set dst "refs/heads/$dst"
+                       if {[string index $src 0] eq {+}} {
+                               set src [string range $src 1 end]
+                       }
+                       if {![string equal -length 5 refs/ $src]} {
+                               set src $rh_str$src
+                       }
+                       if {![string equal -length 5 refs/ $dst]} {
+                               set dst $rh_str$dst
                        }
-                       set tracking_branches($dst) [list $name $src]
+                       if {[string equal -length $rh_len $rh_str $dst]} {
+                               set some_heads_tracking 1
+                       }
+                       lappend trck [list $dst $name $src]
                }
        }
 
+       set tracking_branches [lsort -index 0 -unique $trck]
        set all_remotes [lsort -unique $all_remotes]
 }
 
@@ -95,6 +136,7 @@ proc populate_fetch_menu {} {
        global all_remotes repo_config
 
        set m .mbar.fetch
+       set prune_list [list]
        foreach r $all_remotes {
                set enable 0
                if {![catch {set a $repo_config(remote.$r.url)}]} {
@@ -115,11 +157,21 @@ proc populate_fetch_menu {} {
                }
 
                if {$enable} {
+                       lappend prune_list $r
                        $m add command \
                                -label "Fetch from $r..." \
                                -command [list fetch_from $r]
                }
        }
+
+       if {$prune_list ne {}} {
+               $m add separator
+       }
+       foreach r $prune_list {
+               $m add command \
+                       -label "Prune from $r..." \
+                       -command [list prune_from $r]
+       }
 }
 
 proc populate_push_menu {} {
diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl
new file mode 100644 (file)
index 0000000..c88a360
--- /dev/null
@@ -0,0 +1,347 @@
+# git-gui remote branch deleting support
+# Copyright (C) 2007 Shawn Pearce
+
+class remote_branch_delete {
+
+field w
+field head_m
+
+field urltype   {url}
+field remote    {}
+field url       {}
+
+field checktype  {head}
+field check_head {}
+
+field status    {}
+field idle_id   {}
+field full_list {}
+field head_list {}
+field active_ls {}
+field head_cache
+field full_cache
+field cached
+
+constructor dialog {} {
+       global all_remotes M1B
+
+       make_toplevel top w
+       wm title $top "[appname] ([reponame]): Delete Remote Branch"
+       if {$top ne {.}} {
+               wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
+       }
+
+       label $w.header -text {Delete Remote Branch} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.delete -text Delete \
+               -default active \
+               -command [cb _delete]
+       pack $w.buttons.delete -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -command [list destroy $w]
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       labelframe $w.dest -text {From Repository}
+       if {$all_remotes ne {}} {
+               radiobutton $w.dest.remote_r \
+                       -text {Remote:} \
+                       -value remote \
+                       -variable @urltype
+               eval tk_optionMenu $w.dest.remote_m @remote $all_remotes
+               grid $w.dest.remote_r $w.dest.remote_m -sticky w
+               if {[lsearch -sorted -exact $all_remotes origin] != -1} {
+                       set remote origin
+               } else {
+                       set remote [lindex $all_remotes 0]
+               }
+               set urltype remote
+               trace add variable @remote write [cb _write_remote]
+       } else {
+               set urltype url
+       }
+       radiobutton $w.dest.url_r \
+               -text {Arbitrary URL:} \
+               -value url \
+               -variable @urltype
+       entry $w.dest.url_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 50 \
+               -textvariable @url \
+               -validate key \
+               -validatecommand {
+                       if {%d == 1 && [regexp {\s} %S]} {return 0}
+                       return 1
+               }
+       trace add variable @url write [cb _write_url]
+       grid $w.dest.url_r $w.dest.url_t -sticky we -padx {0 5}
+       grid columnconfigure $w.dest 1 -weight 1
+       pack $w.dest -anchor nw -fill x -pady 5 -padx 5
+
+       labelframe $w.heads -text {Branches}
+       listbox $w.heads.l \
+               -height 10 \
+               -width 70 \
+               -listvariable @head_list \
+               -selectmode extended \
+               -yscrollcommand [list $w.heads.sby set]
+       scrollbar $w.heads.sby -command [list $w.heads.l yview]
+
+       frame $w.heads.footer
+       label $w.heads.footer.status \
+               -textvariable @status \
+               -anchor w \
+               -justify left
+       button $w.heads.footer.rescan \
+               -text {Rescan} \
+               -command [cb _rescan]
+       pack $w.heads.footer.status -side left -fill x
+       pack $w.heads.footer.rescan -side right
+
+       pack $w.heads.footer -side bottom -fill x
+       pack $w.heads.sby -side right -fill y
+       pack $w.heads.l -side left -fill both -expand 1
+       pack $w.heads -fill both -expand 1 -pady 5 -padx 5
+
+       labelframe $w.validate -text {Delete Only If}
+       radiobutton $w.validate.head_r \
+               -text {Merged Into:} \
+               -value head \
+               -variable @checktype
+       set head_m [tk_optionMenu $w.validate.head_m @check_head {}]
+       trace add variable @head_list write [cb _write_head_list]
+       trace add variable @check_head write [cb _write_check_head]
+       grid $w.validate.head_r $w.validate.head_m -sticky w
+       radiobutton $w.validate.always_r \
+               -text {Always (Do not perform merge checks)} \
+               -value always \
+               -variable @checktype
+       grid $w.validate.always_r -columnspan 2 -sticky w
+       grid columnconfigure $w.validate 1 -weight 1
+       pack $w.validate -anchor nw -fill x -pady 5 -padx 5
+
+       trace add variable @urltype write [cb _write_urltype]
+       _rescan $this
+
+       bind $w <Key-F5>     [cb _rescan]
+       bind $w <$M1B-Key-r> [cb _rescan]
+       bind $w <$M1B-Key-R> [cb _rescan]
+       bind $w <Key-Return> [cb _delete]
+       bind $w <Key-Escape> [list destroy $w]
+       return $w
+}
+
+method _delete {} {
+       switch $urltype {
+       remote {set uri $remote}
+       url    {set uri $url}
+       }
+
+       set cache $urltype:$uri
+       set crev {}
+       if {$checktype eq {head}} {
+               if {$check_head eq {}} {
+                       tk_messageBox \
+                               -icon error \
+                               -type ok \
+                               -title [wm title $w] \
+                               -parent $w \
+                               -message "A branch is required for 'Merged Into'."
+                       return
+               }
+               set crev $full_cache("$cache\nrefs/heads/$check_head")
+       }
+
+       set not_merged [list]
+       set need_fetch 0
+       set have_selection 0
+       set push_cmd [list git push]
+       lappend push_cmd -v
+       lappend push_cmd $uri
+
+       foreach i [$w.heads.l curselection] {
+               set ref [lindex $full_list $i]
+               if {$crev ne {}} {
+                       set obj $full_cache("$cache\n$ref")
+                       if {[catch {set m [git merge-base $obj $crev]}]} {
+                               set need_fetch 1
+                               set m {}
+                       }
+                       if {$obj ne $m} {
+                               lappend not_merged [lindex $head_list $i]
+                               continue
+                       }
+               }
+
+               lappend push_cmd :$ref
+               set have_selection 1
+       }
+
+       if {$not_merged ne {}} {
+               set msg "The following branches are not completely merged into $check_head:
+
+ - [join $not_merged "\n - "]"
+
+               if {$need_fetch} {
+                       append msg "
+
+One or more of the merge tests failed because you have not fetched the necessary commits.  Try fetching from $uri first."
+               }
+
+               tk_messageBox \
+                       -icon info \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message $msg
+               if {!$have_selection} return
+       }
+
+       if {!$have_selection} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Please select one or more branches to delete."
+               return
+       }
+
+       if {[tk_messageBox \
+               -icon warning \
+               -type yesno \
+               -title [wm title $w] \
+               -parent $w \
+               -message {Recovering deleted branches is difficult.
+
+Delete the selected branches?}] ne yes} {
+               return
+       }
+
+       destroy $w
+
+       set cons [console::new \
+               "push $uri" \
+               "Deleting branches from $uri"]
+       console::exec $cons $push_cmd
+}
+
+method _rescan {{force 1}} {
+       switch $urltype {
+       remote {set uri $remote}
+       url    {set uri $url}
+       }
+
+       if {$force} {
+               unset -nocomplain cached($urltype:$uri)
+       }
+
+       if {$idle_id ne {}} {
+               after cancel $idle_id
+               set idle_id {}
+       }
+
+       _load $this $urltype:$uri $uri
+}
+
+method _write_remote     {args} { set urltype remote }
+method _write_url        {args} { set urltype url    }
+method _write_check_head {args} { set checktype head }
+
+method _write_head_list {args} {
+       $head_m delete 0 end
+       foreach abr $head_list {
+               $head_m insert end radiobutton \
+                       -label $abr \
+                       -value $abr \
+                       -variable @check_head
+       }
+       if {[lsearch -exact -sorted $head_list $check_head] < 0} {
+               set check_head {}
+       }
+}
+
+method _write_urltype {args} {
+       if {$urltype eq {url}} {
+               if {$idle_id ne {}} {
+                       after cancel $idle_id
+               }
+               _load $this none: {}
+               set idle_id [after 1000 [cb _rescan 0]]
+       } else {
+               _rescan $this 0
+       }
+}
+
+method _load {cache uri} {
+       if {$active_ls ne {}} {
+               catch {close $active_ls}
+       }
+
+       if {$uri eq {}} {
+               $w.heads.l conf -state disabled
+               set head_list [list]
+               set full_list [list]
+               set status {No repository selected.}
+               return
+       }
+
+       if {[catch {set x $cached($cache)}]} {
+               set status "Scanning $uri..."
+               $w.heads.l conf -state disabled
+               set head_list [list]
+               set full_list [list]
+               set head_cache($cache) [list]
+               set full_cache($cache) [list]
+               set active_ls [git_read ls-remote $uri]
+               fconfigure $active_ls \
+                       -blocking 0 \
+                       -translation lf \
+                       -encoding utf-8
+               fileevent $active_ls readable [cb _read $cache $active_ls]
+       } else {
+               set status {}
+               set full_list $full_cache($cache)
+               set head_list $head_cache($cache)
+               $w.heads.l conf -state normal
+       }
+}
+
+method _read {cache fd} {
+       if {$fd ne $active_ls} {
+               catch {close $fd}
+               return
+       }
+
+       while {[gets $fd line] >= 0} {
+               if {[string match {*^{}} $line]} continue
+               if {[regexp {^([0-9a-f]{40})    (.*)$} $line _junk obj ref]} {
+                       if {[regsub ^refs/heads/ $ref {} abr]} {
+                               lappend head_list $abr
+                               lappend head_cache($cache) $abr
+                               lappend full_list $ref
+                               lappend full_cache($cache) $ref
+                               set full_cache("$cache\n$ref") $obj
+                       }
+               }
+       }
+
+       if {[eof $fd]} {
+               if {[catch {close $fd} err]} {
+                       set status $err
+                       set head_list [list]
+                       set full_list [list]
+               } else {
+                       set status {}
+                       set cached($cache) 1
+                       $w.heads.l conf -state normal
+               }
+       }
+} ifdeleted {
+       catch {close $fd}
+}
+
+}
index a0a1b7dddd0eab743fac896316d3410848b703a2..c36be2f3cd29b4b0426c312536dca6f697593305 100644 (file)
@@ -13,10 +13,11 @@ proc do_windows_shortcut {} {
                        set fn ${fn}.bat
                }
                if {[catch {
+                               set ge [file normalize [file dirname $::_git]]
                                set fd [open $fn w]
                                puts $fd "@ECHO Entering [reponame]"
                                puts $fd "@ECHO Starting git-gui... please wait..."
-                               puts $fd "@SET PATH=[file normalize [gitexec]];%PATH%"
+                               puts $fd "@SET PATH=$ge;%PATH%"
                                puts $fd "@SET GIT_DIR=[file normalize [gitdir]]"
                                puts -nonewline $fd "@\"[info nameofexecutable]\""
                                puts $fd " \"[file normalize $argv0]\""
@@ -53,7 +54,7 @@ proc do_cygwin_shortcut {} {
                                set sh [exec cygpath \
                                        --windows \
                                        --absolute \
-                                       /bin/sh]
+                                       /bin/sh.exe]
                                set me [exec cygpath \
                                        --unix \
                                        --absolute \
@@ -62,18 +63,12 @@ proc do_cygwin_shortcut {} {
                                        --unix \
                                        --absolute \
                                        [gitdir]]
-                               set gw [exec cygpath \
-                                       --windows \
-                                       --absolute \
-                                       [file dirname [gitdir]]]
-                               regsub -all ' $me "'\\''" me
-                               regsub -all ' $gd "'\\''" gd
-                               puts $fd "@ECHO Entering $gw"
+                               puts $fd "@ECHO Entering [reponame]"
                                puts $fd "@ECHO Starting git-gui... please wait..."
                                puts -nonewline $fd "@\"$sh\" --login -c \""
-                               puts -nonewline $fd "GIT_DIR='$gd'"
-                               puts -nonewline $fd " '$me'"
-                               puts $fd "&\""
+                               puts -nonewline $fd "GIT_DIR=[sq $gd]"
+                               puts -nonewline $fd " [sq $me]"
+                               puts $fd " &\""
                                close $fd
                        } err]} {
                        error_popup "Cannot write script:\n\n$err"
@@ -90,6 +85,9 @@ proc do_macosx_app {} {
                -initialdir [file join $env(HOME) Desktop] \
                -initialfile "Git [reponame].app"]
        if {$fn != {}} {
+               if {[file extension $fn] ne {.app}} {
+                       set fn ${fn}.app
+               }
                if {[catch {
                                set Contents [file join $fn Contents]
                                set MacOS [file join $Contents MacOS]
@@ -123,20 +121,27 @@ proc do_macosx_app {} {
                                close $fd
 
                                set fd [open $exe w]
-                               set gd [file normalize [gitdir]]
-                               set ep [file normalize [gitexec]]
-                               regsub -all ' $gd "'\\''" gd
-                               regsub -all ' $ep "'\\''" ep
                                puts $fd "#!/bin/sh"
-                               foreach name [array names env] {
-                                       if {[string match GIT_* $name]} {
-                                               regsub -all ' $env($name) "'\\''" v
-                                               puts $fd "export $name='$v'"
+                               foreach name [lsort [array names env]] {
+                                       set value $env($name)
+                                       switch -- $name {
+                                       GIT_DIR { set value [file normalize [gitdir]] }
+                                       }
+
+                                       switch -glob -- $name {
+                                       SSH_* -
+                                       GIT_* {
+                                               puts $fd "if test \"z\$$name\" = z; then"
+                                               puts $fd "  export $name=[sq $value]"
+                                               puts $fd "fi &&"
+                                       }
                                        }
                                }
-                               puts $fd "export PATH='$ep':\$PATH"
-                               puts $fd "export GIT_DIR='$gd'"
-                               puts $fd "exec [file normalize $argv0]"
+                               puts $fd "export PATH=[sq [file dirname $::_git]]:\$PATH &&"
+                               puts $fd "cd [sq [file normalize [pwd]]] &&"
+                               puts $fd "exec \\"
+                               puts $fd " [sq [info nameofexecutable]] \\"
+                               puts $fd " [sq [file normalize $argv0]]"
                                close $fd
 
                                file attributes $exe -permissions u+x,g+x,o+x
diff --git a/git-gui/lib/status_bar.tcl b/git-gui/lib/status_bar.tcl
new file mode 100644 (file)
index 0000000..72a8fe1
--- /dev/null
@@ -0,0 +1,96 @@
+# git-gui status bar mega-widget
+# Copyright (C) 2007 Shawn Pearce
+
+class status_bar {
+
+field w         ; # our own window path
+field w_l       ; # text widget we draw messages into
+field w_c       ; # canvas we draw a progress bar into
+field status  {}; # single line of text we show
+field prefix  {}; # text we format into status
+field units   {}; # unit of progress
+field meter   {}; # current core git progress meter (if active)
+
+constructor new {path} {
+       set w $path
+       set w_l $w.l
+       set w_c $w.c
+
+       frame $w \
+               -borderwidth 1 \
+               -relief sunken
+       label $w_l \
+               -textvariable @status \
+               -anchor w \
+               -justify left
+       pack $w_l -side left
+
+       bind $w <Destroy> [cb _delete %W]
+       return $this
+}
+
+method start {msg uds} {
+       if {[winfo exists $w_c]} {
+               $w_c coords bar 0 0 0 20
+       } else {
+               canvas $w_c \
+                       -width 100 \
+                       -height [expr {int([winfo reqheight $w_l] * 0.6)}] \
+                       -borderwidth 1 \
+                       -relief groove \
+                       -highlightt 0
+               $w_c create rectangle 0 0 0 20 -tags bar -fill navy
+               pack $w_c -side right
+       }
+
+       set status $msg
+       set prefix $msg
+       set units  $uds
+       set meter  {}
+}
+
+method update {have total} {
+       set pdone 0
+       if {$total > 0} {
+               set pdone [expr {100 * $have / $total}]
+       }
+
+       set status [format "%s ... %i of %i %s (%2i%%)" \
+               $prefix $have $total $units $pdone]
+       $w_c coords bar 0 0 $pdone 20
+}
+
+method update_meter {buf} {
+       append meter $buf
+       set r [string last "\r" $meter]
+       if {$r == -1} {
+               return
+       }
+
+       set prior [string range $meter 0 $r]
+       set meter [string range $meter [expr {$r + 1}] end]
+       if {[regexp "\\((\\d+)/(\\d+)\\)\\s+done\r\$" $prior _j a b]} {
+               update $this $a $b
+       }
+}
+
+method stop {{msg {}}} {
+       destroy $w_c
+       if {$msg ne {}} {
+               set status $msg
+       }
+}
+
+method show {msg {test {}}} {
+       if {$test eq {} || $status eq $test} {
+               set status $msg
+       }
+}
+
+method _delete {current} {
+       if {$current eq $w} {
+               delete_this
+       }
+}
+
+}
index c0e7d20fce67349938373c0cfdf879aa3137d18f..3a22bd40d4df6f18204afc154cef82d5e3d52f58 100644 (file)
@@ -5,9 +5,19 @@ proc fetch_from {remote} {
        set w [console::new \
                "fetch $remote" \
                "Fetching new changes from $remote"]
-       set cmd [list git fetch]
-       lappend cmd $remote
-       console::exec $w $cmd
+       set cmds [list]
+       lappend cmds [list exec git fetch $remote]
+       if {[is_config_true gui.pruneduringfetch]} {
+               lappend cmds [list exec git remote prune $remote]
+       }
+       console::chain $w $cmds
+}
+
+proc prune_from {remote} {
+       set w [console::new \
+               "remote prune $remote" \
+               "Pruning tracking branches deleted from $remote"]
+       console::exec $w [list git remote prune $remote]
 }
 
 proc push_to {remote} {
@@ -64,7 +74,7 @@ trace add variable push_remote write \
        [list radio_selector push_urltype remote]
 
 proc do_push_anywhere {} {
-       global all_heads all_remotes current_branch
+       global all_remotes current_branch
        global push_urltype push_remote push_url push_thin push_tags
 
        set w .push_setup
@@ -91,7 +101,7 @@ proc do_push_anywhere {} {
                -width 70 \
                -selectmode extended \
                -yscrollcommand [list $w.source.sby set]
-       foreach h $all_heads {
+       foreach h [load_all_heads] {
                $w.source.l insert end $h
                if {$h eq $current_branch} {
                        $w.source.l select set end
index cbc7418e3501b37a701a5177157733d97b405376..b79c6b6a42069168daf4e4ff191d08835f96e40f 100755 (executable)
@@ -8,13 +8,7 @@ USAGE='[--start] [--stop] [--restart]
 
 . git-sh-setup
 
-case "$GIT_DIR" in
-/*)
-       fqgitdir="$GIT_DIR" ;;
-*)
-       fqgitdir="$PWD/$GIT_DIR" ;;
-esac
-
+fqgitdir="$GIT_DIR"
 local="`git config --bool --get instaweb.local`"
 httpd="`git config --get instaweb.httpd`"
 browser="`git config --get instaweb.browser`"
index 58570dff137adfdeb72ebf3f088af7379eded522..c0b00e0fd143ab9d96933f2d39baa94e7e50e0ef 100755 (executable)
@@ -17,10 +17,10 @@ while read dangling type sha1
 do
        case "$dangling" in
        dangling)
-               if git-rev-parse --verify "$sha1^0" >/dev/null 2>/dev/null
+               if git rev-parse --verify "$sha1^0" >/dev/null 2>/dev/null
                then
                        dir="$laf/commit"
-                       git-show-branch "$sha1"
+                       git show-branch "$sha1"
                else
                        dir="$laf/other"
                fi
index a6ed99a7c52ac557b01e4dbf271abdf5b9a18b4b..b7e5d0458470248e65da0893b8b9bb4ced9152da 100755 (executable)
@@ -58,7 +58,7 @@ http://* | https://* | ftp://* )
             curl_extra_args="-k"
         fi
        if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
-               "`git-config --bool http.noEPSV`" = true ]; then
+               "`git config --bool http.noEPSV`" = true ]; then
                curl_extra_args="${curl_extra_args} --disable-epsv"
        fi
        curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
@@ -82,7 +82,7 @@ rsync://* )
        (cd $tmpdir && find refs -type f) |
        while read path
        do
-               cat "$tmpdir/$path" | tr -d '\012'
+               tr -d '\012' <"$tmpdir/$path"
                echo "  $path"
        done &&
        rm -fr $tmpdir
index eb3f473d5a3ac2a894503c1a7601b763548f8722..645e1147dc886f2b1ca6d2020b44db746b082bf0 100755 (executable)
@@ -45,7 +45,7 @@ esac
 # MRT is the current "merge result tree"
 
 MRC=$head MSG= PARENT="-p $head"
-MRT=$(git-write-tree)
+MRT=$(git write-tree)
 CNT=1 ;# counting our head
 NON_FF_MERGE=0
 OCTOPUS_FAILURE=0
@@ -61,7 +61,7 @@ do
                exit 2
        esac
 
-       common=$(git-merge-base --all $MRC $SHA1) ||
+       common=$(git merge-base --all $MRC $SHA1) ||
                die "Unable to find common commit with $SHA1"
 
        case "$LF$common$LF" in
@@ -82,22 +82,22 @@ do
                # We still need to count this as part of the parent set.
 
                echo "Fast forwarding to: $SHA1"
-               git-read-tree -u -m $head $SHA1 || exit
-               MRC=$SHA1 MRT=$(git-write-tree)
+               git read-tree -u -m $head $SHA1 || exit
+               MRC=$SHA1 MRT=$(git write-tree)
                continue
        fi
 
        NON_FF_MERGE=1
 
        echo "Trying simple merge with $SHA1"
-       git-read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2
-       next=$(git-write-tree 2>/dev/null)
+       git read-tree -u -m --aggressive  $common $MRT $SHA1 || exit 2
+       next=$(git write-tree 2>/dev/null)
        if test $? -ne 0
        then
                echo "Simple merge did not work, trying automatic merge."
                git-merge-index -o git-merge-one-file -a ||
                OCTOPUS_FAILURE=1
-               next=$(git-write-tree 2>/dev/null)
+               next=$(git write-tree 2>/dev/null)
        fi
 
        # We have merged the other branch successfully.  Ideally
index 7d62d7902cb1d7cd2749091b86d532306bea89b3..1e7727d2763e744b94f55c2056eccaab12367c9e 100755 (executable)
@@ -13,7 +13,7 @@
 #   $7 - file in branch2 mode (or empty)
 #
 # Handle some trivial cases.. The _really_ trivial cases have
-# been handled already by git-read-tree, but that one doesn't
+# been handled already by git read-tree, but that one doesn't
 # do any merges that might change the tree layout.
 
 case "${1:-.}${2:-.}${3:-.}" in
@@ -27,14 +27,15 @@ case "${1:-.}${2:-.}${3:-.}" in
                # read-tree checked that index matches HEAD already,
                # so we know we do not have this path tracked.
                # there may be an unrelated working tree file here,
-               # which we should just leave unmolested.
-               exit 0
+               # which we should just leave unmolested.  Make sure
+               # we do not have it in the index, though.
+               exec git update-index --remove -- "$4"
        fi
        if test -f "$4"; then
                rm -f -- "$4" &&
                rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || :
        fi &&
-               exec git-update-index --remove -- "$4"
+               exec git update-index --remove -- "$4"
        ;;
 
 #
@@ -42,7 +43,8 @@ case "${1:-.}${2:-.}${3:-.}" in
 #
 ".$2.")
        # the other side did not add and we added so there is nothing
-       # to be done.
+       # to be done, except making the path merged.
+       exec git update-index --add --cacheinfo "$6" "$2" "$4"
        ;;
 "..$3")
        echo "Adding $4"
@@ -50,8 +52,8 @@ case "${1:-.}${2:-.}${3:-.}" in
                echo "ERROR: untracked $4 is overwritten by the merge."
                exit 1
        }
-       git-update-index --add --cacheinfo "$6$7" "$2$3" "$4" &&
-               exec git-checkout-index -u -f -- "$4"
+       git update-index --add --cacheinfo "$7" "$3" "$4" &&
+               exec git checkout-index -u -f -- "$4"
        ;;
 
 #
@@ -64,8 +66,8 @@ case "${1:-.}${2:-.}${3:-.}" in
                exit 1
        fi
        echo "Adding $4"
-       git-update-index --add --cacheinfo "$6" "$2" "$4" &&
-               exec git-checkout-index -u -f -- "$4"
+       git update-index --add --cacheinfo "$6" "$2" "$4" &&
+               exec git checkout-index -u -f -- "$4"
        ;;
 
 #
@@ -84,11 +86,11 @@ case "${1:-.}${2:-.}${3:-.}" in
        case "$1" in
        '')
                echo "Added $4 in both, but differently."
-               # This extracts OUR file in $orig, and uses git-apply to
+               # This extracts OUR file in $orig, and uses git apply to
                # remove lines that are unique to ours.
                orig=`git-unpack-file $2`
                sz0=`wc -c <"$orig"`
-               diff -u -La/$orig -Lb/$orig $orig $src2 | git-apply --no-add 
+               diff -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
                sz1=`wc -c <"$orig"`
 
                # If we do not have enough common material, it is not
@@ -104,12 +106,12 @@ case "${1:-.}${2:-.}${3:-.}" in
        # Be careful for funny filename such as "-L" in "$4", which
        # would confuse "merge" greatly.
        src1=`git-unpack-file $2`
-       git-merge-file "$src1" "$orig" "$src2"
+       git merge-file "$src1" "$orig" "$src2"
        ret=$?
 
        # Create the working tree file, using "our tree" version from the
        # index, and then store the result of the merge.
-       git-checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4"
+       git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4"
        rm -f -- "$orig" "$src1" "$src2"
 
        if [ "$6" != "$7" ]; then
@@ -124,7 +126,7 @@ case "${1:-.}${2:-.}${3:-.}" in
                echo "ERROR: Merge conflict in $4"
                exit 1
        fi
-       exec git-update-index -- "$4"
+       exec git update-index -- "$4"
        ;;
 
 *)
index 2b6a5c0d104b09b2eb471be9ec86e215ac003b0a..c81a790aa60ea08144dd0899d1aee2ec334a1825 100755 (executable)
@@ -9,6 +9,6 @@
 # because the current index is what we will be committing as the
 # merge result.
 
-git-diff-index --quiet --cached HEAD || exit 2
+git diff-index --quiet --cached HEAD || exit 2
 
 exit 0
index 75e1de49ac9adb443e6065c93ac691c27f0f5518..bb19da2cc15715df9c96ddea18c6d449ce49b6e4 100755 (executable)
@@ -37,10 +37,10 @@ then
        exit 2
 fi
 
-git-update-index --refresh 2>/dev/null
-git-read-tree -u -m --aggressive $bases $head $remotes || exit 2
+git update-index --refresh 2>/dev/null
+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
index 4faecb933df33fd4b72e0968d7ee04e1ccc49aec..4b1e595363cdcfae9d9c567d98127ee83bcc2b23 100755 (executable)
@@ -40,10 +40,10 @@ case "$bases" in
        for c in $bases
        do
                rm -f $G
-               GIT_INDEX_FILE=$G git-read-tree -m $c $head $remotes \
+               GIT_INDEX_FILE=$G git read-tree -m $c $head $remotes \
                         2>/dev/null || continue
                # Count the paths that are unmerged.
-               cnt=`GIT_INDEX_FILE=$G git-ls-files --unmerged | wc -l`
+               cnt=`GIT_INDEX_FILE=$G git ls-files --unmerged | wc -l`
                if test $best_cnt -le 0 -o $cnt -le $best_cnt
                then
                        best=$c
@@ -63,10 +63,10 @@ case "$bases" in
        ;;
 esac
 
-git-update-index --refresh 2>/dev/null
-git-read-tree -u -m $common $head $remotes || exit 2
+git update-index --refresh 2>/dev/null
+git read-tree -u -m $common $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
index b2f8a2acd398b7c61b68a59c6d3631ca84be0727..5ccf28251d51d3ee10675d48cf04829f5d6d8c06 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
+USAGE='[-n] [--summary] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
 
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
@@ -31,7 +31,7 @@ dropsave() {
 
 savestate() {
        # Stash away any local modifications.
-       git-diff-index -z --name-only $head |
+       git diff-index -z --name-only $head |
        cpio -0 -o >"$GIT_DIR/MERGE_SAVE"
 }
 
@@ -40,7 +40,7 @@ restorestate() {
        then
                git reset --hard $head >/dev/null
                cpio -iuv <"$GIT_DIR/MERGE_SAVE"
-               git-update-index --refresh >/dev/null
+               git update-index --refresh >/dev/null
        fi
 }
 
@@ -57,7 +57,7 @@ finish_up_to_date () {
 squash_message () {
        echo Squashed commit of the following:
        echo
-       git-log --no-merges ^"$head" $remote
+       git log --no-merges ^"$head" $remote
 }
 
 finish () {
@@ -79,7 +79,7 @@ finish () {
                        echo "No merge message -- not updating HEAD"
                        ;;
                *)
-                       git-update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
+                       git update-ref -m "$rlogm" HEAD "$1" "$head" || exit 1
                        ;;
                esac
                ;;
@@ -88,24 +88,24 @@ finish () {
        '')
                ;;
        ?*)
-               case "$no_summary" in
-               '')
-                       git-diff-tree --stat --summary -M "$head" "$1"
-                       ;;
-               esac
+               if test "$show_diffstat" = t
+               then
+                       # We want color (if set), but no pager
+                       GIT_PAGER='' git diff --stat --summary -M "$head" "$1"
+               fi
                ;;
        esac
 }
 
 merge_name () {
        remote="$1"
-       rh=$(git-rev-parse --verify "$remote^0" 2>/dev/null) || return
-       bh=$(git-show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
+       rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
+       bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
        if test "$rh" = "$bh"
        then
                echo "$rh               branch '$remote' of ."
        elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
-               git-show-ref -q --verify "refs/heads/$truname" 2>/dev/null
+               git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
        then
                echo "$rh               branch '$truname' (early part) of ."
        elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
@@ -125,7 +125,9 @@ do
        case "$1" in
        -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
                --no-summa|--no-summar|--no-summary)
-               no_summary=t ;;
+               show_diffstat=false ;;
+       --summary)
+               show_diffstat=t ;;
        --sq|--squ|--squa|--squas|--squash)
                squash=t no_commit=t ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
@@ -167,6 +169,11 @@ do
        shift
 done
 
+if test -z "$show_diffstat"; then
+    test "$(git config --bool merge.diffstat)" = false && show_diffstat=false
+    test -z "$show_diffstat" && show_diffstat=t
+fi
+
 # This could be traditional "merge <msg> HEAD <commit>..."  and the
 # way we can tell it is to see if the second token is HEAD, but some
 # people might have misused the interface and used a committish that
@@ -174,15 +181,15 @@ done
 # have "-m" so it is an additional safety measure to check for it.
 
 if test -z "$have_message" &&
-       second_token=$(git-rev-parse --verify "$2^0" 2>/dev/null) &&
-       head_commit=$(git-rev-parse --verify "HEAD" 2>/dev/null) &&
+       second_token=$(git rev-parse --verify "$2^0" 2>/dev/null) &&
+       head_commit=$(git rev-parse --verify "HEAD" 2>/dev/null) &&
        test "$second_token" = "$head_commit"
 then
        merge_msg="$1"
        shift
        head_arg="$1"
        shift
-elif ! git-rev-parse --verify HEAD >/dev/null 2>&1
+elif ! git rev-parse --verify HEAD >/dev/null 2>&1
 then
        # If the merged head is a valid one there is no reason to
        # forbid "git merge" into a branch yet to be born.  We do
@@ -196,8 +203,8 @@ then
        rh=$(git rev-parse --verify "$1^0") ||
                die "$1 - not something we can merge"
 
-       git-update-ref -m "initial pull" HEAD "$rh" "" &&
-       git-read-tree --reset -u HEAD
+       git update-ref -m "initial pull" HEAD "$rh" "" &&
+       git read-tree --reset -u HEAD
        exit
 
 else
@@ -212,11 +219,11 @@ else
        merge_name=$(for remote
                do
                        merge_name "$remote"
-               done | git-fmt-merge-msg
+               done | git fmt-merge-msg
        )
        merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
 fi
-head=$(git-rev-parse --verify "$head_arg"^0) || usage
+head=$(git rev-parse --verify "$head_arg"^0) || usage
 
 # All the rest are remote heads
 test "$#" = 0 && usage ;# we need at least one remote head.
@@ -225,7 +232,7 @@ set_reflog_action "merge $*"
 remoteheads=
 for remote
 do
-       remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
+       remotehead=$(git rev-parse --verify "$remote"^0 2>/dev/null) ||
            die "$remote - not something we can merge"
        remoteheads="${remoteheads}$remotehead "
        eval GITHEAD_$remotehead='"$remote"'
@@ -237,7 +244,7 @@ case "$use_strategies" in
 '')
        case "$#" in
        1)
-               var="`git-config --get pull.twohead`"
+               var="`git config --get pull.twohead`"
                if test -n "$var"
                then
                        use_strategies="$var"
@@ -245,7 +252,7 @@ case "$use_strategies" in
                        use_strategies="$default_twohead_strategies"
                fi ;;
        *)
-               var="`git-config --get pull.octopus`"
+               var="`git config --get pull.octopus`"
                if test -n "$var"
                then
                        use_strategies="$var"
@@ -271,10 +278,10 @@ done
 
 case "$#" in
 1)
-       common=$(git-merge-base --all $head "$@")
+       common=$(git merge-base --all $head "$@")
        ;;
 *)
-       common=$(git-show-branch --merge-base $head "$@")
+       common=$(git show-branch --merge-base $head "$@")
        ;;
 esac
 echo "$head" >"$GIT_DIR/ORIG_HEAD"
@@ -294,15 +301,15 @@ f,*)
        ;;
 ?,1,"$head",*)
        # Again the most common case of merging one remote.
-       echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
-       git-update-index --refresh 2>/dev/null
+       echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)"
+       git update-index --refresh 2>/dev/null
        msg="Fast forward"
        if test -n "$have_message"
        then
                msg="$msg (no commit created; -m option ignored)"
        fi
-       new_head=$(git-rev-parse --verify "$1^0") &&
-       git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
+       new_head=$(git rev-parse --verify "$1^0") &&
+       git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
        finish "$new_head" "$msg" || exit
        dropsave
        exit 0
@@ -314,7 +321,7 @@ f,*)
 ?,1,*,)
        # We are not doing octopus, not fast forward, and have only
        # one common.
-       git-update-index --refresh 2>/dev/null
+       git update-index --refresh 2>/dev/null
        case " $use_strategies " in
        *' recursive '*|*' recur '*)
                : run merge later
@@ -323,13 +330,13 @@ f,*)
                # See if it is really trivial.
                git var GIT_COMMITTER_IDENT >/dev/null || exit
                echo "Trying really trivial in-index merge..."
-               if git-read-tree --trivial -m -u -v $common $head "$1" &&
-                  result_tree=$(git-write-tree)
+               if git read-tree --trivial -m -u -v $common $head "$1" &&
+                  result_tree=$(git write-tree)
                then
                        echo "Wonderful."
                        result_commit=$(
                                printf '%s\n' "$merge_msg" |
-                               git-commit-tree $result_tree -p HEAD -p "$1"
+                               git commit-tree $result_tree -p HEAD -p "$1"
                        ) || exit
                        finish "$result_commit" "In-index merge"
                        dropsave
@@ -343,7 +350,7 @@ f,*)
        up_to_date=t
        for remote
        do
-               common_one=$(git-merge-base --all $head $remote)
+               common_one=$(git merge-base --all $head $remote)
                if test "$common_one" != "$remote"
                then
                        up_to_date=f
@@ -412,8 +419,8 @@ do
        if test "$exit" -eq 1
        then
            cnt=`{
-               git-diff-files --name-only
-               git-ls-files --unmerged
+               git diff-files --name-only
+               git ls-files --unmerged
            } | wc -l`
            if test $best_cnt -le 0 -o $cnt -le $best_cnt
            then
@@ -425,15 +432,15 @@ do
     }
 
     # Automerge succeeded.
-    result_tree=$(git-write-tree) && break
+    result_tree=$(git write-tree) && break
 done
 
 # If we have a resulting tree, that means the strategy module
 # auto resolved the merge cleanly.
 if test '' != "$result_tree"
 then
-    parents=$(git-show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
-    result_commit=$(printf '%s\n' "$merge_msg" | git-commit-tree $result_tree $parents) || exit
+    parents=$(git show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
+    result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
     finish "$result_commit" "Merge made by $wt_strategy."
     dropsave
     exit 0
@@ -489,9 +496,6 @@ Conflicts:
                sed -e 's/^[^   ]*      /       /' |
                uniq
        } >>"$GIT_DIR/MERGE_MSG"
-       if test -d "$GIT_DIR/rr-cache"
-       then
-               git-rerere
-       fi
+       git rerere
        die "Automatic merge failed; fix conflicts and then commit the result."
 fi
index e62351bcbabc00a3d89ddf1c8dd936cea400501f..47a80553ad3283c49cae0fbffcf444b93c8e34b7 100755 (executable)
@@ -5,7 +5,7 @@
 # Copyright (c) 2006 Theodore Y. Ts'o
 #
 # This file is licensed under the GPL v2, or a later version
-# at the discretion of Junio C Hammano.
+# at the discretion of Junio C Hamano.
 #
 
 USAGE='[--tool=tool] [file to merge] ...'
@@ -65,14 +65,14 @@ resolve_symlink_merge () {
        read ans
        case "$ans" in
            [lL]*)
-               git-checkout-index -f --stage=2 -- "$path"
-               git-add -- "$path"
+               git checkout-index -f --stage=2 -- "$path"
+               git add -- "$path"
                cleanup_temp_files --save-backup
                return
                ;;
            [rR]*)
-               git-checkout-index -f --stage=3 -- "$path"
-               git-add -- "$path"
+               git checkout-index -f --stage=3 -- "$path"
+               git add -- "$path"
                cleanup_temp_files --save-backup
                return
                ;;
@@ -93,12 +93,12 @@ resolve_deleted_merge () {
        read ans
        case "$ans" in
            [mMcC]*)
-               git-add -- "$path"
+               git add -- "$path"
                cleanup_temp_files --save-backup
                return
                ;;
            [dD]*)
-               git-rm -- "$path" > /dev/null
+               git rm -- "$path" > /dev/null
                cleanup_temp_files
                return
                ;;
@@ -140,7 +140,7 @@ remove_backup () {
 merge_file () {
     path="$1"
 
-    f=`git-ls-files -u -- "$path"`
+    f=`git ls-files -u -- "$path"`
     if test -z "$f" ; then
        if test ! -f "$path" ; then
            echo "$path: file not found"
@@ -215,6 +215,12 @@ merge_file () {
            check_unchanged
            save_backup
            ;;
+       gvimdiff)
+               touch "$BACKUP"
+               gvimdiff -f -- "$LOCAL" "$path" "$REMOTE"
+               check_unchanged
+               save_backup
+               ;;
        xxdiff)
            touch "$BACKUP"
            if base_present ; then
@@ -291,9 +297,9 @@ do
 done
 
 if test -z "$merge_tool"; then
-    merge_tool=`git-config merge.tool`
+    merge_tool=`git config merge.tool`
     case "$merge_tool" in
-       kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | "")
+       kdiff3 | tkdiff | xxdiff | meld | opendiff | emerge | vimdiff | gvimdiff | "")
            ;; # happy
        *)
            echo >&2 "git config option merge.tool set to unknown tool: $merge_tool"
@@ -304,28 +310,42 @@ if test -z "$merge_tool"; then
 fi
 
 if test -z "$merge_tool" ; then
-    if type kdiff3 >/dev/null 2>&1 && test -n "$DISPLAY"; then
-       merge_tool="kdiff3";
-    elif type tkdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
-       merge_tool=tkdiff
-    elif type xxdiff >/dev/null 2>&1 && test -n "$DISPLAY"; then
-       merge_tool=xxdiff
-    elif type meld >/dev/null 2>&1 && test -n "$DISPLAY"; then
-       merge_tool=meld
-    elif type opendiff >/dev/null 2>&1; then
-       merge_tool=opendiff
-    elif type emacs >/dev/null 2>&1; then
-       merge_tool=emerge
-    elif type vimdiff >/dev/null 2>&1; then
-       merge_tool=vimdiff
-    else
+    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
+        if test $i = emerge ; then
+            cmd=emacs
+        else
+            cmd=$i
+        fi
+        if type $cmd > /dev/null 2>&1; then
+            merge_tool=$i
+            break
+        fi
+    done
+    if test -z "$merge_tool" ; then
        echo "No available merge resolution programs available."
        exit 1
     fi
 fi
 
 case "$merge_tool" in
-    kdiff3|tkdiff|meld|xxdiff|vimdiff|opendiff)
+    kdiff3|tkdiff|meld|xxdiff|vimdiff|gvimdiff|opendiff)
        if ! type "$merge_tool" > /dev/null 2>&1; then
            echo "The merge tool $merge_tool is not available"
            exit 1
diff --git a/git-p4import.py b/git-p4import.py
deleted file mode 100644 (file)
index 60a758b..0000000
+++ /dev/null
@@ -1,361 +0,0 @@
-#!/usr/bin/python
-#
-# This tool is copyright (c) 2006, Sean Estabrooks.
-# It is released under the Gnu Public License, version 2.
-#
-# Import Perforce branches into Git repositories.
-# Checking out the files is done by calling the standard p4
-# client which you must have properly configured yourself
-#
-
-import marshal
-import os
-import sys
-import time
-import getopt
-
-from signal import signal, \
-   SIGPIPE, SIGINT, SIG_DFL, \
-   default_int_handler
-
-signal(SIGPIPE, SIG_DFL)
-s = signal(SIGINT, SIG_DFL)
-if s != default_int_handler:
-   signal(SIGINT, s)
-
-def die(msg, *args):
-    for a in args:
-        msg = "%s %s" % (msg, a)
-    print "git-p4import fatal error:", msg
-    sys.exit(1)
-
-def usage():
-    print "USAGE: git-p4import [-q|-v]  [--authors=<file>]  [-t <timezone>]  [//p4repo/path <branch>]"
-    sys.exit(1)
-
-verbosity = 1
-logfile = "/dev/null"
-ignore_warnings = False
-stitch = 0
-tagall = True
-
-def report(level, msg, *args):
-    global verbosity
-    global logfile
-    for a in args:
-        msg = "%s %s" % (msg, a)
-    fd = open(logfile, "a")
-    fd.writelines(msg)
-    fd.close()
-    if level <= verbosity:
-        print msg
-
-class p4_command:
-    def __init__(self, _repopath):
-        try:
-            global logfile
-            self.userlist = {}
-            if _repopath[-1] == '/':
-                self.repopath = _repopath[:-1]
-            else:
-                self.repopath = _repopath
-            if self.repopath[-4:] != "/...":
-                self.repopath= "%s/..." % self.repopath
-            f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
-            a = f.readlines()
-            if f.close():
-                raise
-        except:
-                die("Could not find the \"p4\" command")
-
-    def p4(self, cmd, *args):
-        global logfile
-        cmd = "%s %s" % (cmd, ' '.join(args))
-        report(2, "P4:", cmd)
-        f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
-        list = []
-        while 1:
-           try:
-                list.append(marshal.load(f))
-           except EOFError:
-                break
-        self.ret = f.close()
-        return list
-
-    def sync(self, id, force=False, trick=False, test=False):
-        if force:
-            ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
-        elif trick:
-            ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
-        elif test:
-            ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
-        else:
-            ret = self.p4("sync    %s@%s"%(self.repopath, id))[0]
-        if ret['code'] == "error":
-             data = ret['data'].upper()
-             if data.find('VIEW') > 0:
-                 die("Perforce reports %s is not in client view"% self.repopath)
-             elif data.find('UP-TO-DATE') < 0:
-                 die("Could not sync files from perforce", self.repopath)
-
-    def changes(self, since=0):
-        try:
-            list = []
-            for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
-                list.append(rec['change'])
-            list.reverse()
-            return list
-        except:
-            return []
-
-    def authors(self, filename):
-        f=open(filename)
-        for l in f.readlines():
-            self.userlist[l[:l.find('=')].rstrip()] = \
-                    (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
-        f.close()
-        for f,e in self.userlist.items():
-                report(2, f, ":", e[0], "  <", e[1], ">")
-
-    def _get_user(self, id):
-        if not self.userlist.has_key(id):
-            try:
-                user = self.p4("users", id)[0]
-                self.userlist[id] = (user['FullName'], user['Email'])
-            except:
-                self.userlist[id] = (id, "")
-        return self.userlist[id]
-
-    def _format_date(self, ticks):
-        symbol='+'
-        name = time.tzname[0]
-        offset = time.timezone
-        if ticks[8]:
-            name = time.tzname[1]
-            offset = time.altzone
-        if offset < 0:
-            offset *= -1
-            symbol = '-'
-        localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
-        tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
-        return "%s %s" % (tickso, localo)
-
-    def where(self):
-        try:
-            return self.p4("where %s" % self.repopath)[-1]['path']
-        except:
-            return ""
-
-    def describe(self, num):
-        desc = self.p4("describe -s", num)[0]
-        self.msg = desc['desc']
-        self.author, self.email = self._get_user(desc['user'])
-        self.date = self._format_date(time.localtime(long(desc['time'])))
-        return self
-
-class git_command:
-    def __init__(self):
-        try:
-            self.version = self.git("--version")[0][12:].rstrip()
-        except:
-            die("Could not find the \"git\" command")
-        try:
-            self.gitdir = self.get_single("rev-parse --git-dir")
-            report(2, "gdir:", self.gitdir)
-        except:
-            die("Not a git repository... did you forget to \"git init\" ?")
-        try:
-            self.cdup = self.get_single("rev-parse --show-cdup")
-            if self.cdup != "":
-                os.chdir(self.cdup)
-            self.topdir = os.getcwd()
-            report(2, "topdir:", self.topdir)
-        except:
-            die("Could not find top git directory")
-
-    def git(self, cmd):
-        global logfile
-        report(2, "GIT:", cmd)
-        f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
-        r=f.readlines()
-        self.ret = f.close()
-        return r
-
-    def get_single(self, cmd):
-        return self.git(cmd)[0].rstrip()
-
-    def current_branch(self):
-        try:
-            testit = self.git("rev-parse --verify HEAD")[0]
-            return self.git("symbolic-ref HEAD")[0][11:].rstrip()
-        except:
-            return None
-
-    def get_config(self, variable):
-        try:
-            return self.git("config --get %s" % variable)[0].rstrip()
-        except:
-            return None
-
-    def set_config(self, variable, value):
-        try:
-            self.git("config %s %s"%(variable, value) )
-        except:
-            die("Could not set %s to " % variable, value)
-
-    def make_tag(self, name, head):
-        self.git("tag -f %s %s"%(name,head))
-
-    def top_change(self, branch):
-        try:
-            a=self.get_single("name-rev --tags refs/heads/%s" % branch)
-            loc = a.find(' tags/') + 6
-            if a[loc:loc+3] != "p4/":
-                raise
-            return int(a[loc+3:][:-2])
-        except:
-            return 0
-
-    def update_index(self):
-        self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
-
-    def checkout(self, branch):
-        self.git("checkout %s" % branch)
-
-    def repoint_head(self, branch):
-        self.git("symbolic-ref HEAD refs/heads/%s" % branch)
-
-    def remove_files(self):
-        self.git("ls-files | xargs rm")
-
-    def clean_directories(self):
-        self.git("clean -d")
-
-    def fresh_branch(self, branch):
-        report(1, "Creating new branch", branch)
-        self.git("ls-files | xargs rm")
-        os.remove(".git/index")
-        self.repoint_head(branch)
-        self.git("clean -d")
-
-    def basedir(self):
-        return self.topdir
-
-    def commit(self, author, email, date, msg, id):
-        self.update_index()
-        fd=open(".msg", "w")
-        fd.writelines(msg)
-        fd.close()
-        try:
-                current = self.get_single("rev-parse --verify HEAD")
-                head = "-p HEAD"
-        except:
-                current = ""
-                head = ""
-        tree = self.get_single("write-tree")
-        for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
-            os.environ['GIT_AUTHOR_%s'%r] = l
-            os.environ['GIT_COMMITTER_%s'%r] = l
-        commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
-        os.remove(".msg")
-        self.make_tag("p4/%s"%id, commit)
-        self.git("update-ref HEAD %s %s" % (commit, current) )
-
-try:
-    opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
-            ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
-except getopt.GetoptError:
-    usage()
-
-for o, a in opts:
-    if o == "-q":
-        verbosity = 0
-    if o == "-v":
-        verbosity += 1
-    if o in ("--log"):
-        logfile = a
-    if o in ("--notags"):
-        tagall = False
-    if o in ("-h", "--help"):
-        usage()
-    if o in ("--ignore"):
-        ignore_warnings = True
-
-git = git_command()
-branch=git.current_branch()
-
-for o, a in opts:
-    if o in ("-t", "--timezone"):
-        git.set_config("perforce.timezone", a)
-    if o in ("--stitch"):
-        git.set_config("perforce.%s.path" % branch, a)
-        stitch = 1
-
-if len(args) == 2:
-    branch = args[1]
-    git.checkout(branch)
-    if branch == git.current_branch():
-        die("Branch %s already exists!" % branch)
-    report(1, "Setting perforce to ", args[0])
-    git.set_config("perforce.%s.path" % branch, args[0])
-elif len(args) != 0:
-    die("You must specify the perforce //depot/path and git branch")
-
-p4path = git.get_config("perforce.%s.path" % branch)
-if p4path == None:
-    die("Do not know Perforce //depot/path for git branch", branch)
-
-p4 = p4_command(p4path)
-
-for o, a in opts:
-    if o in ("-a", "--authors"):
-        p4.authors(a)
-
-localdir = git.basedir()
-if p4.where()[:len(localdir)] != localdir:
-    report(1, "**WARNING** Appears p4 client is misconfigured")
-    report(1, "   for sync from %s to %s" % (p4.repopath, localdir))
-    if ignore_warnings != True:
-        die("Reconfigure or use \"--ignore\" on command line")
-
-if stitch == 0:
-    top = git.top_change(branch)
-else:
-    top = 0
-changes = p4.changes(top)
-count = len(changes)
-if count == 0:
-    report(1, "Already up to date...")
-    sys.exit(0)
-
-ptz = git.get_config("perforce.timezone")
-if ptz:
-    report(1, "Setting timezone to", ptz)
-    os.environ['TZ'] = ptz
-    time.tzset()
-
-if stitch == 1:
-    git.remove_files()
-    git.clean_directories()
-    p4.sync(changes[0], force=True)
-elif top == 0 and branch != git.current_branch():
-    p4.sync(changes[0], test=True)
-    report(1, "Creating new initial commit");
-    git.fresh_branch(branch)
-    p4.sync(changes[0], force=True)
-else:
-    p4.sync(changes[0], trick=True)
-
-report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
-for id in changes:
-    report(1, "Importing changeset", id)
-    change = p4.describe(id)
-    p4.sync(id)
-    if tagall :
-            git.commit(change.author, change.email, change.date, change.msg, id)
-    else:
-            git.commit(change.author, change.email, change.date, change.msg, "import")
-    if stitch == 1:
-        git.clean_directories()
-        stitch = 0
-
index 0506b12cb26966cfaa9eb73f3e1d1a9c5d66cc02..695a4094bb4230341618bd6f16d0bea9bff2e826 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 --git-dir 2>/dev/null) || :;
 
 get_data_source () {
        case "$1" in
@@ -13,7 +13,7 @@ get_data_source () {
                echo self
                ;;
        *)
-               if test "$(git-config --get "remote.$1.url")"
+               if test "$(git config --get "remote.$1.url")"
                then
                        echo config
                elif test -f "$GIT_DIR/remotes/$1"
@@ -38,7 +38,7 @@ get_remote_url () {
                echo "$1"
                ;;
        config)
-               git-config --get "remote.$1.url"
+               git config --get "remote.$1.url"
                ;;
        remotes)
                sed -ne '/^URL: */{
@@ -55,8 +55,8 @@ get_remote_url () {
 }
 
 get_default_remote () {
-       curr_branch=$(git-symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
-       origin=$(git-config --get "branch.$curr_branch.remote")
+       curr_branch=$(git symbolic-ref -q HEAD | sed -e 's|^refs/heads/||')
+       origin=$(git config --get "branch.$curr_branch.remote")
        echo ${origin:-origin}
 }
 
@@ -66,7 +66,7 @@ get_remote_default_refs_for_push () {
        '' | branches | self)
                ;; # no default push mapping, just send matching refs.
        config)
-               git-config --get-all "remote.$1.push" ;;
+               git config --get-all "remote.$1.push" ;;
        remotes)
                sed -ne '/^Push: */{
                        s///p
@@ -107,9 +107,9 @@ canon_refs_list_for_fetch () {
                shift
                if test "$remote" = "$(get_default_remote)"
                then
-                       curr_branch=$(git-symbolic-ref -q HEAD | \
+                       curr_branch=$(git symbolic-ref -q HEAD | \
                            sed -e 's|^refs/heads/||')
-                       merge_branches=$(git-config \
+                       merge_branches=$(git config \
                            --get-all "branch.${curr_branch}.merge")
                fi
                if test -z "$merge_branches" && test $is_explicit != explicit
@@ -156,7 +156,7 @@ canon_refs_list_for_fetch () {
 
                if local_ref_name=$(expr "z$local" : 'zrefs/\(.*\)')
                then
-                  git-check-ref-format "$local_ref_name" ||
+                  git check-ref-format "$local_ref_name" ||
                   die "* refusing to create funny ref '$local_ref_name' locally"
                fi
                echo "${dot_prefix}${force}${remote}:${local}"
@@ -171,11 +171,11 @@ get_remote_default_refs_for_fetch () {
                echo "HEAD:" ;;
        self)
                canon_refs_list_for_fetch -d "$1" \
-                       $(git-for-each-ref --format='%(refname):')
+                       $(git for-each-ref --format='%(refname):')
                ;;
        config)
                canon_refs_list_for_fetch -d "$1" \
-                       $(git-config --get-all "remote.$1.fetch") ;;
+                       $(git config --get-all "remote.$1.fetch") ;;
        branches)
                remote_branch=$(sed -ne '/#/s/.*#//p' "$GIT_DIR/branches/$1")
                case "$remote_branch" in '') remote_branch=master ;; esac
@@ -254,7 +254,7 @@ get_uploadpack () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
        config)
-               uplp=$(git-config --get "remote.$1.uploadpack")
+               uplp=$(git config --get "remote.$1.uploadpack")
                echo ${uplp:-git-upload-pack}
                ;;
        *)
index a3665d7751cc836dc669390df7b972c29575037b..5e96d1f228da506b11452abec70e7c50d8907733 100755 (executable)
@@ -22,6 +22,9 @@ do
        -n|--n|--no|--no-|--no-s|--no-su|--no-sum|--no-summ|\
                --no-summa|--no-summar|--no-summary)
                no_summary=-n ;;
+       --summary)
+               no_summary=$1
+               ;;
        --no-c|--no-co|--no-com|--no-comm|--no-commi|--no-commit)
                no_commit=--no-commit ;;
        --sq|--squ|--squa|--squas|--squash)
@@ -51,10 +54,10 @@ do
        shift
 done
 
-orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+orig_head=$(git rev-parse --verify HEAD 2>/dev/null)
 git-fetch --update-head-ok "$@" || exit 1
 
-curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
+curr_head=$(git rev-parse --verify HEAD 2>/dev/null)
 if test "$curr_head" != "$orig_head"
 then
        # The fetch involved updating the current branch.
@@ -66,8 +69,8 @@ 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-read-tree -u -m "$orig_head" "$curr_head" ||
+       git update-index --refresh 2>/dev/null
+       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
 $ git diff '$orig_head'
@@ -83,7 +86,7 @@ merge_head=$(sed -e '/        not-for-merge   /d' \
 
 case "$merge_head" in
 '')
-       curr_branch=$(git-symbolic-ref -q HEAD)
+       curr_branch=$(git symbolic-ref -q HEAD)
        case $? in
          0) ;;
          1) echo >&2 "You are not currently on a branch; you must explicitly"
@@ -110,11 +113,11 @@ esac
 
 if test -z "$orig_head"
 then
-       git-update-ref -m "initial pull" HEAD $merge_head "" &&
-       git-read-tree --reset -u HEAD || exit 1
+       git update-ref -m "initial pull" HEAD $merge_head "" &&
+       git read-tree --reset -u HEAD || exit 1
        exit
 fi
 
-merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
+merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
 exec git-merge $no_summary $no_commit $squash $strategy_args \
        "$merge_name" HEAD $merge_head
index a7a6757dd8a3e0ff2635537a83591169c259986f..9de54d19fbc96c18ad52b1fe6b1126432d40f17a 100755 (executable)
@@ -67,13 +67,14 @@ tmp_info="$tmp_dir/info"
 
 
 # Find the intial commit
-commit=$(git-rev-parse HEAD)
+commit=$(git rev-parse HEAD)
 
 mkdir $tmp_dir || exit 2
-for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do
+for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do
        echo $patch_name
-       (cat $QUILT_PATCHES/$patch_name | git-mailinfo "$tmp_msg" "$tmp_patch" > "$tmp_info") || exit 3
-       test -s .dotest/patch || {
+       git mailinfo "$tmp_msg" "$tmp_patch" \
+               <"$QUILT_PATCHES/$patch_name" >"$tmp_info" || exit 3
+       test -s "$tmp_patch" || {
                echo "Patch is empty.  Was it split wrong?"
                exit 1
        }
@@ -113,10 +114,10 @@ for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do
        fi
 
        if [ -z "$dry_run" ] ; then
-               git-apply --index -C1 "$tmp_patch" &&
-               tree=$(git-write-tree) &&
-               commit=$( (echo "$SUBJECT"; echo; cat "$tmp_msg") | git-commit-tree $tree -p $commit) &&
-               git-update-ref -m "quiltimport: $patch_name" HEAD $commit || exit 4
+               git apply --index -C1 "$tmp_patch" &&
+               tree=$(git write-tree) &&
+               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
 rm -rf $tmp_dir || exit 5
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
new file mode 100755 (executable)
index 0000000..bdec462
--- /dev/null
@@ -0,0 +1,483 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+
+# SHORT DESCRIPTION
+#
+# This script makes it easy to fix up commits in the middle of a series,
+# and rearrange commits.
+#
+# The original idea comes from Eric W. Biederman, in
+# http://article.gmane.org/gmane.comp.version-control.git/22407
+
+USAGE='(--continue | --abort | --skip | [--preserve-merges] [--verbose]
+       [--onto <branch>] <upstream> [<branch>])'
+
+. git-sh-setup
+require_work_tree
+
+DOTEST="$GIT_DIR/.dotest-merge"
+TODO="$DOTEST"/git-rebase-todo
+DONE="$DOTEST"/done
+MSG="$DOTEST"/message
+SQUASH_MSG="$DOTEST"/message-squash
+REWRITTEN="$DOTEST"/rewritten
+PRESERVE_MERGES=
+STRATEGY=
+VERBOSE=
+test -d "$REWRITTEN" && PRESERVE_MERGES=t
+test -f "$DOTEST"/strategy && STRATEGY="$(cat "$DOTEST"/strategy)"
+test -f "$DOTEST"/verbose && VERBOSE=t
+
+warn () {
+       echo "$*" >&2
+}
+
+output () {
+       case "$VERBOSE" in
+       '')
+               "$@" > "$DOTEST"/output 2>&1
+               status=$?
+               test $status != 0 &&
+                       cat "$DOTEST"/output
+               return $status
+       ;;
+       *)
+               "$@"
+       esac
+}
+
+require_clean_work_tree () {
+       # test if working tree is dirty
+       git rev-parse --verify HEAD > /dev/null &&
+       git update-index --refresh &&
+       git diff-files --quiet &&
+       git diff-index --cached --quiet HEAD ||
+       die "Working tree is dirty"
+}
+
+ORIG_REFLOG_ACTION="$GIT_REFLOG_ACTION"
+
+comment_for_reflog () {
+       case "$ORIG_REFLOG_ACTION" in
+       ''|rebase*)
+               GIT_REFLOG_ACTION="rebase -i ($1)"
+               export GIT_REFLOG_ACTION
+       esac
+}
+
+mark_action_done () {
+       sed -e 1q < "$TODO" >> "$DONE"
+       sed -e 1d < "$TODO" >> "$TODO".new
+       mv -f "$TODO".new "$TODO"
+       count=$(($(wc -l < "$DONE")))
+       total=$(($count+$(wc -l < "$TODO")))
+       printf "Rebasing (%d/%d)\r" $count $total
+       test -z "$VERBOSE" || echo
+}
+
+make_patch () {
+       parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
+       git diff "$parent_sha1".."$1" > "$DOTEST"/patch
+}
+
+die_with_patch () {
+       test -f "$DOTEST"/message ||
+               git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message
+       test -f "$DOTEST"/author-script ||
+               get_author_ident_from_commit $sha1 > "$DOTEST"/author-script
+       make_patch "$1"
+       die "$2"
+}
+
+die_abort () {
+       rm -rf "$DOTEST"
+       die "$1"
+}
+
+pick_one () {
+       case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+       output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
+       test -d "$REWRITTEN" &&
+               pick_one_preserving_merges "$@" && return
+       parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
+       current_sha1=$(git rev-parse --verify HEAD)
+       if test $current_sha1 = $parent_sha1; then
+               output git reset --hard $sha1
+               test "a$1" = a-n && output git reset --soft $current_sha1
+               sha1=$(git rev-parse --short $sha1)
+               output warn Fast forward to $sha1
+       else
+               output git cherry-pick $STRATEGY "$@"
+       fi
+}
+
+pick_one_preserving_merges () {
+       case "$1" in -n) sha1=$2 ;; *) sha1=$1 ;; esac
+       sha1=$(git rev-parse $sha1)
+
+       if test -f "$DOTEST"/current-commit
+       then
+               current_commit=$(cat "$DOTEST"/current-commit) &&
+               git rev-parse HEAD > "$REWRITTEN"/$current_commit &&
+               rm "$DOTEST"/current-commit ||
+               die "Cannot write current commit's replacement sha1"
+       fi
+
+       # rewrite parents; if none were rewritten, we can fast-forward.
+       fast_forward=t
+       preserve=t
+       new_parents=
+       for p in $(git rev-list --parents -1 $sha1 | cut -d\  -f2-)
+       do
+               if test -f "$REWRITTEN"/$p
+               then
+                       preserve=f
+                       new_p=$(cat "$REWRITTEN"/$p)
+                       test $p != $new_p && fast_forward=f
+                       case "$new_parents" in
+                       *$new_p*)
+                               ;; # do nothing; that parent is already there
+                       *)
+                               new_parents="$new_parents $new_p"
+                       esac
+               fi
+       done
+       case $fast_forward in
+       t)
+               output warn "Fast forward to $sha1"
+               test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
+               ;;
+       f)
+               test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
+
+               first_parent=$(expr "$new_parents" : " \([^ ]*\)")
+               # detach HEAD to current parent
+               output git checkout $first_parent 2> /dev/null ||
+                       die "Cannot move HEAD to $first_parent"
+
+               echo $sha1 > "$DOTEST"/current-commit
+               case "$new_parents" in
+               \ *\ *)
+                       # redo merge
+                       author_script=$(get_author_ident_from_commit $sha1)
+                       eval "$author_script"
+                       msg="$(git cat-file commit $sha1 | \
+                               sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
+                       # NEEDSWORK: give rerere a chance
+                       if ! output git merge $STRATEGY -m "$msg" $new_parents
+                       then
+                               echo "$msg" > "$GIT_DIR"/MERGE_MSG
+                               die Error redoing merge $sha1
+                       fi
+                       ;;
+               *)
+                       output git cherry-pick $STRATEGY "$@" ||
+                               die_with_patch $sha1 "Could not pick $sha1"
+               esac
+       esac
+}
+
+nth_string () {
+       case "$1" in
+       *1[0-9]|*[04-9]) echo "$1"th;;
+       *1) echo "$1"st;;
+       *2) echo "$1"nd;;
+       *3) echo "$1"rd;;
+       esac
+}
+
+make_squash_message () {
+       if test -f "$SQUASH_MSG"; then
+               COUNT=$(($(sed -n "s/^# This is [^0-9]*\([0-9]\+\).*/\1/p" \
+                       < "$SQUASH_MSG" | tail -n 1)+1))
+               echo "# This is a combination of $COUNT commits."
+               sed -n "2,\$p" < "$SQUASH_MSG"
+       else
+               COUNT=2
+               echo "# This is a combination of two commits."
+               echo "# The first commit's message is:"
+               echo
+               git cat-file commit HEAD | sed -e '1,/^$/d'
+               echo
+       fi
+       echo "# This is the $(nth_string $COUNT) commit message:"
+       echo
+       git cat-file commit $1 | sed -e '1,/^$/d'
+}
+
+peek_next_command () {
+       sed -n "1s/ .*$//p" < "$TODO"
+}
+
+do_next () {
+       test -f "$DOTEST"/message && rm "$DOTEST"/message
+       test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script
+       read command sha1 rest < "$TODO"
+       case "$command" in
+       \#|'')
+               mark_action_done
+               ;;
+       pick)
+               comment_for_reflog pick
+
+               mark_action_done
+               pick_one $sha1 ||
+                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               ;;
+       edit)
+               comment_for_reflog edit
+
+               mark_action_done
+               pick_one $sha1 ||
+                       die_with_patch $sha1 "Could not apply $sha1... $rest"
+               make_patch $sha1
+               warn
+               warn "You can amend the commit now, with"
+               warn
+               warn "  git commit --amend"
+               warn
+               exit 0
+               ;;
+       squash)
+               comment_for_reflog squash
+
+               test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
+                       die "Cannot 'squash' without a previous commit"
+
+               mark_action_done
+               make_squash_message $sha1 > "$MSG"
+               case "$(peek_next_command)" in
+               squash)
+                       EDIT_COMMIT=
+                       USE_OUTPUT=output
+                       cp "$MSG" "$SQUASH_MSG"
+               ;;
+               *)
+                       EDIT_COMMIT=-e
+                       USE_OUTPUT=
+                       test -f "$SQUASH_MSG" && rm "$SQUASH_MSG"
+               esac
+
+               failed=f
+               output git reset --soft HEAD^
+               pick_one -n $sha1 || failed=t
+               author_script=$(get_author_ident_from_commit $sha1)
+               echo "$author_script" > "$DOTEST"/author-script
+               case $failed in
+               f)
+                       # This is like --amend, but with a different message
+                       eval "$author_script"
+                       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+                       $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT
+                       ;;
+               t)
+                       cp "$MSG" "$GIT_DIR"/MERGE_MSG
+                       warn
+                       warn "Could not apply $sha1... $rest"
+                       die_with_patch $sha1 ""
+               esac
+               ;;
+       *)
+               warn "Unknown command: $command $sha1 $rest"
+               die_with_patch $sha1 "Please fix this in the file $TODO."
+       esac
+       test -s "$TODO" && return
+
+       comment_for_reflog finish &&
+       HEADNAME=$(cat "$DOTEST"/head-name) &&
+       OLDHEAD=$(cat "$DOTEST"/head) &&
+       SHORTONTO=$(git rev-parse --short $(cat "$DOTEST"/onto)) &&
+       if test -d "$REWRITTEN"
+       then
+               test -f "$DOTEST"/current-commit &&
+                       current_commit=$(cat "$DOTEST"/current-commit) &&
+                       git rev-parse HEAD > "$REWRITTEN"/$current_commit
+               NEWHEAD=$(cat "$REWRITTEN"/$OLDHEAD)
+       else
+               NEWHEAD=$(git rev-parse HEAD)
+       fi &&
+       message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
+       git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
+       git symbolic-ref HEAD $HEADNAME && {
+               test ! -f "$DOTEST"/verbose ||
+                       git diff --stat $(cat "$DOTEST"/head)..HEAD
+       } &&
+       rm -rf "$DOTEST" &&
+       warn "Successfully rebased and updated $HEADNAME."
+
+       exit
+}
+
+do_rest () {
+       while :
+       do
+               do_next
+       done
+}
+
+while case $# in 0) break ;; esac
+do
+       case "$1" in
+       --continue)
+               comment_for_reflog continue
+
+               test -d "$DOTEST" || die "No interactive rebase running"
+
+               # commit if necessary
+               git rev-parse --verify HEAD > /dev/null &&
+               git update-index --refresh &&
+               git diff-files --quiet &&
+               ! git diff-index --cached --quiet HEAD &&
+               . "$DOTEST"/author-script &&
+               export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE &&
+               git commit -F "$DOTEST"/message -e
+
+               require_clean_work_tree
+               do_rest
+               ;;
+       --abort)
+               comment_for_reflog abort
+
+               test -d "$DOTEST" || die "No interactive rebase running"
+
+               HEADNAME=$(cat "$DOTEST"/head-name)
+               HEAD=$(cat "$DOTEST"/head)
+               git symbolic-ref HEAD $HEADNAME &&
+               output git reset --hard $HEAD &&
+               rm -rf "$DOTEST"
+               exit
+               ;;
+       --skip)
+               comment_for_reflog skip
+
+               test -d "$DOTEST" || die "No interactive rebase running"
+
+               output git reset --hard && do_rest
+               ;;
+       -s|--strategy)
+               shift
+               case "$#,$1" in
+               *,*=*)
+                       STRATEGY="-s `expr "z$1" : 'z-[^=]*=\(.*\)'`" ;;
+               1,*)
+                       usage ;;
+               *)
+                       STRATEGY="-s $2"
+                       shift ;;
+               esac
+               ;;
+       --merge)
+               # we use merge anyway
+               ;;
+       -C*)
+               die "Interactive rebase uses merge, so $1 does not make sense"
+               ;;
+       -v|--verbose)
+               VERBOSE=t
+               ;;
+       -p|--preserve-merges)
+               PRESERVE_MERGES=t
+               ;;
+       -i|--interactive)
+               # yeah, we know
+               ;;
+       ''|-h)
+               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"
+
+               comment_for_reflog start
+
+               ONTO=
+               case "$1" in
+               --onto)
+                       ONTO=$(git rev-parse --verify "$2") ||
+                               die "Does not point to a valid commit: $2"
+                       shift; shift
+                       ;;
+               esac
+
+               require_clean_work_tree
+
+               mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
+               if test ! -z "$2"
+               then
+                       output git show-ref --verify --quiet "refs/heads/$2" ||
+                               die "Invalid branchname: $2"
+                       output git checkout "$2" ||
+                               die "Could not checkout $2"
+               fi
+
+               HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
+               UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
+
+               test -z "$ONTO" && ONTO=$UPSTREAM
+
+               : > "$DOTEST"/interactive || die "Could not mark as interactive"
+               git symbolic-ref HEAD > "$DOTEST"/head-name ||
+                       die "Could not get HEAD"
+
+               echo $HEAD > "$DOTEST"/head
+               echo $UPSTREAM > "$DOTEST"/upstream
+               echo $ONTO > "$DOTEST"/onto
+               test -z "$STRATEGY" || echo "$STRATEGY" > "$DOTEST"/strategy
+               test t = "$VERBOSE" && : > "$DOTEST"/verbose
+               if test t = "$PRESERVE_MERGES"
+               then
+                       # $REWRITTEN contains files for each commit that is
+                       # reachable by at least one merge base of $HEAD and
+                       # $UPSTREAM. They are not necessarily rewritten, but
+                       # their children might be.
+                       # 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 ||
+                                       die "Could not init rewritten commits"
+                       done
+                       MERGES_OPTION=
+               else
+                       MERGES_OPTION=--no-merges
+               fi
+
+               SHORTUPSTREAM=$(git rev-parse --short $UPSTREAM)
+               SHORTHEAD=$(git rev-parse --short $HEAD)
+               SHORTONTO=$(git rev-parse --short $ONTO)
+               cat > "$TODO" << EOF
+# Rebasing $SHORTUPSTREAM..$SHORTHEAD onto $SHORTONTO
+#
+# Commands:
+#  pick = use commit
+#  edit = use commit, but stop for amending
+#  squash = use commit, but meld into previous commit
+#
+# If you remove a line here THAT COMMIT WILL BE LOST.
+#
+EOF
+               git rev-list $MERGES_OPTION --pretty=oneline --abbrev-commit \
+                       --abbrev=7 --reverse --left-right --cherry-pick \
+                       $UPSTREAM...$HEAD | \
+                       sed -n "s/^>/pick /p" >> "$TODO"
+
+               test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+                       die_abort "Nothing to do"
+
+               cp "$TODO" "$TODO".backup
+               git_editor "$TODO" ||
+                       die "Could not execute editor"
+
+               test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+                       die_abort "Nothing to do"
+
+               output git checkout $ONTO && do_rest
+       esac
+       shift
+done
index 2dc2c4fe9b4d4a7ff10398938efbed28fa6f43f7..cbafa14ed524212441ba76e1bdb558585795e2f3 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano.
 #
 
-USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
+USAGE='[--interactive | -i] [-v] [--onto <newbase>] <upstream> [<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>
@@ -51,15 +51,15 @@ continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
        test -d "$dotest" || die "$dotest directory does not exist"
 
-       unmerged=$(git-ls-files -u)
+       unmerged=$(git ls-files -u)
        if test -n "$unmerged"
        then
                echo "You still have unmerged paths in your index"
-               echo "did you forget update-index?"
+               echo "did you forget to use git add?"
                die "$RESOLVEMSG"
        fi
 
-       if ! git-diff-index --quiet HEAD
+       if ! git diff-index --quiet HEAD
        then
                if ! git-commit -C "`cat $dotest/current`"
                then
@@ -71,10 +71,10 @@ continue_merge () {
        else
                printf "Already applied: %0${prec}d" $msgnum
        fi
-       echo ' '`git-rev-list --pretty=oneline -1 HEAD | \
+       echo ' '`git rev-list --pretty=oneline -1 HEAD | \
                                sed 's/^[a-f0-9]\+ //'`
 
-       prev_head=`git-rev-parse HEAD^0`
+       prev_head=`git rev-parse HEAD^0`
        # save the resulting commit so we can read-tree on it later
        echo "$prev_head" > "$dotest/prev_head"
 
@@ -86,8 +86,8 @@ continue_merge () {
 call_merge () {
        cmt="$(cat $dotest/cmt.$1)"
        echo "$cmt" > "$dotest/current"
-       hd=$(git-rev-parse --verify HEAD)
-       cmt_name=$(git-symbolic-ref HEAD)
+       hd=$(git rev-parse --verify HEAD)
+       cmt_name=$(git symbolic-ref HEAD)
        msgnum=$(cat $dotest/msgnum)
        end=$(cat $dotest/end)
        eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"'
@@ -101,7 +101,7 @@ call_merge () {
                return
                ;;
        1)
-               test -d "$GIT_DIR/rr-cache" && git-rerere
+               git rerere
                die "$RESOLVEMSG"
                ;;
        2)
@@ -120,13 +120,23 @@ finish_rb_merge () {
        echo "All done."
 }
 
+is_interactive () {
+       test -f "$dotest"/interactive ||
+       while case $#,"$1" in 0,|*,-i|*,--interactive) break ;; esac
+       do
+               shift
+       done && test -n "$1"
+}
+
+is_interactive "$@" && exec git-rebase--interactive "$@"
+
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
        --continue)
-               git-diff-files --quiet || {
+               git diff-files --quiet || {
                        echo "You must edit all merge conflicts and then"
-                       echo "mark them as resolved using git update-index"
+                       echo "mark them as resolved using git add"
                        exit 1
                }
                if test -d "$dotest"
@@ -150,10 +160,7 @@ do
        --skip)
                if test -d "$dotest"
                then
-                       if test -d "$GIT_DIR/rr-cache"
-                       then
-                               git-rerere clear
-                       fi
+                       git rerere clear
                        prev_head="`cat $dotest/prev_head`"
                        end="`cat $dotest/end`"
                        msgnum="`cat $dotest/msgnum`"
@@ -171,10 +178,7 @@ do
                exit
                ;;
        --abort)
-               if test -d "$GIT_DIR/rr-cache"
-               then
-                       git-rerere clear
-               fi
+               git rerere clear
                if test -d "$dotest"
                then
                        rm -r "$dotest"
@@ -249,8 +253,8 @@ else
 fi
 
 # The tree must be really really clean.
-git-update-index --refresh || exit
-diff=$(git-diff-index --cached --name-status -r HEAD)
+git update-index --refresh || exit
+diff=$(git diff-index --cached --name-status -r HEAD)
 case "$diff" in
 ?*)    echo "cannot rebase: your index is not up-to-date"
        echo "$diff"
@@ -265,7 +269,7 @@ upstream=`git rev-parse --verify "${upstream_name}^0"` ||
 
 # Make sure the branch to rebase onto is valid.
 onto_name=${newbase-"$upstream_name"}
-onto=$(git-rev-parse --verify "${onto_name}^0") || exit
+onto=$(git rev-parse --verify "${onto_name}^0") || exit
 
 # If a hook exists, give it a chance to interrupt
 if test -x "$GIT_DIR/hooks/pre-rebase"
@@ -291,14 +295,16 @@ case "$#" in
        fi
        ;;
 esac
-branch=$(git-rev-parse --verify "${branch_name}^0") || exit
+branch=$(git rev-parse --verify "${branch_name}^0") || exit
 
 # Now we are rebasing commits $upstream..$branch on top of $onto
 
-# Check if we are already based on $onto, but this should be
-# done only when upstream and onto are the same.
-mb=$(git-merge-base "$onto" "$branch")
-if test "$upstream" = "$onto" && test "$mb" = "$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.
+mb=$(git merge-base "$onto" "$branch")
+if test "$upstream" = "$onto" && test "$mb" = "$onto" &&
+       # linear history?
+       ! git rev-list --parents "$onto".."$branch" | grep " .* " > /dev/null
 then
        echo >&2 "Current branch $branch_name is up to date."
        exit 0
@@ -307,7 +313,8 @@ fi
 if test -n "$verbose"
 then
        echo "Changes from $mb to $onto:"
-       git-diff-tree --stat --summary "$mb" "$onto"
+       # We want color (if set), but no pager
+       GIT_PAGER='' git diff --stat --summary "$mb" "$onto"
 fi
 
 # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
@@ -324,7 +331,7 @@ fi
 
 if test -z "$do_merge"
 then
-       git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
+       git format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
        git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG"
        exit $?
 fi
@@ -335,11 +342,11 @@ fi
 mkdir -p "$dotest"
 echo "$onto" > "$dotest/onto"
 echo "$onto_name" > "$dotest/onto_name"
-prev_head=`git-rev-parse HEAD^0`
+prev_head=`git rev-parse HEAD^0`
 echo "$prev_head" > "$dotest/prev_head"
 
 msgnum=0
-for cmt in `git-rev-list --reverse --no-merges "$upstream"..ORIG_HEAD`
+for cmt in `git rev-list --reverse --no-merges "$upstream"..ORIG_HEAD`
 do
        msgnum=$(($msgnum + 1))
        echo "$cmt" > "$dotest/cmt.$msgnum"
index 576379912793c9740620892cd3ee1cf7d2f18cfe..01cf480221be1e5860bd701d5d17ced25766d38d 100755 (executable)
@@ -258,6 +258,7 @@ sub show_remote {
        if ($info->{'PUSH'}) {
                my @pushed = map {
                        s|^refs/heads/||;
+                       s|^\+refs/heads/|+|;
                        s|:refs/heads/|:|;
                        $_;
                } @{$info->{'PUSH'}};
@@ -318,9 +319,21 @@ sub add_usage {
        exit(1);
 }
 
+local $VERBOSE = 0;
+@ARGV = grep {
+       if ($_ eq '-v' or $_ eq '--verbose') {
+               $VERBOSE=1;
+               0
+       } else {
+               1
+       }
+} @ARGV;
+
 if (!@ARGV) {
        for (sort keys %$remote) {
-               print "$_\n";
+               print "$_";
+               print "\t$remote->{$_}->{URL}" if $VERBOSE;
+               print "\n";
        }
 }
 elsif ($ARGV[0] eq 'show') {
index ddfa8b44a17f1ea7ff039bca1d6132ba07e5cf12..156c5e8f4aa2f6c04e33af323ef43356fd336adb 100755 (executable)
@@ -3,12 +3,12 @@
 # Copyright (c) 2005 Linus Torvalds
 #
 
-USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--window=N] [--depth=N]'
+USAGE='[-a] [-d] [-f] [-l] [-n] [-q] [--max-pack-size=N] [--window=N] [--window-memory=N] [--depth=N]'
 SUBDIRECTORY_OK='Yes'
 . git-sh-setup
 
 no_update_info= all_into_one= remove_redundant=
-local= quiet= no_reuse_delta= extra=
+local= quiet= no_reuse= extra=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -16,9 +16,11 @@ do
        -a)     all_into_one=t ;;
        -d)     remove_redundant=t ;;
        -q)     quiet=-q ;;
-       -f)     no_reuse_delta=--no-reuse-delta ;;
+       -f)     no_reuse=--no-reuse-object ;;
        -l)     local=--local ;;
+       --max-pack-size=*) extra="$extra $1" ;;
        --window=*) extra="$extra $1" ;;
+       --window-memory=*) extra="$extra $1" ;;
        --depth=*) extra="$extra $1" ;;
        *)      usage ;;
        esac
@@ -35,7 +37,7 @@ true)
 esac
 
 PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
-PACKTMP="$GIT_DIR/.tmp-$$-pack"
+PACKTMP="$GIT_OBJECT_DIRECTORY/.tmp-$$-pack"
 rm -f "$PACKTMP"-*
 trap 'rm -f "$PACKTMP"-*' 0 1 2 3 15
 
@@ -61,12 +63,16 @@ case ",$all_into_one," in
        ;;
 esac
 
-args="$args $local $quiet $no_reuse_delta$extra"
-name=$(git-pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
+args="$args $local $quiet $no_reuse$extra"
+names=$(git pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
        exit 1
-if [ -z "$name" ]; then
-       echo Nothing new to pack.
-else
+if [ -z "$names" ]; then
+       if test -z "$quiet"; then
+               echo Nothing new to pack.
+       fi
+fi
+for name in $names ; do
+       fullbases="$fullbases pack-$name"
        chmod a-w "$PACKTMP-$name.pack"
        chmod a-w "$PACKTMP-$name.idx"
        if test "$quiet" != '-q'; then
@@ -92,7 +98,7 @@ else
                exit 1
        }
        rm -f "$PACKDIR/old-pack-$name.pack" "$PACKDIR/old-pack-$name.idx"
-fi
+done
 
 if test "$remove_redundant" = t
 then
@@ -103,14 +109,14 @@ then
                ( cd "$PACKDIR" &&
                  for e in $existing
                  do
-                       case "$e" in
-                       pack-$name) ;;
+                       case " $fullbases " in
+                       *" $e "*) ;;
                        *)      rm -f "$e.pack" "$e.idx" "$e.keep" ;;
                        esac
                  done
                )
        fi
-       git-prune-packed $quiet
+       git prune-packed $quiet
 fi
 
 case "$no_update_info" in
index ba577d4ce1b0aaf5777667410dc9cca6c999df1d..a992430679ab2c4b3c8c5eeb5a26a6fbba0976f4 100755 (executable)
@@ -18,8 +18,8 @@ head=${3-HEAD}
 [ "$base" ] || usage
 [ "$url" ] || usage
 
-baserev=`git-rev-parse --verify "$base"^0` &&
-headrev=`git-rev-parse --verify "$head"^0` || exit
+baserev=`git rev-parse --verify "$base"^0` &&
+headrev=`git rev-parse --verify "$head"^0` || exit
 
 merge_base=`git merge-base $baserev $headrev` ||
 die "fatal: No commits in common between $base and $head"
index a172d7ce25b40f192b0036d29a8a60e62649159c..1dc606fbd3fd29f5f4c9928a2518df46109ef449 100755 (executable)
@@ -24,7 +24,7 @@ do
                usage
                ;;
        *)
-               rev=$(git-rev-parse --verify "$1") || exit
+               rev=$(git rev-parse --verify "$1") || exit
                shift
                break
                ;;
@@ -33,7 +33,7 @@ do
 done
 
 : ${rev=HEAD}
-rev=$(git-rev-parse --verify $rev^0) || exit
+rev=$(git rev-parse --verify $rev^0) || exit
 
 # Skip -- in "git reset HEAD -- foo" and "git reset -- foo".
 case "$1" in --) shift ;; esac
@@ -46,7 +46,7 @@ then
        test "$reset_type" = "--mixed" ||
                die "Cannot do partial $reset_type reset."
 
-       git-diff-index --cached $rev -- "$@" |
+       git diff-index --cached $rev -- "$@" |
        sed -e 's/^:\([0-7][0-7]*\) [0-7][0-7]* \([0-9a-f][0-9a-f]*\) [0-9a-f][0-9a-f]* [A-Z]   \(.*\)$/\1 \2   \3/' |
        git update-index --add --remove --index-info || exit
        git update-index --refresh
@@ -66,22 +66,22 @@ fi
 if test "$reset_type" = "--soft"
 then
        if test -f "$GIT_DIR/MERGE_HEAD" ||
-          test "" != "$(git-ls-files --unmerged)"
+          test "" != "$(git ls-files --unmerged)"
        then
                die "Cannot do a soft reset in the middle of a merge."
        fi
 else
-       git-read-tree -v --reset $update "$rev" || exit
+       git read-tree -v --reset $update "$rev" || exit
 fi
 
 # Any resets update HEAD to the head being switched to.
-if orig=$(git-rev-parse --verify HEAD 2>/dev/null)
+if orig=$(git rev-parse --verify HEAD 2>/dev/null)
 then
        echo "$orig" >"$GIT_DIR/ORIG_HEAD"
 else
        rm -f "$GIT_DIR/ORIG_HEAD"
 fi
-git-update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
+git update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
 update_ref_status=$?
 
 case "$reset_type" in
@@ -96,7 +96,7 @@ case "$reset_type" in
        ;; # Nothing else to do
 --mixed )
        # Report what has not been updated.
-       git-update-index --refresh
+       git update-index --refresh
        ;;
 esac
 
index 7c0c90bd21bbb009de81aa315bed1c947a32c423..69559b289a28acf7fd095260797535f62db8172e 100755 (executable)
@@ -49,8 +49,8 @@ Options:
    --bcc          Specify a list of email addresses that should be Bcc:
                  on all the emails.
 
-   --compose      Use \$EDITOR to edit an introductory message for the
-                  patch series.
+   --compose      Use \$GIT_EDITOR, core.editor, \$EDITOR, or \$VISUAL to edit
+                 an introductory message for the patch series.
 
    --subject      Specify the initial "Subject:" line.
                   Only necessary if --compose is also set.  If --compose
@@ -64,15 +64,17 @@ Options:
                   email sent, rather than to the first email sent.
                   Defaults to on.
 
-   --no-signed-off-cc Suppress the automatic addition of email addresses
-                 that appear in Signed-off-by: or Cc: lines to the cc:
-                 list.  Note: Using this option is not recommended.
+   --signed-off-cc Automatically add email addresses that appear in
+                 Signed-off-by: or Cc: lines to the cc: list. Defaults to on.
 
    --smtp-server  If set, specifies the outgoing SMTP server to use.
                   Defaults to localhost.
 
    --suppress-from Suppress sending emails to yourself if your address
-                  appears in a From: line.
+                  appears in a From: line. Defaults to off.
+
+   --thread       Specify that the "In-Reply-To:" header should be set on all
+                  emails. Defaults to on.
 
    --quiet       Make git-send-email less verbose.  One line per email
                   should be all that is output.
@@ -135,11 +137,8 @@ my $compose_filename = ".msg.$$";
 
 # Variables we fill in automatically, or via prompting:
 my (@to,@cc,@initial_cc,@bcclist,@xh,
-       $initial_reply_to,$initial_subject,@files,$from,$compose,$time);
+       $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time);
 
-# Behavior modification variables
-my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
-       $dry_run) = (1, 0, 0, 0, 0);
 my $smtp_server;
 my $envelope_sender;
 
@@ -154,9 +153,22 @@ if ($@) {
        $term = new FakeTerm "$@: going non-interactive";
 }
 
-my $def_chain = $repo->config_bool('sendemail.chainreplyto');
-if (defined $def_chain and not $def_chain) {
-    $chain_reply_to = 0;
+# Behavior modification variables
+my ($quiet, $dry_run) = (0, 0);
+
+# Variables with corresponding config settings
+my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc);
+
+my %config_settings = (
+    "thread" => [\$thread, 1],
+    "chainreplyto" => [\$chain_reply_to, 1],
+    "suppressfrom" => [\$suppress_from, 0],
+    "signedoffcc" => [\$signed_off_cc, 1],
+);
+
+foreach my $setting (keys %config_settings) {
+    my $config = $repo->config_bool("sendemail.$setting");
+    ${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1];
 }
 
 @bcclist = $repo->config('sendemail.bcc');
@@ -167,7 +179,7 @@ if (!@bcclist or !$bcclist[0]) {
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
 
-my $rc = GetOptions("from=s" => \$from,
+my $rc = GetOptions("sender|from=s" => \$sender,
                     "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
                    "to=s" => \@to,
@@ -177,10 +189,11 @@ my $rc = GetOptions("from=s" => \$from,
                    "smtp-server=s" => \$smtp_server,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
-                   "suppress-from" => \$suppress_from,
-                   "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+                   "suppress-from!" => \$suppress_from,
+                   "signed-off-cc|signed-off-by-cc!" => \$signed_off_cc,
                    "dry-run" => \$dry_run,
                    "envelope-sender=s" => \$envelope_sender,
+                   "thread!" => \$thread,
         );
 
 unless ($rc) {
@@ -203,8 +216,8 @@ foreach my $entry (@bcclist) {
 
 # Now, let's fill any that aren't set in with defaults:
 
-my ($author) = $repo->ident_person('author');
-my ($committer) = $repo->ident_person('committer');
+my ($repoauthor) = $repo->ident_person('author');
+my ($repocommitter) = $repo->ident_person('committer');
 
 my %aliases;
 my @alias_files = $repo->config('sendemail.aliasesfile');
@@ -224,7 +237,7 @@ my %parse_alias = (
                        $aliases{$1} = [ split(/\s+/, $2) ];
                }}},
        pine => sub { my $fh = shift; while (<$fh>) {
-               if (/^(\S+)\s+(.*)$/) {
+               if (/^(\S+)\t.*\t(.*)$/) {
                        $aliases{$1} = [ split(/\s*,\s*/, $2) ];
                }}},
        gnus => sub { my $fh = shift; while (<$fh>) {
@@ -241,15 +254,17 @@ if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
        }
 }
 
+($sender) = expand_aliases($sender) if defined $sender;
+
 my $prompting = 0;
-if (!defined $from) {
-       $from = $author || $committer;
+if (!defined $sender) {
+       $sender = $repoauthor || $repocommitter;
        do {
-               $_ = $term->readline("Who should the emails appear to be from? [$from] ");
+               $_ = $term->readline("Who should the emails appear to be from? [$sender] ");
        } while (!defined $_);
 
-       $from = $_ if ($_);
-       print "Emails will be sent from: ", $from, "\n";
+       $sender = $_ if ($_);
+       print "Emails will be sent from: ", $sender, "\n";
        $prompting++;
 }
 
@@ -274,7 +289,7 @@ sub expand_aliases {
 }
 
 @to = expand_aliases(@to);
-@to = (map { sanitize_address_rfc822($_) } @to);
+@to = (map { sanitize_address($_) } @to);
 @initial_cc = expand_aliases(@initial_cc);
 @bcclist = expand_aliases(@bcclist);
 
@@ -287,7 +302,7 @@ if (!defined $initial_subject && $compose) {
        $prompting++;
 }
 
-if (!defined $initial_reply_to && $prompting) {
+if ($thread && !defined $initial_reply_to && $prompting) {
        do {
                $_= $term->readline("Message-ID to be used as In-Reply-To for the first email? ",
                        $initial_reply_to);
@@ -315,7 +330,7 @@ if ($compose) {
        # effort to have it be unique
        open(C,">",$compose_filename)
                or die "Failed to open for writing $compose_filename: $!";
-       print C "From $from # This line is ignored.\n";
+       print C "From $sender # This line is ignored.\n";
        printf C "Subject: %s\n\n", $initial_subject;
        printf C <<EOT;
 GIT: Please enter your email below.
@@ -326,8 +341,7 @@ GIT: for the patch you are writing.
 EOT
        close(C);
 
-       my $editor = $ENV{EDITOR};
-       $editor = 'vi' unless defined $editor;
+       my $editor = $ENV{GIT_EDITOR} || $repo->config("core.editor") || $ENV{VISUAL} || $ENV{EDITOR} || "vi";
        system($editor, $compose_filename);
 
        open(C2,">",$compose_filename . ".final")
@@ -394,6 +408,7 @@ sub extract_valid_address {
        # check for a local address:
        return $address if ($address =~ /^($local_part_regexp)$/);
 
+       $address =~ s/^\s*<(.*)>\s*$/$1/;
        if ($have_email_valid) {
                return scalar Email::Valid->address($address);
        } else {
@@ -412,13 +427,21 @@ sub extract_valid_address {
 # 1 second since the last time we were called.
 
 # We'll setup a template for the message id, using the "from" address:
-my $message_id_from = extract_valid_address($from);
-my $message_id_template = "<%s-git-send-email-$message_id_from>";
 
 sub make_message_id
 {
        my $date = time;
        my $pseudo_rand = int (rand(4200));
+       my $du_part;
+       for ($sender, $repocommitter, $repoauthor) {
+               $du_part = extract_valid_address(sanitize_address($_));
+               last if (defined $du_part and $du_part ne '');
+       }
+       if (not defined $du_part or $du_part eq '') {
+               use Sys::Hostname qw();
+               $du_part = 'user@' . Sys::Hostname::hostname();
+       }
+       my $message_id_template = "<%s-git-send-email-$du_part>";
        $message_id = sprintf $message_id_template, "$date$pseudo_rand";
        #print "new message id = $message_id\n"; # Was useful for debugging
 }
@@ -436,22 +459,41 @@ sub unquote_rfc2047 {
        return "$_";
 }
 
-# If an address contains a . in the name portion, the name must be quoted.
-sub sanitize_address_rfc822
+# use the simplest quoting being able to handle the recipient
+sub sanitize_address
 {
        my ($recipient) = @_;
-       my ($recipient_name) = ($recipient =~ /^(.*?)\s+</);
-       if ($recipient_name && $recipient_name =~ /\./ && $recipient_name !~ /^".*"$/) {
-               my ($name, $addr) = ($recipient =~ /^(.*?)(\s+<.*)/);
-               $recipient = "\"$name\"$addr";
+       my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/);
+
+       if (not $recipient_name) {
+               return "$recipient";
        }
-       return $recipient;
+
+       # if recipient_name is already quoted, do nothing
+       if ($recipient_name =~ /^(".*"|=\?utf-8\?q\?.*\?=)$/) {
+               return $recipient;
+       }
+
+       # rfc2047 is needed if a non-ascii char is included
+       if ($recipient_name =~ /[^[:ascii:]]/) {
+               $recipient_name =~ s/([^-a-zA-Z0-9!*+\/])/sprintf("=%02X", ord($1))/eg;
+               $recipient_name =~ s/(.*)/=\?utf-8\?q\?$1\?=/;
+       }
+
+       # double quotes are needed if specials or CTLs are included
+       elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
+               $recipient_name =~ s/(["\\\r])/\\$1/;
+               $recipient_name = "\"$recipient_name\"";
+       }
+
+       return "$recipient_name $recipient_addr";
+
 }
 
 sub send_message
 {
        my @recipients = unique_email_list(@to);
-       @cc = (map { sanitize_address_rfc822($_) } @cc);
+       @cc = (map { sanitize_address($_) } @cc);
        my $to = join (",\n\t", @recipients);
        @recipients = unique_email_list(@recipients,@cc,@bcclist);
        @recipients = (map { extract_valid_address($_) } @recipients);
@@ -466,15 +508,17 @@ sub send_message
        if ($cc ne '') {
                $ccline = "\nCc: $cc";
        }
-       $from = sanitize_address_rfc822($from);
-       my $header = "From: $from
+       my $sanitized_sender = sanitize_address($sender);
+       make_message_id();
+
+       my $header = "From: $sanitized_sender
 To: $to${ccline}
 Subject: $subject
 Date: $date
 Message-Id: $message_id
 X-Mailer: git-send-email $gitversion
 ";
-       if ($reply_to) {
+       if ($thread && $reply_to) {
 
                $header .= "In-Reply-To: $reply_to\n";
                $header .= "References: $references\n";
@@ -484,7 +528,7 @@ X-Mailer: git-send-email $gitversion
        }
 
        my @sendmail_parameters = ('-i', @recipients);
-       my $raw_from = $from;
+       my $raw_from = $sanitized_sender;
        $raw_from = $envelope_sender if (defined $envelope_sender);
        $raw_from = extract_valid_address($raw_from);
        unshift (@sendmail_parameters,
@@ -521,7 +565,7 @@ X-Mailer: git-send-email $gitversion
                } else {
                        print "Sendmail: $smtp_server ".join(' ',@sendmail_parameters)."\n";
                }
-               print "From: $from\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
+               print "From: $sanitized_sender\nSubject: $subject\nCc: $cc\nTo: $to\n\n";
                if ($smtp) {
                        print "Result: ", $smtp->code, ' ',
                                ($smtp->message =~ /\n([^\n]+\n)$/s), "\n";
@@ -533,13 +577,12 @@ X-Mailer: git-send-email $gitversion
 
 $reply_to = $initial_reply_to;
 $references = $initial_reply_to || '';
-make_message_id();
 $subject = $initial_subject;
 
 foreach my $t (@files) {
        open(F,"<",$t) or die "can't open file $t";
 
-       my $author_not_sender = undef;
+       my $author = undef;
        @cc = @initial_cc;
        @xh = ();
        my $input_format = undef;
@@ -561,12 +604,11 @@ foreach my $t (@files) {
                                        $subject = $1;
 
                                } elsif (/^(Cc|From):\s+(.*)$/) {
-                                       if (unquote_rfc2047($2) eq $from) {
-                                               $from = $2;
+                                       if (unquote_rfc2047($2) eq $sender) {
                                                next if ($suppress_from);
                                        }
                                        elsif ($1 eq 'From') {
-                                               $author_not_sender = $2;
+                                               $author = unquote_rfc2047($2);
                                        }
                                        printf("(mbox) Adding cc: %s from line '%s'\n",
                                                $2, $_) unless $quiet;
@@ -600,7 +642,7 @@ foreach my $t (@files) {
                        }
                } else {
                        $message .=  $_;
-                       if (/^(Signed-off-by|Cc): (.*)$/i && !$no_signed_off_cc) {
+                       if (/^(Signed-off-by|Cc): (.*)$/i && $signed_off_cc) {
                                my $c = $2;
                                chomp $c;
                                push @cc, $c;
@@ -610,9 +652,8 @@ foreach my $t (@files) {
                }
        }
        close F;
-       if (defined $author_not_sender) {
-               $author_not_sender = unquote_rfc2047($author_not_sender);
-               $message = "From: $author_not_sender\n\n$message";
+       if (defined $author) {
+               $message = "From: $author\n\n$message";
        }
 
 
@@ -627,7 +668,6 @@ foreach my $t (@files) {
                        $references = "$message_id";
                }
        }
-       make_message_id();
 }
 
 if ($compose) {
index f24c7f2d23c13e9874308a019f3c0f93225de3c0..185c5c6c95df389e0d7f39f47feeabbe67124e80 100755 (executable)
@@ -28,16 +28,28 @@ set_reflog_action() {
        fi
 }
 
-is_bare_repository () {
-       git-config --bool --get core.bare ||
-       case "$GIT_DIR" in
-       .git | */.git) echo false ;;
-       *) echo true ;;
+git_editor() {
+       : "${GIT_EDITOR:=$(git config core.editor)}"
+       : "${GIT_EDITOR:=${VISUAL:-${EDITOR}}}"
+       case "$GIT_EDITOR,$TERM" in
+       ,dumb)
+               echo >&2 "No editor specified in GIT_EDITOR, core.editor, VISUAL,"
+               echo >&2 "or EDITOR. Tried to fall back to vi but terminal is dumb."
+               echo >&2 "Please set one of these variables to an appropriate"
+               echo >&2 "editor or run $0 with options that will not cause an"
+               echo >&2 "editor to be invoked (e.g., -m or -F for git-commit)."
+               exit 1
+               ;;
        esac
+       eval "${GIT_EDITOR:=vi}" '"$@"'
+}
+
+is_bare_repository () {
+       git rev-parse --is-bare-repository
 }
 
 cd_to_toplevel () {
-       cdup=$(git-rev-parse --show-cdup)
+       cdup=$(git rev-parse --show-cdup)
        if test ! -z "$cdup"
        then
                cd "$cdup" || {
@@ -48,11 +60,37 @@ cd_to_toplevel () {
 }
 
 require_work_tree () {
-       test $(is_bare_repository) = false &&
-       test $(git-rev-parse --is-inside-git-dir) = false ||
+       test $(git rev-parse --is-inside-work-tree) = true ||
        die "fatal: $0 cannot be used without a working tree."
 }
 
+get_author_ident_from_commit () {
+       pick_author_script='
+       /^author /{
+               s/'\''/'\''\\'\'\''/g
+               h
+               s/^author \([^<]*\) <[^>]*> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+               g
+               s/^author [^<]* <\([^>]*\)> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+               g
+               s/^author [^<]* <[^>]*> \(.*\)$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+               q
+       }
+       '
+       encoding=$(git config i18n.commitencoding || echo UTF-8)
+       git show -s --pretty=raw --encoding="$encoding" "$1" |
+       LANG=C LC_ALL=C sed -ne "$pick_author_script"
+}
+
 if [ -z "$LONG_USAGE" ]
 then
        LONG_USAGE="Usage: $0 $USAGE"
@@ -72,12 +110,22 @@ esac
 if [ -z "$SUBDIRECTORY_OK" ]
 then
        : ${GIT_DIR=.git}
-       GIT_DIR=$(GIT_DIR="$GIT_DIR" git-rev-parse --git-dir) || {
+       GIT_DIR=$(GIT_DIR="$GIT_DIR" git rev-parse --git-dir) || {
                exit=$?
                echo >&2 "You need to run this command from the toplevel of the working tree."
                exit $exit
        }
 else
-       GIT_DIR=$(git-rev-parse --git-dir) || exit
+       GIT_DIR=$(git rev-parse --git-dir) || {
+           exit=$?
+           echo >&2 "Failed to find a valid git directory."
+           exit $exit
+       }
 fi
+
+test -n "$GIT_DIR" && GIT_DIR=$(cd "$GIT_DIR" && pwd) || {
+    echo >&2 "Unable to determine absolute path of git directory"
+    exit 1
+}
+
 : ${GIT_OBJECT_DIRECTORY="$GIT_DIR/objects"}
diff --git a/git-stash.sh b/git-stash.sh
new file mode 100755 (executable)
index 0000000..30425ce
--- /dev/null
@@ -0,0 +1,215 @@
+#!/bin/sh
+# Copyright (c) 2007, Nanako Shiraishi
+
+USAGE='[ | list | show | apply | clear]'
+
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+TMP="$GIT_DIR/.git-stash.$$"
+trap 'rm -f "$TMP-*"' 0
+
+ref_stash=refs/stash
+
+no_changes () {
+       git diff-index --quiet --cached HEAD &&
+       git diff-files --quiet
+}
+
+clear_stash () {
+       if current=$(git rev-parse --verify $ref_stash 2>/dev/null)
+       then
+               git update-ref -d refs/stash $current
+       fi
+}
+
+save_stash () {
+       stash_msg="$1"
+
+       if no_changes
+       then
+               echo >&2 'No local changes to save'
+               exit 0
+       fi
+       test -f "$GIT_DIR/logs/$ref_stash" ||
+               clear_stash || die "Cannot initialize stash"
+
+       # Make sure the reflog for stash is kept.
+       : >>"$GIT_DIR/logs/$ref_stash"
+
+       # state of the base commit
+       if b_commit=$(git rev-parse --verify HEAD)
+       then
+               head=$(git log --abbrev-commit --pretty=oneline -n 1 HEAD)
+       else
+               die "You do not have the initial commit yet"
+       fi
+
+       if branch=$(git symbolic-ref -q HEAD)
+       then
+               branch=${branch#refs/heads/}
+       else
+               branch='(no branch)'
+       fi
+       msg=$(printf '%s: %s' "$branch" "$head")
+
+       # state of the index
+       i_tree=$(git write-tree) &&
+       i_commit=$(printf 'index on %s' "$msg" |
+               git commit-tree $i_tree -p $b_commit) ||
+               die "Cannot save the current index state"
+
+       # state of the working tree
+       w_tree=$( (
+               rm -f "$TMP-index" &&
+               cp -p ${GIT_INDEX_FILE-"$GIT_DIR/index"} "$TMP-index" &&
+               GIT_INDEX_FILE="$TMP-index" &&
+               export GIT_INDEX_FILE &&
+               git read-tree -m $i_tree &&
+               git add -u &&
+               git write-tree &&
+               rm -f "$TMP-index"
+       ) ) ||
+               die "Cannot save the current worktree state"
+
+       # create the stash
+       if test -z "$stash_msg"
+       then
+               stash_msg=$(printf 'WIP on %s' "$msg")
+       else
+               stash_msg=$(printf 'On %s: %s' "$branch" "$stash_msg")
+       fi
+       w_commit=$(printf '%s\n' "$stash_msg" |
+               git commit-tree $w_tree -p $b_commit -p $i_commit) ||
+               die "Cannot record working tree state"
+
+       git update-ref -m "$stash_msg" $ref_stash $w_commit ||
+               die "Cannot save the current status"
+       printf >&2 'Saved "%s"\n' "$stash_msg"
+}
+
+have_stash () {
+       git rev-parse --verify $ref_stash >/dev/null 2>&1
+}
+
+list_stash () {
+       have_stash || return 0
+       git log --pretty=oneline -g "$@" $ref_stash |
+       sed -n -e 's/^[.0-9a-f]* refs\///p'
+}
+
+show_stash () {
+       flags=$(git rev-parse --no-revs --flags "$@")
+       if test -z "$flags"
+       then
+               flags=--stat
+       fi
+       s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@")
+
+       w_commit=$(git rev-parse --verify "$s") &&
+       b_commit=$(git rev-parse --verify "$s^") &&
+       git diff $flags $b_commit $w_commit
+}
+
+apply_stash () {
+       git diff-files --quiet ||
+               die 'Cannot restore on top of a dirty state'
+
+       unstash_index=
+       case "$1" in
+       --index)
+               unstash_index=t
+               shift
+       esac
+
+       # current index state
+       c_tree=$(git write-tree) ||
+               die 'Cannot apply a stash in the middle of a merge'
+
+       # stash records the work tree, and is a merge between the
+       # base commit (first parent) and the index tree (second parent).
+       s=$(git rev-parse --revs-only --no-flags --default $ref_stash "$@") &&
+       w_tree=$(git rev-parse --verify "$s:") &&
+       b_tree=$(git rev-parse --verify "$s^1:") &&
+       i_tree=$(git rev-parse --verify "$s^2:") ||
+               die "$*: no valid stashed state found"
+
+       unstashed_index_tree=
+       if test -n "$unstash_index" && test "$b_tree" != "$i_tree"
+       then
+               git diff --binary $s^2^..$s^2 | git apply --cached
+               test $? -ne 0 &&
+                       die 'Conflicts in index. Try without --index.'
+               unstashed_index_tree=$(git-write-tree) ||
+                       die 'Could not save index tree'
+               git reset
+       fi
+
+       eval "
+               GITHEAD_$w_tree='Stashed changes' &&
+               GITHEAD_$c_tree='Updated upstream' &&
+               GITHEAD_$b_tree='Version stash was based on' &&
+               export GITHEAD_$w_tree GITHEAD_$c_tree GITHEAD_$b_tree
+       "
+
+       if git-merge-recursive $b_tree -- $c_tree $w_tree
+       then
+               # No conflict
+               if test -n "$unstashed_index_tree"
+               then
+                       git read-tree "$unstashed_index_tree"
+               else
+                       a="$TMP-added" &&
+                       git diff --cached --name-only --diff-filter=A $c_tree >"$a" &&
+                       git read-tree --reset $c_tree &&
+                       git update-index --add --stdin <"$a" ||
+                               die "Cannot unstage modified files"
+                       rm -f "$a"
+               fi
+               git status || :
+       else
+               # Merge conflict; keep the exit status from merge-recursive
+               status=$?
+               if test -n "$unstash_index"
+               then
+                       echo >&2 'Index was not unstashed.'
+               fi
+               exit $status
+       fi
+}
+
+# Main command set
+case "$1" in
+list)
+       shift
+       if test $# = 0
+       then
+               set x -n 10
+               shift
+       fi
+       list_stash "$@"
+       ;;
+show)
+       shift
+       show_stash "$@"
+       ;;
+apply)
+       shift
+       apply_stash "$@"
+       ;;
+clear)
+       clear_stash
+       ;;
+help | usage)
+       usage
+       ;;
+*)
+       if test $# -gt 0 && test "$1" = save
+       then
+               shift
+       fi
+       save_stash "$*" && git-reset --hard
+       ;;
+esac
diff --git a/git-submodule.sh b/git-submodule.sh
new file mode 100755 (executable)
index 0000000..2cfeadd
--- /dev/null
@@ -0,0 +1,322 @@
+#!/bin/sh
+#
+# git-submodules.sh: add, init, update or list git submodules
+#
+# Copyright (c) 2007 Lars Hjemli
+
+USAGE='[--quiet] [--cached] [add <repo> [-b branch]|status|init|update] [--] [<path>...]'
+. git-sh-setup
+require_work_tree
+
+add=
+branch=
+init=
+update=
+status=
+quiet=
+cached=
+
+#
+# print stuff on stdout unless -q was specified
+#
+say()
+{
+       if test -z "$quiet"
+       then
+               echo "$@"
+       fi
+}
+
+# NEEDSWORK: identical function exists in get_repo_base in clone.sh
+get_repo_base() {
+       (
+               cd "`/bin/pwd`" &&
+               cd "$1" || cd "$1.git" &&
+               {
+                       cd .git
+                       pwd
+               }
+       ) 2>/dev/null
+}
+
+#
+# Map submodule path to submodule name
+#
+# $1 = path
+#
+module_name()
+{
+       # Do we have "submodule.<something>.path = $1" defined in .gitmodules file?
+       re=$(printf '%s' "$1" | sed -e 's/\([^a-zA-Z0-9_]\)/\\\1/g')
+       name=$( GIT_CONFIG=.gitmodules \
+               git config --get-regexp '^submodule\..*\.path$' |
+               sed -n -e 's|^submodule\.\(.*\)\.path '"$re"'$|\1|p' )
+       test -z "$name" &&
+       die "No submodule mapping found in .gitmodules for path '$path'"
+       echo "$name"
+}
+
+#
+# Clone a submodule
+#
+# Prior to calling, modules_update checks that a possibly existing
+# path is not a git repository.
+# Likewise, module_add checks that path does not exist at all,
+# since it is the location of a new submodule.
+#
+module_clone()
+{
+       path=$1
+       url=$2
+
+       # If there already is a directory at the submodule path,
+       # expect it to be empty (since that is the default checkout
+       # action) and try to remove it.
+       # Note: if $path is a symlink to a directory the test will
+       # succeed but the rmdir will fail. We might want to fix this.
+       if test -d "$path"
+       then
+               rmdir "$path" 2>/dev/null ||
+               die "Directory '$path' exist, but is neither empty nor a git repository"
+       fi
+
+       test -e "$path" &&
+       die "A file already exist at path '$path'"
+
+       git-clone -n "$url" "$path" ||
+       die "Clone of '$url' into submodule path '$path' failed"
+}
+
+#
+# Add a new submodule to the working tree, .gitmodules and the index
+#
+# $@ = repo [path]
+#
+# optional branch is stored in global branch variable
+#
+module_add()
+{
+       repo=$1
+       path=$2
+
+       if test -z "$repo"; then
+               usage
+       fi
+
+       # Turn the source into an absolute path if
+       # it is local
+       if base=$(get_repo_base "$repo"); then
+               repo="$base"
+       fi
+
+       # Guess path from repo if not specified or strip trailing slashes
+       if test -z "$path"; then
+               path=$(echo "$repo" | sed -e 's|/*$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+       else
+               path=$(echo "$path" | sed -e 's|/*$||')
+       fi
+
+       test -e "$path" &&
+       die "'$path' already exists"
+
+       git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
+       die "'$path' already exists in the index"
+
+       module_clone "$path" "$repo" || exit
+       (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
+       die "Unable to checkout submodule '$path'"
+       git add "$path" ||
+       die "Failed to add submodule '$path'"
+
+       GIT_CONFIG=.gitmodules git config submodule."$path".path "$path" &&
+       GIT_CONFIG=.gitmodules git config submodule."$path".url "$repo" &&
+       git add .gitmodules ||
+       die "Failed to register submodule '$path'"
+}
+
+#
+# Register submodules in .git/config
+#
+# $@ = requested paths (default to all)
+#
+modules_init()
+{
+       git ls-files --stage -- "$@" | grep -e '^160000 ' |
+       while read mode sha1 stage path
+       do
+               # Skip already registered paths
+               name=$(module_name "$path") || exit
+               url=$(git config submodule."$name".url)
+               test -z "$url" || continue
+
+               url=$(GIT_CONFIG=.gitmodules git config submodule."$name".url)
+               test -z "$url" &&
+               die "No url found for submodule path '$path' in .gitmodules"
+
+               git config submodule."$name".url "$url" ||
+               die "Failed to register url for submodule path '$path'"
+
+               say "Submodule '$name' ($url) registered for path '$path'"
+       done
+}
+
+#
+# Update each submodule path to correct revision, using clone and checkout as needed
+#
+# $@ = requested paths (default to all)
+#
+modules_update()
+{
+       git ls-files --stage -- "$@" | grep -e '^160000 ' |
+       while read mode sha1 stage path
+       do
+               name=$(module_name "$path") || exit
+               url=$(git config submodule."$name".url)
+               if test -z "$url"
+               then
+                       # Only mention uninitialized submodules when its
+                       # path have been specified
+                       test "$#" != "0" &&
+                       say "Submodule path '$path' not initialized"
+                       continue
+               fi
+
+               if ! test -d "$path"/.git
+               then
+                       module_clone "$path" "$url" || exit
+                       subsha1=
+               else
+                       subsha1=$(unset GIT_DIR && cd "$path" &&
+                               git rev-parse --verify HEAD) ||
+                       die "Unable to find current revision in submodule path '$path'"
+               fi
+
+               if test "$subsha1" != "$sha1"
+               then
+                       (unset GIT_DIR && cd "$path" && git-fetch &&
+                               git-checkout -q "$sha1") ||
+                       die "Unable to checkout '$sha1' in submodule path '$path'"
+
+                       say "Submodule path '$path': checked out '$sha1'"
+               fi
+       done
+}
+
+set_name_rev () {
+       revname=$( (
+               unset GIT_DIR &&
+               cd "$1" && {
+                       git describe "$2" 2>/dev/null ||
+                       git describe --tags "$2" 2>/dev/null ||
+                       git describe --contains --tags "$2"
+               }
+       ) )
+       test -z "$revname" || revname=" ($revname)"
+}
+
+#
+# List all submodules, prefixed with:
+#  - submodule not initialized
+#  + different revision checked out
+#
+# If --cached was specified the revision in the index will be printed
+# instead of the currently checked out revision.
+#
+# $@ = requested paths (default to all)
+#
+modules_list()
+{
+       git ls-files --stage -- "$@" | grep -e '^160000 ' |
+       while read mode sha1 stage path
+       do
+               name=$(module_name "$path") || exit
+               url=$(git config submodule."$name".url)
+               if test -z "url" || ! test -d "$path"/.git
+               then
+                       say "-$sha1 $path"
+                       continue;
+               fi
+               set_name_rev "$path" "$sha1"
+               if git diff-files --quiet -- "$path"
+               then
+                       say " $sha1 $path$revname"
+               else
+                       if test -z "$cached"
+                       then
+                               sha1=$(unset GIT_DIR && cd "$path" && git rev-parse --verify HEAD)
+                               set_name_rev "$path" "$sha1"
+                       fi
+                       say "+$sha1 $path$revname"
+               fi
+       done
+}
+
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       add)
+               add=1
+               ;;
+       init)
+               init=1
+               ;;
+       update)
+               update=1
+               ;;
+       status)
+               status=1
+               ;;
+       -q|--quiet)
+               quiet=1
+               ;;
+       -b|--branch)
+               case "$2" in
+               '')
+                       usage
+                       ;;
+               esac
+               branch="$2"; shift
+               ;;
+       --cached)
+               cached=1
+               ;;
+       --)
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+               ;;
+       esac
+       shift
+done
+
+case "$add,$branch" in
+1,*)
+       ;;
+,)
+       ;;
+,*)
+       usage
+       ;;
+esac
+
+case "$add,$init,$update,$status,$cached" in
+1,,,,)
+       module_add "$@"
+       ;;
+,1,,,)
+       modules_init "$@"
+       ;;
+,,1,,)
+       modules_update "$@"
+       ;;
+,,,1,*)
+       modules_list "$@"
+       ;;
+*)
+       usage
+       ;;
+esac
index e35006142af766080b3bafe0cdcd39bf2ccf3f04..d162114e26d64ff6a8a63dd8ff5d795c476868d4 100755 (executable)
@@ -38,14 +38,16 @@ use IPC::Open3;
 use Git;
 
 BEGIN {
-       my $s;
+       # 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/) {
-               $s .= "*SVN::Git::Editor::$_ = *SVN::Git::Fetcher::$_ = ".
-                     "*Git::SVN::Migration::$_ = ".
-                     "*Git::SVN::Log::$_ = *Git::SVN::$_ = *$_ = *Git::$_; ";
+               for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
+                       Git::SVN::Migration Git::SVN::Log Git::SVN),
+                       __PACKAGE__) {
+                       *{"${package}::$_"} = \&{"Git::$_"};
+               }
        }
-       eval $s;
 }
 
 my ($SVN);
@@ -372,16 +374,9 @@ sub cmd_dcommit {
                die "Unable to determine upstream SVN information from ",
                    "$head history\n";
        }
-       my $c = $refs[-1];
        my $last_rev;
-       foreach my $d (@refs) {
-               if (!verify_ref("$d~1")) {
-                       fatal "Commit $d\n",
-                             "has no parent commit, and therefore ",
-                             "nothing to diff against.\n",
-                             "You should be working from a repository ",
-                             "originally created by git-svn\n";
-               }
+       my ($linear_refs, $parents) = linearize_history($gs, \@refs);
+       foreach my $d (@$linear_refs) {
                unless (defined $last_rev) {
                        (undef, $last_rev, undef) = cmt_metadata("$d~1");
                        unless (defined $last_rev) {
@@ -403,6 +398,9 @@ sub cmd_dcommit {
                                        svn_path => '');
                        if (!SVN::Git::Editor->new(\%ed_opts)->apply_diff) {
                                print "No changes\n$d~1 == $d\n";
+                       } elsif ($parents->{$d} && @{$parents->{$d}}) {
+                               $gs->{inject_parents_dcommit}->{$last_rev} =
+                                                              $parents->{$d};
                        }
                }
        }
@@ -594,8 +592,7 @@ sub post_fetch_checkout {
        my $index = $ENV{GIT_INDEX_FILE} || "$ENV{GIT_DIR}/index";
        return if -f $index;
 
-       chomp(my $bare = `git config --bool --get core.bare`);
-       return if $bare eq 'true';
+       return if command_oneline(qw/rev-parse --is-inside-work-tree/) eq 'false';
        return if command_oneline(qw/rev-parse --is-inside-git-dir/) eq 'true';
        command_noisy(qw/read-tree -m -u -v HEAD HEAD/);
        print STDERR "Checked out HEAD:\n  ",
@@ -743,7 +740,7 @@ sub load_authors {
        my $log = $cmd eq 'log';
        while (<$authors>) {
                chomp;
-               next unless /^(\S+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
+               next unless /^(.+?|\(no author\))\s*=\s*(.+?)\s*<(.+)>\s*$/;
                my ($user, $name, $email) = ($1, $2, $3);
                if ($log) {
                        $Git::SVN::Log::rusers{"$name <$email>"} = $user;
@@ -785,12 +782,12 @@ sub read_repo_config {
 
 sub extract_metadata {
        my $id = shift or return (undef, undef, undef);
-       my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
+       my ($url, $rev, $uuid) = ($id =~ /^\s*git-svn-id:\s+(.*)\@(\d+)
                                                        \s([a-f\d\-]+)$/x);
        if (!defined $rev || !$uuid || !$url) {
                # some of the original repositories I made had
                # identifiers like this:
-               ($rev, $uuid) = ($id =~/^git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
+               ($rev, $uuid) = ($id =~/^\s*git-svn-id:\s(\d+)\@([a-f\d\-]+)/);
        }
        return ($url, $rev, $uuid);
 }
@@ -802,25 +799,87 @@ sub cmt_metadata {
 
 sub working_head_info {
        my ($head, $refs) = @_;
-       my ($fh, $ctx) = command_output_pipe('rev-list', $head);
-       while (my $hash = <$fh>) {
-               chomp($hash);
-               my ($url, $rev, $uuid) = cmt_metadata($hash);
+       my ($fh, $ctx) = command_output_pipe('log', $head);
+       my $hash;
+       my %max;
+       while (<$fh>) {
+               if ( m{^commit ($::sha1)$} ) {
+                       unshift @$refs, $hash if $hash and $refs;
+                       $hash = $1;
+                       next;
+               }
+               next unless s{^\s*(git-svn-id:)}{$1};
+               my ($url, $rev, $uuid) = extract_metadata($_);
                if (defined $url && defined $rev) {
+                       next if $max{$url} and $max{$url} < $rev;
                        if (my $gs = Git::SVN->find_by_url($url)) {
                                my $c = $gs->rev_db_get($rev);
                                if ($c && $c eq $hash) {
                                        close $fh; # break the pipe
                                        return ($url, $rev, $uuid, $gs);
+                               } else {
+                                       $max{$url} ||= $gs->rev_db_max;
                                }
                        }
                }
-               unshift @$refs, $hash if $refs;
        }
        command_close_pipe($fh, $ctx);
        (undef, undef, undef, undef);
 }
 
+sub read_commit_parents {
+       my ($parents, $c) = @_;
+       my ($fh, $ctx) = command_output_pipe(qw/cat-file commit/, $c);
+       while (<$fh>) {
+               chomp;
+               last if '';
+               /^parent ($sha1)/ or next;
+               push @{$parents->{$c}}, $1;
+       }
+       close $fh; # break the pipe
+}
+
+sub linearize_history {
+       my ($gs, $refs) = @_;
+       my %parents;
+       foreach my $c (@$refs) {
+               read_commit_parents(\%parents, $c);
+       }
+
+       my @linear_refs;
+       my %skip = ();
+       my $last_svn_commit = $gs->last_commit;
+       foreach my $c (reverse @$refs) {
+               next if $c eq $last_svn_commit;
+               last if $skip{$c};
+
+               unshift @linear_refs, $c;
+               $skip{$c} = 1;
+
+               # we only want the first parent to diff against for linear
+               # history, we save the rest to inject when we finalize the
+               # svn commit
+               my $fp_a = verify_ref("$c~1");
+               my $fp_b = shift @{$parents{$c}} if $parents{$c};
+               if (!$fp_a || !$fp_b) {
+                       die "Commit $c\n",
+                           "has no parent commit, and therefore ",
+                           "nothing to diff against.\n",
+                           "You should be working from a repository ",
+                           "originally created by git-svn\n";
+               }
+               if ($fp_a ne $fp_b) {
+                       die "$c~1 = $fp_a, however parsing commit $c ",
+                           "revealed that:\n$c~1 = $fp_b\nBUG!\n";
+               }
+
+               foreach my $p (@{$parents{$c}}) {
+                       $skip{$p} = 1;
+               }
+       }
+       (\@linear_refs, \%parents);
+}
+
 package Git::SVN;
 use strict;
 use warnings;
@@ -846,26 +905,26 @@ BEGIN {
        # some options are read globally, but can be overridden locally
        # per [svn-remote "..."] section.  Command-line options will *NOT*
        # override options set in an [svn-remote "..."] section
-       my $e;
-       foreach (qw/follow_parent no_metadata use_svm_props
-                   use_svnsync_props/) {
-               my $key = $_;
+       no strict 'refs';
+       for my $option (qw/follow_parent no_metadata use_svm_props
+                          use_svnsync_props/) {
+               my $key = $option;
                $key =~ tr/_//d;
-               $e .= "sub $_ {
-                       my (\$self) = \@_;
-                       return \$self->{-$_} if exists \$self->{-$_};
-                       my \$k = \"svn-remote.\$self->{repo_id}\.$key\";
-                       eval { command_oneline(qw/config --get/, \$k) };
-                       if (\$@) {
-                               \$self->{-$_} = \$Git::SVN::_$_;
+               my $prop = "-$option";
+               *$option = sub {
+                       my ($self) = @_;
+                       return $self->{$prop} if exists $self->{$prop};
+                       my $k = "svn-remote.$self->{repo_id}.$key";
+                       eval { command_oneline(qw/config --get/, $k) };
+                       if ($@) {
+                               $self->{$prop} = ${"Git::SVN::_$option"};
                        } else {
-                               my \$v = command_oneline(qw/config --bool/,\$k);
-                               \$self->{-$_} = \$v eq 'false' ? 0 : 1;
+                               my $v = command_oneline(qw/config --bool/,$k);
+                               $self->{$prop} = $v eq 'false' ? 0 : 1;
                        }
-                       return \$self->{-$_} }\n";
+                       return $self->{$prop};
+               }
        }
-       $e .= "1;\n";
-       eval $e or die $@;
 }
 
 my %LOCKFILES;
@@ -879,8 +938,8 @@ sub resolve_local_globs {
        foreach (command(qw#for-each-ref --format=%(refname) refs/remotes#)) {
                next unless m#^refs/remotes/$ref->{regex}$#;
                my $p = $1;
-               my $pathname = $path->full_path($p);
-               my $refname = $ref->full_path($p);
+               my $pathname = desanitize_refname($path->full_path($p));
+               my $refname = desanitize_refname($ref->full_path($p));
                if (my $existing = $fetch->{$pathname}) {
                        if ($existing ne $refname) {
                                die "Refspec conflict:\n",
@@ -967,7 +1026,9 @@ sub read_all_remotes {
        my $r = {};
        foreach (grep { s/^svn-remote\.// } command(qw/config -l/)) {
                if (m!^(.+)\.fetch=\s*(.*)\s*:\s*refs/remotes/(.+)\s*$!) {
-                       $r->{$1}->{fetch}->{$2} = $3;
+                       my ($remote, $local_ref, $remote_ref) = ($1, $2, $3);
+                       $local_ref =~ s{^/}{};
+                       $r->{$remote}->{fetch}->{$local_ref} = $remote_ref;
                } elsif (m!^(.+)\.url=\s*(.*)\s*$!) {
                        $r->{$1}->{url} = $2;
                } elsif (m!^(.+)\.(branches|tags)=
@@ -1087,6 +1148,7 @@ sub init_remote_config {
        unless ($no_write) {
                command_noisy('config',
                              "svn-remote.$self->{repo_id}.url", $url);
+               $self->{path} =~ s{^/}{};
                command_noisy('config', '--add',
                              "svn-remote.$self->{repo_id}.fetch",
                              "$self->{path}:".$self->refname);
@@ -1177,7 +1239,40 @@ sub new {
        $self;
 }
 
-sub refname { "refs/remotes/$_[0]->{ref_id}" }
+sub refname {
+       my ($refname) = "refs/remotes/$_[0]->{ref_id}" ;
+
+       # It cannot end with a slash /, we'll throw up on this because
+       # SVN can't have directories with a slash in their name, either:
+       if ($refname =~ m{/$}) {
+               die "ref: '$refname' ends with a trailing slash, this is ",
+                   "not permitted by git nor Subversion\n";
+       }
+
+       # It cannot have ASCII control character space, tilde ~, caret ^,
+       # colon :, question-mark ?, asterisk *, space, or open bracket [
+       # anywhere.
+       #
+       # Additionally, % must be escaped because it is used for escaping
+       # and we want our escaped refname to be reversible
+       $refname =~ s{([ \%~\^:\?\*\[\t])}{uc sprintf('%%%02x',ord($1))}eg;
+
+       # no slash-separated component can begin with a dot .
+       # /.* becomes /%2E*
+       $refname =~ s{/\.}{/%2E}g;
+
+       # It cannot have two consecutive dots .. anywhere
+       # .. becomes %2E%2E
+       $refname =~ s{\.\.}{%2E%2E}g;
+
+       return $refname;
+}
+
+sub desanitize_refname {
+       my ($refname) = @_;
+       $refname =~ s{%(?:([0-9A-F]{2}))}{chr hex($1)}eg;
+       return $refname;
+}
 
 sub svm_uuid {
        my ($self) = @_;
@@ -1457,7 +1552,7 @@ sub tmp_config {
        my (@args) = @_;
        my $old_def_config = "$ENV{GIT_DIR}/svn/config";
        my $config = "$ENV{GIT_DIR}/svn/.metadata";
-       if (-e $old_def_config && ! -e $config) {
+       if (! -f $config && -f $old_def_config) {
                rename $old_def_config, $config or
                       die "Failed rename $old_def_config => $config: $!\n";
        }
@@ -1541,6 +1636,11 @@ sub get_commit_parents {
        if (my $cur = ::verify_ref($self->refname.'^0')) {
                push @tmp, $cur;
        }
+       if (my $ipd = $self->{inject_parents_dcommit}) {
+               if (my $commit = delete $ipd->{$log_entry->{revision}}) {
+                       push @tmp, @$commit;
+               }
+       }
        push @tmp, $_ foreach (@{$log_entry->{parents}}, @tmp);
        while (my $p = shift @tmp) {
                next if $seen{$p};
@@ -1964,16 +2064,19 @@ sub rebuild {
                return;
        }
        print "Rebuilding $db_path ...\n";
-       my ($rev_list, $ctx) = command_output_pipe("rev-list", $self->refname);
+       my ($log, $ctx) = command_output_pipe("log", $self->refname);
        my $latest;
        my $full_url = $self->full_url;
        remove_username($full_url);
        my $svn_uuid;
-       while (<$rev_list>) {
-               chomp;
-               my $c = $_;
-               die "Non-SHA1: $c\n" unless $c =~ /^$::sha1$/o;
-               my ($url, $rev, $uuid) = ::cmt_metadata($c);
+       my $c;
+       while (<$log>) {
+               if ( m{^commit ($::sha1)$} ) {
+                       $c = $1;
+                       next;
+               }
+               next unless s{^\s*(git-svn-id:)}{$1};
+               my ($url, $rev, $uuid) = ::extract_metadata($_);
                remove_username($url);
 
                # ignore merges (from set-tree)
@@ -1991,7 +2094,7 @@ sub rebuild {
                $self->rev_db_set($rev, $c);
                print "r$rev = $c\n";
        }
-       command_close_pipe($rev_list, $ctx);
+       command_close_pipe($log, $ctx);
        print "Done rebuilding $db_path\n";
 }
 
@@ -2654,6 +2757,9 @@ sub repo_path {
 
 sub url_path {
        my ($self, $path) = @_;
+       if ($self->{url} =~ m#^https?://#) {
+               $path =~ s/([^a-zA-Z0-9_.-])/uc sprintf("%%%02x",ord($1))/eg;
+       }
        $self->{url} . '/' . $self->repo_path($path);
 }
 
@@ -2899,17 +3005,17 @@ my ($can_do_switch, %ignored_err, $RA);
 
 BEGIN {
        # enforce temporary pool usage for some simple functions
-       my $e;
-       foreach (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
-               $e .= "sub $_ {
-                       my \$self = shift;
-                       my \$pool = SVN::Pool->new;
-                       my \@ret = \$self->SUPER::$_(\@_,\$pool);
-                       \$pool->clear;
-                       wantarray ? \@ret : \$ret[0]; }\n";
+       no strict 'refs';
+       for my $f (qw/rev_proplist get_latest_revnum get_uuid get_repos_root/) {
+               my $SUPER = "SUPER::$f";
+               *$f = sub {
+                       my $self = shift;
+                       my $pool = SVN::Pool->new;
+                       my @ret = $self->$SUPER(@_,$pool);
+                       $pool->clear;
+                       wantarray ? @ret : $ret[0];
+               };
        }
-
-       eval "$e; 1;" or die $@;
 }
 
 sub new {
@@ -2923,6 +3029,7 @@ sub new {
            SVN::Client::get_ssl_server_trust_file_provider(),
            SVN::Client::get_simple_prompt_provider(
              \&Git::SVN::Prompt::simple, 2),
+           SVN::Client::get_ssl_client_cert_file_provider(),
            SVN::Client::get_ssl_client_cert_prompt_provider(
              \&Git::SVN::Prompt::ssl_client_cert, 2),
            SVN::Client::get_ssl_client_cert_pw_prompt_provider(
@@ -2934,6 +3041,7 @@ sub new {
              \&Git::SVN::Prompt::username, 2),
          ]);
        my $config = SVN::Core::config_get_config($config_dir);
+       $RA = undef;
        my $self = SVN::Ra->new(url => $url, auth => $baton,
                              config => $config,
                              pool => SVN::Pool->new,
@@ -3072,11 +3180,8 @@ sub gs_do_switch {
        $editor->{git_commit_ok};
 }
 
-sub gs_fetch_loop_common {
-       my ($self, $base, $head, $gsv, $globs) = @_;
-       return if ($base > $head);
-       my $inc = $_log_window_size;
-       my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
+sub longest_common_path {
+       my ($gsv, $globs) = @_;
        my %common;
        my $common_max = scalar @$gsv;
 
@@ -3108,6 +3213,15 @@ sub gs_fetch_loop_common {
                        last;
                }
        }
+       $longest_path;
+}
+
+sub gs_fetch_loop_common {
+       my ($self, $base, $head, $gsv, $globs) = @_;
+       return if ($base > $head);
+       my $inc = $_log_window_size;
+       my ($min, $max) = ($base, $head < $base + $inc ? $head : $base + $inc);
+       my $longest_path = longest_common_path($gsv, $globs);
        while (1) {
                my %revs;
                my $err;
@@ -3387,11 +3501,17 @@ sub log_use_color {
 sub git_svn_log_cmd {
        my ($r_min, $r_max, @args) = @_;
        my $head = 'HEAD';
+       my (@files, @log_opts);
        foreach my $x (@args) {
-               last if $x eq '--';
-               next unless ::verify_ref("$x^0");
-               $head = $x;
-               last;
+               if ($x eq '--' || @files) {
+                       push @files, $x;
+               } else {
+                       if (::verify_ref("$x^0")) {
+                               $head = $x;
+                       } else {
+                               push @log_opts, $x;
+                       }
+               }
        }
 
        my ($url, $rev, $uuid, $gs) = ::working_head_info($head);
@@ -3401,13 +3521,13 @@ sub git_svn_log_cmd {
        push @cmd, '-r' unless $non_recursive;
        push @cmd, qw/--raw --name-status/ if $verbose;
        push @cmd, '--color' if log_use_color();
-       return @cmd unless defined $r_max;
-       if ($r_max == $r_min) {
+       push @cmd, @log_opts;
+       if (defined $r_max && $r_max == $r_min) {
                push @cmd, '--max-count=1';
                if (my $c = $gs->rev_db_get($r_max)) {
                        push @cmd, $c;
                }
-       } else {
+       } elsif (defined $r_max) {
                my ($c_min, $c_max);
                $c_max = $gs->rev_db_get($r_max);
                $c_min = $gs->rev_db_get($r_min);
@@ -3423,7 +3543,7 @@ sub git_svn_log_cmd {
                        push @cmd, $c_min;
                }
        }
-       return @cmd;
+       return (@cmd, @files);
 }
 
 # adapted from pager.c
@@ -3588,7 +3708,7 @@ sub cmd_show_log {
        }
 
        config_pager();
-       @args = (git_svn_log_cmd($r_min, $r_max, @args), @args);
+       @args = git_svn_log_cmd($r_min, $r_max, @args);
        my $log = command_output_pipe(@args);
        run_pager();
        my (@k, $c, $d, $stat);
index 3af8c7e1106d755b1589750ff5673ff6a6cd3b14..8c17fb5ae240c0335ce211b19d516f54e145065f 100755 (executable)
@@ -49,7 +49,7 @@ getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:R:uv") or usage();
 usage if $opt_h;
 
 my $tag_name = $opt_t || "tags";
-my $trunk_name = $opt_T || "trunk";
+my $trunk_name = defined $opt_T ? $opt_T : "trunk";
 my $branch_name = $opt_b || "branches";
 my $project_name = $opt_P || "";
 $project_name = "/" . $project_name if ($project_name);
@@ -542,7 +542,7 @@ sub copy_path($$$$$$$$) {
        if ($node_kind eq $SVN::Node::dir) {
                $srcpath =~ s#/*$#/#;
        }
-       
+
        my $pid = open my $f,'-|';
        die $! unless defined $pid;
        if (!$pid) {
@@ -560,7 +560,7 @@ sub copy_path($$$$$$$$) {
                } else {
                        $p = $path;
                }
-               push(@$new,[$mode,$sha1,$p]);   
+               push(@$new,[$mode,$sha1,$p]);
        }
        close($f) or
                print STDERR "$newrev:$newbranch: could not list files in $oldpath \@ $rev\n";
@@ -867,34 +867,14 @@ sub commit {
                        or die "Cannot write branch $dest for update: $!\n";
        }
 
-       if($tag) {
-               my($in, $out) = ('','');
+       if ($tag) {
                $last_rev = "-" if %$changed_paths;
                # the tag was 'complex', i.e. did not refer to a "real" revision
 
                $dest =~ tr/_/\./ if $opt_u;
-               $branch = $dest;
-
-               my $pid = open2($in, $out, 'git-mktag');
-               print $out ("object $cid\n".
-                   "type commit\n".
-                   "tag $dest\n".
-                   "tagger $committer_name <$committer_email> 0 +0000\n") and
-               close($out)
-                   or die "Cannot create tag object $dest: $!\n";
-
-               my $tagobj = <$in>;
-               chomp $tagobj;
-
-               if ( !close($in) or waitpid($pid, 0) != $pid or
-                               $? != 0 or $tagobj !~ /^[0123456789abcdef]{40}$/ ) {
-                       die "Cannot create tag object $dest: $!\n";
-               }
 
-               open(C,">$git_dir/refs/tags/$dest") and
-               print C ("$tagobj\n") and
-               close(C)
-                       or die "Cannot create tag $branch: $!\n";
+               system('git-tag', $dest, $cid) == 0
+                       or die "Cannot create tag $dest: $!\n";
 
                print "Created tag '$dest' on '$branch'\n" if $opt_v;
        }
diff --git a/git-tag.sh b/git-tag.sh
deleted file mode 100755 (executable)
index 6f0b7a7..0000000
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/bin/sh
-# Copyright (c) 2005 Linus Torvalds
-
-USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d | -v] [-m <msg>] <tagname> [<head>]'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-message_given=
-annotate=
-signed=
-force=
-message=
-username=
-list=
-verify=
-while case "$#" in 0) break ;; esac
-do
-    case "$1" in
-    -a)
-       annotate=1
-       ;;
-    -s)
-       annotate=1
-       signed=1
-       ;;
-    -f)
-       force=1
-       ;;
-    -l)
-       case "$#" in
-       1)
-               set x . ;;
-       esac
-       shift
-       git rev-parse --symbolic --tags | sort | grep "$@"
-       exit $?
-       ;;
-    -m)
-       annotate=1
-       shift
-       message="$1"
-       if test "$#" = "0"; then
-           die "error: option -m needs an argument"
-       else
-           message_given=1
-       fi
-       ;;
-    -F)
-       annotate=1
-       shift
-       if test "$#" = "0"; then
-           die "error: option -F needs an argument"
-       else
-           message="$(cat "$1")"
-           message_given=1
-       fi
-       ;;
-    -u)
-       annotate=1
-       signed=1
-       shift
-       username="$1"
-       ;;
-    -d)
-       shift
-       had_error=0
-       for tag
-       do
-               cur=$(git-show-ref --verify --hash -- "refs/tags/$tag") || {
-                       echo >&2 "Seriously, what tag are you talking about?"
-                       had_error=1
-                       continue
-               }
-               git-update-ref -m 'tag: delete' -d "refs/tags/$tag" "$cur" || {
-                       had_error=1
-                       continue
-               }
-               echo "Deleted tag $tag."
-       done
-       exit $had_error
-       ;;
-    -v)
-       shift
-       tag_name="$1"
-       tag=$(git-show-ref --verify --hash -- "refs/tags/$tag_name") ||
-               die "Seriously, what tag are you talking about?"
-       git-verify-tag -v "$tag"
-       exit $?
-       ;;
-    -*)
-        usage
-       ;;
-    *)
-       break
-       ;;
-    esac
-    shift
-done
-
-name="$1"
-[ "$name" ] || usage
-prev=0000000000000000000000000000000000000000
-if git-show-ref --verify --quiet -- "refs/tags/$name"
-then
-    test -n "$force" || die "tag '$name' already exists"
-    prev=`git rev-parse "refs/tags/$name"`
-fi
-shift
-git-check-ref-format "tags/$name" ||
-       die "we do not like '$name' as a tag name."
-
-object=$(git-rev-parse --verify --default HEAD "$@") || exit 1
-type=$(git-cat-file -t $object) || exit 1
-tagger=$(git-var GIT_COMMITTER_IDENT) || exit 1
-
-test -n "$username" ||
-       username=$(git-repo-config user.signingkey) ||
-       username=$(expr "z$tagger" : 'z\(.*>\)')
-
-trap 'rm -f "$GIT_DIR"/TAG_TMP* "$GIT_DIR"/TAG_FINALMSG "$GIT_DIR"/TAG_EDITMSG' 0
-
-if [ "$annotate" ]; then
-    if [ -z "$message_given" ]; then
-        ( echo "#"
-          echo "# Write a tag message"
-          echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
-        ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR"/TAG_EDITMSG || exit
-    else
-        printf '%s\n' "$message" >"$GIT_DIR"/TAG_EDITMSG
-    fi
-
-    grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
-    git-stripspace >"$GIT_DIR"/TAG_FINALMSG
-
-    [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
-       echo >&2 "No tag message?"
-       exit 1
-    }
-
-    ( printf 'object %s\ntype %s\ntag %s\ntagger %s\n\n' \
-       "$object" "$type" "$name" "$tagger";
-      cat "$GIT_DIR"/TAG_FINALMSG ) >"$GIT_DIR"/TAG_TMP
-    rm -f "$GIT_DIR"/TAG_TMP.asc "$GIT_DIR"/TAG_FINALMSG
-    if [ "$signed" ]; then
-       gpg -bsa -u "$username" "$GIT_DIR"/TAG_TMP &&
-       cat "$GIT_DIR"/TAG_TMP.asc >>"$GIT_DIR"/TAG_TMP ||
-       die "failed to sign the tag with GPG."
-    fi
-    object=$(git-mktag < "$GIT_DIR"/TAG_TMP)
-fi
-
-git update-ref "refs/tags/$name" "$object" "$prev"
-
diff --git a/git-verify-tag.sh b/git-verify-tag.sh
deleted file mode 100755 (executable)
index 8db7dd0..0000000
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/bin/sh
-
-USAGE='<tag>'
-SUBDIRECTORY_OK='Yes'
-. git-sh-setup
-
-verbose=
-while case $# in 0) break;; esac
-do
-       case "$1" in
-       -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
-               verbose=t ;;
-       *)
-               break ;;
-       esac
-       shift
-done
-
-if [ "$#" != "1" ]
-then
-       usage
-fi
-
-type="$(git-cat-file -t "$1" 2>/dev/null)" ||
-       die "$1: no such object."
-
-test "$type" = tag ||
-       die "$1: cannot verify a non-tag object of type $type."
-
-case "$verbose" in
-t)
-       git-cat-file -p "$1" |
-       sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
-       ;;
-esac
-
-trap 'rm -f "$GIT_DIR/.tmp-vtag"' 0
-
-git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
-
-cat "$GIT_DIR/.tmp-vtag" |
-sed '/-----BEGIN PGP/Q' |
-gpg --verify "$GIT_DIR/.tmp-vtag" - || exit 1
-rm -f "$GIT_DIR/.tmp-vtag"
-
diff --git a/git.c b/git.c
index 29b55a16047837084fd9e2e8238137b8a2fe44ea..cab0e7227dfbea3c07be81317418518e55499e18 100644 (file)
--- a/git.c
+++ b/git.c
@@ -4,7 +4,7 @@
 #include "quote.h"
 
 const char git_usage_string[] =
-       "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
+       "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
 
 static void prepend_to_path(const char *dir, int len)
 {
@@ -28,7 +28,7 @@ static void prepend_to_path(const char *dir, int len)
        free(path);
 }
 
-static int handle_options(const char*** argv, int* argc)
+static int handle_options(const char*** argv, int* argc, int* envchanged)
 {
        int handled = 0;
 
@@ -64,14 +64,34 @@ static int handle_options(const char*** argv, int* argc)
                                usage(git_usage_string);
                        }
                        setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
+                       if (envchanged)
+                               *envchanged = 1;
                        (*argv)++;
                        (*argc)--;
                        handled++;
                } else if (!prefixcmp(cmd, "--git-dir=")) {
                        setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
+                       if (envchanged)
+                               *envchanged = 1;
+               } else if (!strcmp(cmd, "--work-tree")) {
+                       if (*argc < 2) {
+                               fprintf(stderr, "No directory given for --work-tree.\n" );
+                               usage(git_usage_string);
+                       }
+                       setenv(GIT_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
+                       if (envchanged)
+                               *envchanged = 1;
+                       (*argv)++;
+                       (*argc)--;
+               } else if (!prefixcmp(cmd, "--work-tree=")) {
+                       setenv(GIT_WORK_TREE_ENVIRONMENT, cmd + 12, 1);
+                       if (envchanged)
+                               *envchanged = 1;
                } else if (!strcmp(cmd, "--bare")) {
                        static char git_dir[PATH_MAX+1];
                        setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
+                       if (envchanged)
+                               *envchanged = 1;
                } else {
                        fprintf(stderr, "Unknown option: %s\n", cmd);
                        usage(git_usage_string);
@@ -150,7 +170,7 @@ static int split_cmdline(char *cmdline, const char ***argv)
 
 static int handle_alias(int *argcp, const char ***argv)
 {
-       int nongit = 0, ret = 0, saved_errno = errno;
+       int nongit = 0, envchanged = 0, ret = 0, saved_errno = errno;
        const char *subdir;
        int count, option_count;
        const char** new_argv;
@@ -161,6 +181,21 @@ static int handle_alias(int *argcp, const char ***argv)
        git_config(git_alias_config);
        if (alias_string) {
                if (alias_string[0] == '!') {
+                       if (*argcp > 1) {
+                               int i, sz = PATH_MAX;
+                               char *s = xmalloc(sz), *new_alias = s;
+
+                               add_to_string(&s, &sz, alias_string, 0);
+                               free(alias_string);
+                               alias_string = new_alias;
+                               for (i = 1; i < *argcp &&
+                                       !add_to_string(&s, &sz, " ", 0) &&
+                                       !add_to_string(&s, &sz, (*argv)[i], 1)
+                                       ; i++)
+                                       ; /* do nothing */
+                               if (!sz)
+                                       die("Too many or long arguments");
+                       }
                        trace_printf("trace: alias to shell cmd: %s => %s\n",
                                     alias_command, alias_string + 1);
                        ret = system(alias_string + 1);
@@ -171,7 +206,11 @@ static int handle_alias(int *argcp, const char ***argv)
                            alias_string + 1, alias_command);
                }
                count = split_cmdline(alias_string, &new_argv);
-               option_count = handle_options(&new_argv, &count);
+               option_count = handle_options(&new_argv, &count, &envchanged);
+               if (envchanged)
+                       die("alias '%s' changes environment variables\n"
+                                "You can use '!git' in the alias to do this.",
+                                alias_command);
                memmove(new_argv - option_count, new_argv,
                                count * sizeof(char *));
                new_argv -= option_count;
@@ -214,34 +253,79 @@ const char git_version_string[] = GIT_VERSION;
  * require working tree to be present -- anything uses this needs
  * RUN_SETUP for reading from the configuration file.
  */
-#define NOT_BARE       (1<<2)
+#define NEED_WORK_TREE (1<<2)
 
-static void handle_internal_command(int argc, const char **argv, char **envp)
+struct cmd_struct {
+       const char *cmd;
+       int (*fn)(int, const char **, const char *);
+       int option;
+};
+
+static int run_command(struct cmd_struct *p, int argc, const char **argv)
+{
+       int status;
+       struct stat st;
+       const char *prefix;
+
+       prefix = NULL;
+       if (p->option & RUN_SETUP)
+               prefix = setup_git_directory();
+       if (p->option & USE_PAGER)
+               setup_pager();
+       if (p->option & NEED_WORK_TREE) {
+               const char *work_tree = get_git_work_tree();
+               const char *git_dir = get_git_dir();
+               if (!is_absolute_path(git_dir))
+                       set_git_dir(make_absolute_path(git_dir));
+               if (!work_tree || chdir(work_tree))
+                       die("%s must be run in a work tree", p->cmd);
+       }
+       trace_argv_printf(argv, argc, "trace: built-in: git");
+
+       status = p->fn(argc, argv, prefix);
+       if (status)
+               return status;
+
+       /* Somebody closed stdout? */
+       if (fstat(fileno(stdout), &st))
+               return 0;
+       /* Ignore write errors for pipes and sockets.. */
+       if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
+               return 0;
+
+       /* Check for ENOSPC and EIO errors.. */
+       if (fflush(stdout))
+               die("write failure on standard output: %s", strerror(errno));
+       if (ferror(stdout))
+               die("unknown write failure on standard output");
+       if (fclose(stdout))
+               die("close failed on standard output: %s", strerror(errno));
+       return 0;
+}
+
+static void handle_internal_command(int argc, const char **argv)
 {
        const char *cmd = argv[0];
-       static struct cmd_struct {
-               const char *cmd;
-               int (*fn)(int, const char **, const char *);
-               int option;
-       } commands[] = {
-               { "add", cmd_add, RUN_SETUP | NOT_BARE },
-               { "annotate", cmd_annotate, RUN_SETUP | USE_PAGER },
+       static struct cmd_struct commands[] = {
+               { "add", cmd_add, RUN_SETUP | NEED_WORK_TREE },
+               { "annotate", cmd_annotate, RUN_SETUP },
                { "apply", cmd_apply },
                { "archive", cmd_archive },
                { "blame", cmd_blame, RUN_SETUP },
                { "branch", cmd_branch, RUN_SETUP },
                { "bundle", cmd_bundle },
                { "cat-file", cmd_cat_file, RUN_SETUP },
-               { "checkout-index", cmd_checkout_index, RUN_SETUP },
+               { "checkout-index", cmd_checkout_index,
+                       RUN_SETUP | NEED_WORK_TREE},
                { "check-ref-format", cmd_check_ref_format },
-               { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
+               { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
                { "cherry", cmd_cherry, RUN_SETUP },
-               { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
+               { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
                { "config", cmd_config },
                { "count-objects", cmd_count_objects, RUN_SETUP },
                { "describe", cmd_describe, RUN_SETUP },
-               { "diff", cmd_diff, USE_PAGER },
+               { "diff", cmd_diff },
                { "diff-files", cmd_diff_files },
                { "diff-index", cmd_diff_index, RUN_SETUP },
                { "diff-tree", cmd_diff_tree, RUN_SETUP },
@@ -264,10 +348,10 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "mailsplit", cmd_mailsplit },
                { "merge-base", cmd_merge_base, RUN_SETUP },
                { "merge-file", cmd_merge_file },
-               { "mv", cmd_mv, RUN_SETUP | NOT_BARE },
+               { "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
-               { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
+               { "pickaxe", cmd_blame, RUN_SETUP },
                { "prune", cmd_prune, RUN_SETUP },
                { "prune-packed", cmd_prune_packed, RUN_SETUP },
                { "push", cmd_push, RUN_SETUP },
@@ -277,19 +361,21 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "rerere", cmd_rerere, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
-               { "revert", cmd_revert, RUN_SETUP | NOT_BARE },
-               { "rm", cmd_rm, RUN_SETUP | NOT_BARE },
-               { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
+               { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
+               { "rm", cmd_rm, RUN_SETUP | NEED_WORK_TREE },
+               { "runstatus", cmd_runstatus, RUN_SETUP | NEED_WORK_TREE },
                { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
                { "stripspace", cmd_stripspace },
                { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP },
+               { "tag", cmd_tag, RUN_SETUP },
                { "tar-tree", cmd_tar_tree },
                { "unpack-objects", cmd_unpack_objects, RUN_SETUP },
                { "update-index", cmd_update_index, RUN_SETUP },
                { "update-ref", cmd_update_ref, RUN_SETUP },
                { "upload-archive", cmd_upload_archive },
+               { "verify-tag", cmd_verify_tag, RUN_SETUP },
                { "version", cmd_version },
                { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER },
                { "write-tree", cmd_write_tree, RUN_SETUP },
@@ -307,25 +393,13 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
 
        for (i = 0; i < ARRAY_SIZE(commands); i++) {
                struct cmd_struct *p = commands+i;
-               const char *prefix;
                if (strcmp(p->cmd, cmd))
                        continue;
-
-               prefix = NULL;
-               if (p->option & RUN_SETUP)
-                       prefix = setup_git_directory();
-               if (p->option & USE_PAGER)
-                       setup_pager();
-               if ((p->option & NOT_BARE) &&
-                               (is_bare_repository() || is_inside_git_dir()))
-                       die("%s must be run in a work tree", cmd);
-               trace_argv_printf(argv, argc, "trace: built-in: git");
-
-               exit(p->fn(argc, argv, prefix));
+               exit(run_command(p, argc, argv));
        }
 }
 
-int main(int argc, const char **argv, char **envp)
+int main(int argc, const char **argv)
 {
        const char *cmd = argv[0] ? argv[0] : "git-help";
        char *slash = strrchr(cmd, '/');
@@ -358,14 +432,14 @@ int main(int argc, const char **argv, char **envp)
        if (!prefixcmp(cmd, "git-")) {
                cmd += 4;
                argv[0] = cmd;
-               handle_internal_command(argc, argv, envp);
+               handle_internal_command(argc, argv);
                die("cannot handle %s internally", cmd);
        }
 
        /* Look for flags.. */
        argv++;
        argc--;
-       handle_options(&argv, &argc);
+       handle_options(&argv, &argc, NULL);
        if (argc > 0) {
                if (!prefixcmp(argv[0], "--"))
                        argv[0] += 2;
@@ -377,11 +451,11 @@ int main(int argc, const char **argv, char **envp)
        cmd = argv[0];
 
        /*
-        * We search for git commands in the following order:
-        *  - git_exec_path()
-        *  - the path of the "git" command if we could find it
-        *    in $0
-        *  - the regular PATH.
+        * We execute external git command via execv_git_cmd(),
+        * which looks at "--exec-path" option, GIT_EXEC_PATH
+        * environment, and $(gitexecdir) in Makefile while built,
+        * in this order.  For scripted commands, we prepend
+        * the value of the exec_path variable to the PATH.
         */
        if (exec_path)
                prepend_to_path(exec_path, strlen(exec_path));
@@ -390,7 +464,7 @@ int main(int argc, const char **argv, char **envp)
 
        while (1) {
                /* See if it's an internal command */
-               handle_internal_command(argc, argv, envp);
+               handle_internal_command(argc, argv);
 
                /* .. then try the external ones */
                execv_git_cmd(argv);
index 3a45eb876157d9979f51fe88ecc5d5ae4a93ff1c..fe7b3d821567fd7a8108ae3fd7a90cdd5baac72a 100644 (file)
@@ -12,7 +12,7 @@ URL:          http://kernel.org/pub/software/scm/git/
 Source:        http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
 BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel  %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
 BuildRoot:     %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, git-p4, perl-Git
+Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, perl-Git
 
 %description
 Git is a fast, scalable, distributed revision control system with an
@@ -53,17 +53,10 @@ Requires:       git-core = %{version}-%{release}, tla
 %description arch
 Git tools for importing Arch repositories.
 
-%package p4
-Summary:        Git tools for importing Perforce repositories
-Group:          Development/Tools
-Requires:       git-core = %{version}-%{release}, python
-%description p4
-Git tools for importing Perforce repositories.
-
 %package email
 Summary:        Git tools for sending email
 Group:          Development/Tools
-Requires:      git-core = %{version}-%{release} 
+Requires:      git-core = %{version}-%{release}
 %description email
 Git tools for sending email.
 
@@ -95,14 +88,14 @@ Perl interface to Git
 %setup -q
 
 %build
-make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_P4IMPORT=YesPlease \
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" \
      ETC_GITCONFIG=/etc/gitconfig \
-     prefix=%{_prefix} PYTHON_PATH=%{python_path} all %{!?_without_docs: doc}
+     prefix=%{_prefix} all %{!?_without_docs: doc}
 
 %install
 rm -rf $RPM_BUILD_ROOT
 make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
-     WITH_P4IMPORT=YesPlease prefix=%{_prefix} mandir=%{_mandir} \
+     prefix=%{_prefix} mandir=%{_mandir} \
      ETC_GITCONFIG=/etc/gitconfig \
      PYTHON_PATH=%{python_path} \
      INSTALLDIRS=vendor install %{!?_without_docs: install-doc}
@@ -110,10 +103,10 @@ find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
 find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
 
-(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "p4import|archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "archimport|svn|cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@)               > bin-man-doc-files
 (find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
 %if %{!?_without_docs:1}0
-(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "p4import|archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "archimport|svn|git-cvs|email|gitk|git-gui|git-citool" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
 %else
 rm -rf $RPM_BUILD_ROOT%{_mandir}
 %endif
@@ -145,13 +138,6 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %{_mandir}/man1/git-archimport.1*}
 %{!?_without_docs: %doc Documentation/git-archimport.html }
 
-%files p4
-%defattr(-,root,root)
-%doc Documentation/git-p4import.txt
-%{_bindir}/git-p4import
-%{!?_without_docs: %{_mandir}/man1/git-p4import.1*}
-%{!?_without_docs: %doc Documentation/git-p4import.html }
-
 %files email
 %defattr(-,root,root)
 %doc Documentation/*email*.txt
@@ -164,11 +150,10 @@ rm -rf $RPM_BUILD_ROOT
 %{_bindir}/git-gui
 %{_bindir}/git-citool
 %{_datadir}/git-gui/
-# Not Yet...
-# %{!?_without_docs: %{_mandir}/man1/git-gui.1}
-# %{!?_without_docs: %doc Documentation/git-gui.html}
-# %{!?_without_docs: %{_mandir}/man1/git-citool.1}
-# %{!?_without_docs: %doc Documentation/git-citool.html}
+%{!?_without_docs: %{_mandir}/man1/git-gui.1*}
+%{!?_without_docs: %doc Documentation/git-gui.html}
+%{!?_without_docs: %{_mandir}/man1/git-citool.1*}
+%{!?_without_docs: %doc Documentation/git-citool.html}
 
 %files -n gitk
 %defattr(-,root,root)
@@ -188,6 +173,15 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %doc Documentation/technical}
 
 %changelog
+* Sun Jul 15 2007 Sean Estabrooks <seanlkml@sympatico.ca>
+- Removed p4import.
+
+* Tue Jun 26 2007 Quy Tonthat <qtonthat@gmail.com>
+- Fixed problems looking for wrong manpages.
+
+* Thu Jun 21 2007 Shawn O. Pearce <spearce@spearce.org>
+- Added documentation files for git-gui
+
 * Tue May 13 2007 Quy Tonthat <qtonthat@gmail.com>
 - Added lib files for git-gui
 - Added Documentation/technical (As needed by Git Users Manual)
diff --git a/gitk b/gitk
index a57e84cef7143839af954e2aa1fcb3d633befe41..57617d58b070892a730e956e82a2f382c28659e2 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -16,36 +16,96 @@ proc gitdir {} {
     }
 }
 
+# A simple scheduler for compute-intensive stuff.
+# The aim is to make sure that event handlers for GUI actions can
+# run at least every 50-100 ms.  Unfortunately fileevent handlers are
+# run before X event handlers, so reading from a fast source can
+# make the GUI completely unresponsive.
+proc run args {
+    global isonrunq runq
+
+    set script $args
+    if {[info exists isonrunq($script)]} return
+    if {$runq eq {}} {
+       after idle dorunq
+    }
+    lappend runq [list {} $script]
+    set isonrunq($script) 1
+}
+
+proc filerun {fd script} {
+    fileevent $fd readable [list filereadable $fd $script]
+}
+
+proc filereadable {fd script} {
+    global runq
+
+    fileevent $fd readable {}
+    if {$runq eq {}} {
+       after idle dorunq
+    }
+    lappend runq [list $fd $script]
+}
+
+proc dorunq {} {
+    global isonrunq runq
+
+    set tstart [clock clicks -milliseconds]
+    set t0 $tstart
+    while {$runq ne {}} {
+       set fd [lindex $runq 0 0]
+       set script [lindex $runq 0 1]
+       set repeat [eval $script]
+       set t1 [clock clicks -milliseconds]
+       set t [expr {$t1 - $t0}]
+       set runq [lrange $runq 1 end]
+       if {$repeat ne {} && $repeat} {
+           if {$fd eq {} || $repeat == 2} {
+               # script returns 1 if it wants to be readded
+               # file readers return 2 if they could do more straight away
+               lappend runq [list $fd $script]
+           } else {
+               fileevent $fd readable [list filereadable $fd $script]
+           }
+       } elseif {$fd eq {}} {
+           unset isonrunq($script)
+       }
+       set t0 $t1
+       if {$t1 - $tstart >= 80} break
+    }
+    if {$runq ne {}} {
+       after idle dorunq
+    }
+}
+
+# Start off a git rev-list process and arrange to read its output
 proc start_rev_list {view} {
-    global startmsecs nextupdate
+    global startmsecs
     global commfd leftover tclencoding datemode
     global viewargs viewfiles commitidx
+    global lookingforhead showlocalchanges
 
     set startmsecs [clock clicks -milliseconds]
-    set nextupdate [expr {$startmsecs + 100}]
     set commitidx($view) 0
-    set args $viewargs($view)
-    if {$viewfiles($view) ne {}} {
-       set args [concat $args "--" $viewfiles($view)]
-    }
     set order "--topo-order"
     if {$datemode} {
        set order "--date-order"
     }
     if {[catch {
-       set fd [open [concat | git rev-list --header $order \
-                         --parents --boundary --default HEAD $args] r]
+       set fd [open [concat | git log -z --pretty=raw $order --parents \
+                        --boundary $viewargs($view) "--" $viewfiles($view)] r]
     } err]} {
-       puts stderr "Error executing git rev-list: $err"
+       error_popup "Error executing git rev-list: $err"
        exit 1
     }
     set commfd($view) $fd
     set leftover($view) {}
-    fconfigure $fd -blocking 0 -translation lf
+    set lookingforhead $showlocalchanges
+    fconfigure $fd -blocking 0 -translation lf -eofchar {}
     if {$tclencoding != {}} {
        fconfigure $fd -encoding $tclencoding
     }
-    fileevent $fd readable [list getcommitlines $fd $view]
+    filerun $fd [list getcommitlines $fd $view]
     nowbusy $view
 }
 
@@ -72,15 +132,21 @@ proc getcommits {} {
 }
 
 proc getcommitlines {fd view}  {
-    global commitlisted nextupdate
+    global commitlisted
     global leftover commfd
     global displayorder commitidx commitrow commitdata
-    global parentlist childlist children curview hlview
-    global vparentlist vchildlist vdisporder vcmitlisted
+    global parentlist children curview hlview
+    global vparentlist vdisporder vcmitlisted
 
     set stuff [read $fd 500000]
+    # git log doesn't terminate the last commit with a null...
+    if {$stuff == {} && $leftover($view) ne {} && [eof $fd]} {
+       set stuff "\0"
+    }
     if {$stuff == {}} {
-       if {![eof $fd]} return
+       if {![eof $fd]} {
+           return 1
+       }
        global viewname
        unset commfd($view)
        notbusy $view
@@ -105,9 +171,9 @@ proc getcommitlines {fd view}  {
            error_popup $err
        }
        if {$view == $curview} {
-           after idle finishcommits
+           run chewcommits $view
        }
-       return
+       return 0
     }
     set start 0
     set gotsome 0
@@ -128,10 +194,14 @@ proc getcommitlines {fd view}  {
        set j [string first "\n" $cmit]
        set ok 0
        set listed 1
-       if {$j >= 0} {
-           set ids [string range $cmit 0 [expr {$j - 1}]]
-           if {[string range $ids 0 0] == "-"} {
-               set listed 0
+       if {$j >= 0 && [string match "commit *" $cmit]} {
+           set ids [string range $cmit 7 [expr {$j - 1}]]
+           if {[string match {[-<>]*} $ids]} {
+               switch -- [string index $ids 0] {
+                   "-" {set listed 0}
+                   "<" {set listed 2}
+                   ">" {set listed 3}
+               }
                set ids [string range $ids 1 end]
            }
            set ok 1
@@ -147,7 +217,7 @@ proc getcommitlines {fd view}  {
            if {[string length $shortcmit] > 80} {
                set shortcmit "[string range $shortcmit 0 80]..."
            }
-           error_popup "Can't parse git rev-list output: {$shortcmit}"
+           error_popup "Can't parse git log output: {$shortcmit}"
            exit 1
        }
        set id [lindex $ids 0]
@@ -171,41 +241,52 @@ proc getcommitlines {fd view}  {
        incr commitidx($view)
        if {$view == $curview} {
            lappend parentlist $olds
-           lappend childlist $children($view,$id)
            lappend displayorder $id
            lappend commitlisted $listed
        } else {
            lappend vparentlist($view) $olds
-           lappend vchildlist($view) $children($view,$id)
            lappend vdisporder($view) $id
            lappend vcmitlisted($view) $listed
        }
        set gotsome 1
     }
     if {$gotsome} {
-       if {$view == $curview} {
-           while {[layoutmore $nextupdate]} doupdate
-       } elseif {[info exists hlview] && $view == $hlview} {
-           vhighlightmore
-       }
-    }
-    if {[clock clicks -milliseconds] >= $nextupdate} {
-       doupdate
+       run chewcommits $view
     }
+    return 2
 }
 
-proc doupdate {} {
-    global commfd nextupdate numcommits
+proc chewcommits {view} {
+    global curview hlview commfd
+    global selectedline pending_select
 
-    foreach v [array names commfd] {
-       fileevent $commfd($v) readable {}
+    set more 0
+    if {$view == $curview} {
+       set allread [expr {![info exists commfd($view)]}]
+       set tlimit [expr {[clock clicks -milliseconds] + 50}]
+       set more [layoutmore $tlimit $allread]
+       if {$allread && !$more} {
+           global displayorder commitidx phase
+           global numcommits startmsecs
+
+           if {[info exists pending_select]} {
+               set row [first_real_row]
+               selectline $row 1
+           }
+           if {$commitidx($curview) > 0} {
+               #set ms [expr {[clock clicks -milliseconds] - $startmsecs}]
+               #puts "overall $ms ms for $numcommits commits"
+           } else {
+               show_status "No commits selected"
+           }
+           notbusy layout
+           set phase {}
+       }
     }
-    update
-    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
-    foreach v [array names commfd] {
-       set fd $commfd($v)
-       fileevent $fd readable [list getcommitlines $fd $v]
+    if {[info exists hlview] && $view == $hlview} {
+       vhighlightmore
     }
+    return $more
 }
 
 proc readcommit {id} {
@@ -215,7 +296,7 @@ proc readcommit {id} {
 
 proc updatecommits {} {
     global viewdata curview phase displayorder
-    global children commitrow selectedline thickerline
+    global children commitrow selectedline thickerline showneartags
 
     if {$phase ne {}} {
        stop_rev_list
@@ -230,8 +311,11 @@ proc updatecommits {} {
     catch {unset selectedline}
     catch {unset thickerline}
     catch {unset viewdata($n)}
-    discardallcommits
     readrefs
+    changedrefs
+    if {$showneartags} {
+       getallcommits
+    }
     showview $n
 }
 
@@ -263,12 +347,16 @@ proc parsecommit {id contents listed} {
        }
     }
     set headline {}
-    # take the first line of the comment as the headline
-    set i [string first "\n" $comment]
+    # take the first non-blank line of the comment as the headline
+    set headline [string trimleft $comment]
+    set i [string first "\n" $headline]
     if {$i >= 0} {
-       set headline [string trim [string range $comment 0 $i]]
-    } else {
-       set headline $comment
+       set headline [string range $headline 0 $i]
+    }
+    set headline [string trimright $headline]
+    set i [string first "\r" $headline]
+    if {$i >= 0} {
+       set headline [string trimright [string range $headline 0 $i]]
     }
     if {!$listed} {
        # git rev-list indents the comment by 4 spaces;
@@ -303,60 +391,93 @@ proc getcommit {id} {
 }
 
 proc readrefs {} {
-    global tagids idtags headids idheads tagcontents
-    global otherrefids idotherrefs mainhead
+    global tagids idtags headids idheads tagobjid
+    global otherrefids idotherrefs mainhead mainheadid
 
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
        catch {unset $v}
     }
-    set refd [open [list | git show-ref] r]
-    while {0 <= [set n [gets $refd line]]} {
-       if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \
-           match id path]} {
-           continue
-       }
-       if {[regexp {^remotes/.*/HEAD$} $path match]} {
-           continue
-       }
-       if {![regexp {^(tags|heads)/(.*)$} $path match type name]} {
-           set type others
-           set name $path
-       }
-       if {[regexp {^remotes/} $path match]} {
-           set type heads
-       }
-       if {$type == "tags"} {
-           set tagids($name) $id
-           lappend idtags($id) $name
-           set obj {}
-           set type {}
-           set tag {}
-           catch {
-               set commit [exec git rev-parse "$id^0"]
-               if {$commit != $id} {
-                   set tagids($name) $commit
-                   lappend idtags($commit) $name
-               }
-           }           
-           catch {
-               set tagcontents($name) [exec git cat-file tag $id]
+    set refd [open [list | git show-ref -d] r]
+    while {[gets $refd line] >= 0} {
+       if {[string index $line 40] ne " "} continue
+       set id [string range $line 0 39]
+       set ref [string range $line 41 end]
+       if {![string match "refs/*" $ref]} continue
+       set name [string range $ref 5 end]
+       if {[string match "remotes/*" $name]} {
+           if {![string match "*/HEAD" $name]} {
+               set headids($name) $id
+               lappend idheads($id) $name
            }
-       } elseif { $type == "heads" } {
+       } elseif {[string match "heads/*" $name]} {
+           set name [string range $name 6 end]
            set headids($name) $id
            lappend idheads($id) $name
+       } elseif {[string match "tags/*" $name]} {
+           # this lets refs/tags/foo^{} overwrite refs/tags/foo,
+           # which is what we want since the former is the commit ID
+           set name [string range $name 5 end]
+           if {[string match "*^{}" $name]} {
+               set name [string range $name 0 end-3]
+           } else {
+               set tagobjid($name) $id
+           }
+           set tagids($name) $id
+           lappend idtags($id) $name
        } else {
            set otherrefids($name) $id
            lappend idotherrefs($id) $name
        }
     }
-    close $refd
+    catch {close $refd}
     set mainhead {}
+    set mainheadid {}
     catch {
        set thehead [exec git symbolic-ref HEAD]
        if {[string match "refs/heads/*" $thehead]} {
            set mainhead [string range $thehead 11 end]
+           if {[info exists headids($mainhead)]} {
+               set mainheadid $headids($mainhead)
+           }
+       }
+    }
+}
+
+# skip over fake commits
+proc first_real_row {} {
+    global nullid nullid2 displayorder numcommits
+
+    for {set row 0} {$row < $numcommits} {incr row} {
+       set id [lindex $displayorder $row]
+       if {$id ne $nullid && $id ne $nullid2} {
+           break
+       }
+    }
+    return $row
+}
+
+# update things for a head moved to a child of its previous location
+proc movehead {id name} {
+    global headids idheads
+
+    removehead $headids($name) $name
+    set headids($name) $id
+    lappend idheads($id) $name
+}
+
+# update things when a head has been removed
+proc removehead {id name} {
+    global headids idheads
+
+    if {$idheads($id) eq $name} {
+       unset idheads($id)
+    } else {
+       set i [lsearch -exact $idheads($id) $name]
+       if {$i >= 0} {
+           set idheads($id) [lreplace $idheads($id) $i $i]
        }
     }
+    unset headids($name)
 }
 
 proc show_error {w top msg} {
@@ -395,14 +516,14 @@ proc confirm_popup msg {
 
 proc makewindow {} {
     global canv canv2 canv3 linespc charspc ctext cflist
-    global textfont mainfont uifont
+    global textfont mainfont uifont tabstop
     global findtype findtypemenu findloc findstring fstring geometry
     global entries sha1entry sha1string sha1but
     global maincursor textcursor curtextcursor
-    global rowctxmenu mergemax wrapcomment
+    global rowctxmenu fakerowmenu mergemax wrapcomment
     global highlight_files gdttype
     global searchstring sstring
-    global bgcolor fgcolor bglist fglist diffcolors
+    global bgcolor fgcolor bglist fglist diffcolors selectbgcolor
     global headctxmenu
 
     menu .bar
@@ -457,15 +578,18 @@ proc makewindow {} {
     set cscroll .tf.histframe.csb
     set canv .tf.histframe.pwclist.canv
     canvas $canv \
+       -selectbackground $selectbgcolor \
        -background $bgcolor -bd 0 \
        -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
     .tf.histframe.pwclist add $canv
     set canv2 .tf.histframe.pwclist.canv2
     canvas $canv2 \
+       -selectbackground $selectbgcolor \
        -background $bgcolor -bd 0 -yscrollincr $linespc
     .tf.histframe.pwclist add $canv2
     set canv3 .tf.histframe.pwclist.canv3
     canvas $canv3 \
+       -selectbackground $selectbgcolor \
        -background $bgcolor -bd 0 -yscrollincr $linespc
     .tf.histframe.pwclist add $canv3
     eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
@@ -612,6 +736,7 @@ proc makewindow {} {
     pack .bleft.mid.diff .bleft.mid.old .bleft.mid.new -side left
     set ctext .bleft.ctext
     text $ctext -background $bgcolor -foreground $fgcolor \
+       -tabs "[expr {$tabstop * $charspc}]" \
        -state disabled -font $textfont \
        -yscrollcommand scrolltext -wrap none
     scrollbar .bleft.sb -command "$ctext yview"
@@ -666,6 +791,7 @@ proc makewindow {} {
     set cflist .bright.cfiles
     set indent [font measure $mainfont "nn"]
     text $cflist \
+       -selectbackground $selectbgcolor \
        -background $bgcolor -foreground $fgcolor \
        -font $mainfont \
        -tabs [list $indent [expr {2 * $indent}]] \
@@ -689,12 +815,23 @@ proc makewindow {} {
         wm geometry . "$geometry(main)"
     }
 
+    if {[tk windowingsystem] eq {aqua}} {
+        set M1B M1
+    } else {
+        set M1B Control
+    }
+
     bind .pwbottom <Configure> {resizecdetpanes %W %w}
     pack .ctop -fill both -expand 1
     bindall <1> {selcanvline %W %x %y}
     #bindall <B1-Motion> {selcanvline %W %x %y}
-    bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
-    bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
+    if {[tk windowingsystem] == "win32"} {
+       bind . <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D }
+       bind $ctext <MouseWheel> { windows_mousewheel_redirector %W %X %Y %D ; break }
+    } else {
+       bindall <ButtonRelease-4> "allcanvs yview scroll -5 units"
+       bindall <ButtonRelease-5> "allcanvs yview scroll 5 units"
+    }
     bindall <2> "canvscan mark %W %x %y"
     bindall <B2-Motion> "canvscan dragto %W %x %y"
     bindkey <Home> selfirstline
@@ -707,12 +844,12 @@ proc makewindow {} {
     bindkey <Key-Left> "goback"
     bind . <Key-Prior> "selnextpage -1"
     bind . <Key-Next> "selnextpage 1"
-    bind . <Control-Home> "allcanvs yview moveto 0.0"
-    bind . <Control-End> "allcanvs yview moveto 1.0"
-    bind . <Control-Key-Up> "allcanvs yview scroll -1 units"
-    bind . <Control-Key-Down> "allcanvs yview scroll 1 units"
-    bind . <Control-Key-Prior> "allcanvs yview scroll -1 pages"
-    bind . <Control-Key-Next> "allcanvs yview scroll 1 pages"
+    bind . <$M1B-Home> "allcanvs yview moveto 0.0"
+    bind . <$M1B-End> "allcanvs yview moveto 1.0"
+    bind . <$M1B-Key-Up> "allcanvs yview scroll -1 units"
+    bind . <$M1B-Key-Down> "allcanvs yview scroll 1 units"
+    bind . <$M1B-Key-Prior> "allcanvs yview scroll -1 pages"
+    bind . <$M1B-Key-Next> "allcanvs yview scroll 1 pages"
     bindkey <Key-Delete> "$ctext yview scroll -1 pages"
     bindkey <Key-BackSpace> "$ctext yview scroll -1 pages"
     bindkey <Key-space> "$ctext yview scroll 1 pages"
@@ -732,15 +869,15 @@ proc makewindow {} {
     bindkey ? findprev
     bindkey f nextfile
     bindkey <F5> updatecommits
-    bind . <Control-q> doquit
-    bind . <Control-f> dofind
-    bind . <Control-g> {findnext 0}
-    bind . <Control-r> dosearchback
-    bind . <Control-s> dosearch
-    bind . <Control-equal> {incrfont 1}
-    bind . <Control-KP_Add> {incrfont 1}
-    bind . <Control-minus> {incrfont -1}
-    bind . <Control-KP_Subtract> {incrfont -1}
+    bind . <$M1B-q> doquit
+    bind . <$M1B-f> dofind
+    bind . <$M1B-g> {findnext 0}
+    bind . <$M1B-r> dosearchback
+    bind . <$M1B-s> dosearch
+    bind . <$M1B-equal> {incrfont 1}
+    bind . <$M1B-KP_Add> {incrfont 1}
+    bind . <$M1B-minus> {incrfont -1}
+    bind . <$M1B-KP_Subtract> {incrfont -1}
     wm protocol . WM_DELETE_WINDOW doquit
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> dofind
@@ -749,6 +886,7 @@ proc makewindow {} {
     bind $cflist <1> {sel_flist %W %x %y; break}
     bind $cflist <B1-Motion> {sel_flist %W %x %y; break}
     bind $cflist <ButtonRelease-1> {treeclick %W %x %y}
+    bind $cflist <Button-3> {pop_flist_menu %W %X %Y %x %y}
 
     set maincursor [. cget -cursor]
     set textcursor [$ctext cget -cursor]
@@ -766,6 +904,19 @@ proc makewindow {} {
     $rowctxmenu add command -label "Create new branch" -command mkbranch
     $rowctxmenu add command -label "Cherry-pick this commit" \
        -command cherrypick
+    $rowctxmenu add command -label "Reset HEAD branch to here" \
+       -command resethead
+
+    set fakerowmenu .fakerowmenu
+    menu $fakerowmenu -tearoff 0
+    $fakerowmenu add command -label "Diff this -> selected" \
+       -command {diffvssel 0}
+    $fakerowmenu add command -label "Diff selected -> this" \
+       -command {diffvssel 1}
+    $fakerowmenu add command -label "Make patch" -command mkpatch
+#    $fakerowmenu add command -label "Commit" -command {mkcommit 0}
+#    $fakerowmenu add command -label "Commit all" -command {mkcommit 1}
+#    $fakerowmenu add command -label "Revert local changes" -command revertlocal
 
     set headctxmenu .headctxmenu
     menu $headctxmenu -tearoff 0
@@ -773,6 +924,32 @@ proc makewindow {} {
        -command cobranch
     $headctxmenu add command -label "Remove this branch" \
        -command rmbranch
+
+    global flist_menu
+    set flist_menu .flistctxmenu
+    menu $flist_menu -tearoff 0
+    $flist_menu add command -label "Highlight this too" \
+       -command {flist_hl 0}
+    $flist_menu add command -label "Highlight this only" \
+       -command {flist_hl 1}
+}
+
+# Windows sends all mouse wheel events to the current focused window, not
+# the one where the mouse hovers, so bind those events here and redirect
+# to the correct window
+proc windows_mousewheel_redirector {W X Y D} {
+    global canv canv2 canv3
+    set w [winfo containing -displayof $W $X $Y]
+    if {$w ne ""} {
+       set u [expr {$D < 0 ? 5 : -5}]
+       if {$w == $canv || $w == $canv2 || $w == $canv3} {
+           allcanvs yview scroll $u units
+       } else {
+           catch {
+               $w yview scroll $u units
+           }
+       }
+    }
 }
 
 # mouse-2 makes all windows scan vertically, but only the one
@@ -812,20 +989,20 @@ proc bindkey {ev script} {
 # set the focus back to the toplevel for any click outside
 # the entry widgets
 proc click {w} {
-    global entries
-    foreach e $entries {
+    global ctext entries
+    foreach e [concat $entries $ctext] {
        if {$w == $e} return
     }
     focus .
 }
 
 proc savestuff {w} {
-    global canv canv2 canv3 ctext cflist mainfont textfont uifont
+    global canv canv2 canv3 ctext cflist mainfont textfont uifont tabstop
     global stuffsaved findmergefiles maxgraphpct
-    global maxwidth showneartags
+    global maxwidth showneartags showlocalchanges
     global viewname viewfiles viewargs viewperm nextviewnum
     global cmitmode wrapcomment
-    global colors bgcolor fgcolor diffcolors
+    global colors bgcolor fgcolor diffcolors selectbgcolor
 
     if {$stuffsaved} return
     if {![winfo viewable .]} return
@@ -834,16 +1011,19 @@ proc savestuff {w} {
        puts $f [list set mainfont $mainfont]
        puts $f [list set textfont $textfont]
        puts $f [list set uifont $uifont]
+       puts $f [list set tabstop $tabstop]
        puts $f [list set findmergefiles $findmergefiles]
        puts $f [list set maxgraphpct $maxgraphpct]
        puts $f [list set maxwidth $maxwidth]
        puts $f [list set cmitmode $cmitmode]
        puts $f [list set wrapcomment $wrapcomment]
        puts $f [list set showneartags $showneartags]
+       puts $f [list set showlocalchanges $showlocalchanges]
        puts $f [list set bgcolor $bgcolor]
        puts $f [list set fgcolor $fgcolor]
        puts $f [list set colors $colors]
        puts $f [list set diffcolors $diffcolors]
+       puts $f [list set selectbgcolor $selectbgcolor]
 
        puts $f "set geometry(main) [wm geometry .]"
        puts $f "set geometry(topwidth) [winfo width .tf]"
@@ -965,12 +1145,17 @@ proc keys {} {
        raise $w
        return
     }
+    if {[tk windowingsystem] eq {aqua}} {
+       set M1T Cmd
+    } else {
+       set M1T Ctrl
+    }
     toplevel $w
     wm title $w "Gitk key bindings"
-    message $w.m -text {
+    message $w.m -text "
 Gitk key bindings:
 
-<Ctrl-Q>               Quit
+<$M1T-Q>               Quit
 <Home>         Move to first commit
 <End>          Move to last commit
 <Up>, p, i     Move up one commit
@@ -979,12 +1164,12 @@ Gitk key bindings:
 <Right>, x, l  Go forward in history list
 <PageUp>       Move up one page in commit list
 <PageDown>     Move down one page in commit list
-<Ctrl-Home>    Scroll to top of commit list
-<Ctrl-End>     Scroll to bottom of commit list
-<Ctrl-Up>      Scroll commit list up one line
-<Ctrl-Down>    Scroll commit list down one line
-<Ctrl-PageUp>  Scroll commit list up one page
-<Ctrl-PageDown>        Scroll commit list down one page
+<$M1T-Home>    Scroll to top of commit list
+<$M1T-End>     Scroll to bottom of commit list
+<$M1T-Up>      Scroll commit list up one line
+<$M1T-Down>    Scroll commit list down one line
+<$M1T-PageUp>  Scroll commit list up one page
+<$M1T-PageDown>        Scroll commit list down one page
 <Shift-Up>     Move to previous highlighted line
 <Shift-Down>   Move to next highlighted line
 <Delete>, b    Scroll diff view up one page
@@ -992,20 +1177,20 @@ Gitk key bindings:
 <Space>                Scroll diff view down one page
 u              Scroll diff view up 18 lines
 d              Scroll diff view down 18 lines
-<Ctrl-F>               Find
-<Ctrl-G>               Move to next find hit
+<$M1T-F>               Find
+<$M1T-G>               Move to next find hit
 <Return>       Move to next find hit
 /              Move to next find hit, or redo find
 ?              Move to previous find hit
 f              Scroll diff view to next file
-<Ctrl-S>               Search for next hit in diff view
-<Ctrl-R>               Search for previous hit in diff view
-<Ctrl-KP+>     Increase font size
-<Ctrl-plus>    Increase font size
-<Ctrl-KP->     Decrease font size
-<Ctrl-minus>   Decrease font size
+<$M1T-S>               Search for next hit in diff view
+<$M1T-R>               Search for previous hit in diff view
+<$M1T-KP+>     Increase font size
+<$M1T-plus>    Increase font size
+<$M1T-KP->     Decrease font size
+<$M1T-minus>   Decrease font size
 <F5>           Update
-} \
+" \
            -justify left -bg white -border 2 -relief groove
     pack $w.m -side top -fill both -padx 2 -pady 2
     $w.m configure -font $uifont
@@ -1093,6 +1278,9 @@ proc treeview {w l openlevs} {
        set treeheight($prefix) $ht
        incr ht [lindex $htstack end]
        set htstack [lreplace $htstack end end]
+       set prefixend [lindex $prefendstack end]
+       set prefendstack [lreplace $prefendstack end end]
+       set prefix [string range $prefix 0 $prefixend]
     }
     $w conf -state disabled
 }
@@ -1345,6 +1533,33 @@ proc sel_flist {w x y} {
     }
 }
 
+proc pop_flist_menu {w X Y x y} {
+    global ctext cflist cmitmode flist_menu flist_menu_file
+    global treediffs diffids
+
+    set l [lindex [split [$w index "@$x,$y"] "."] 0]
+    if {$l <= 1} return
+    if {$cmitmode eq "tree"} {
+       set e [linetoelt $l]
+       if {[string index $e end] eq "/"} return
+    } else {
+       set e [lindex $treediffs($diffids) [expr {$l-2}]]
+    }
+    set flist_menu_file $e
+    tk_popup $flist_menu $X $Y
+}
+
+proc flist_hl {only} {
+    global flist_menu_file highlight_files
+
+    set x [shellquote $flist_menu_file]
+    if {$only || $highlight_files eq {}} {
+       set highlight_files $x
+    } else {
+       append highlight_files " " $x
+    }
+}
+
 # Functions for adding and removing shell-type quoting
 
 proc shellquote {str} {
@@ -1562,9 +1777,9 @@ proc newviewok {top n} {
        set viewargs($n) $newargs
        addviewmenu $n
        if {!$newishighlight} {
-           after idle showview $n
+           run showview $n
        } else {
-           after idle addvhighlight $n
+           run addvhighlight $n
        }
     } else {
        # editing an existing view
@@ -1580,7 +1795,7 @@ proc newviewok {top n} {
            set viewfiles($n) $files
            set viewargs($n) $newargs
            if {$curview == $n} {
-               after idle updatecommits
+               run updatecommits
            }
        }
     }
@@ -1631,16 +1846,16 @@ proc unflatten {var l} {
 
 proc showview {n} {
     global curview viewdata viewfiles
-    global displayorder parentlist childlist rowidlist rowoffsets
+    global displayorder parentlist rowidlist rowoffsets
     global colormap rowtextx commitrow nextcolor canvxmax
-    global numcommits rowrangelist commitlisted idrowranges
+    global numcommits rowrangelist commitlisted idrowranges rowchk
     global selectedline currentid canv canvy0
-    global matchinglines treediffs
+    global treediffs
     global pending_select phase
-    global commitidx rowlaidout rowoptim linesegends
-    global commfd nextupdate
-    global selectedview
-    global vparentlist vchildlist vdisporder vcmitlisted
+    global commitidx rowlaidout rowoptim
+    global commfd
+    global selectedview selectfirst
+    global vparentlist vdisporder vcmitlisted
     global hlview selectedhlview
 
     if {$n == $curview} return
@@ -1657,27 +1872,27 @@ proc showview {n} {
        } else {
            set yscreen [expr {($ybot - $ytop) / 2}]
        }
+    } elseif {[info exists pending_select]} {
+       set selid $pending_select
+       unset pending_select
     }
     unselectline
     normalline
-    stopfindproc
     if {$curview >= 0} {
        set vparentlist($curview) $parentlist
-       set vchildlist($curview) $childlist
        set vdisporder($curview) $displayorder
        set vcmitlisted($curview) $commitlisted
        if {$phase ne {}} {
            set viewdata($curview) \
                [list $phase $rowidlist $rowoffsets $rowrangelist \
                     [flatten idrowranges] [flatten idinlist] \
-                    $rowlaidout $rowoptim $numcommits $linesegends]
+                    $rowlaidout $rowoptim $numcommits]
        } elseif {![info exists viewdata($curview)]
                  || [lindex $viewdata($curview) 0] ne {}} {
            set viewdata($curview) \
                [list {} $rowidlist $rowoffsets $rowrangelist]
        }
     }
-    catch {unset matchinglines}
     catch {unset treediffs}
     clear_display
     if {[info exists hlview] && $hlview == $n} {
@@ -1691,7 +1906,9 @@ proc showview {n} {
     .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
 
     if {![info exists viewdata($n)]} {
-       set pending_select $selid
+       if {$selid ne {}} {
+           set pending_select $selid
+       }
        getcommits
        return
     }
@@ -1700,7 +1917,6 @@ proc showview {n} {
     set phase [lindex $v 0]
     set displayorder $vdisporder($n)
     set parentlist $vparentlist($n)
-    set childlist $vchildlist($n)
     set commitlisted $vcmitlisted($n)
     set rowidlist [lindex $v 1]
     set rowoffsets [lindex $v 2]
@@ -1714,7 +1930,7 @@ proc showview {n} {
        set rowlaidout [lindex $v 6]
        set rowoptim [lindex $v 7]
        set numcommits [lindex $v 8]
-       set linesegends [lindex $v 9]
+       catch {unset rowchk}
     }
 
     catch {unset colormap}
@@ -1725,7 +1941,8 @@ proc showview {n} {
     set row 0
     setcanvscroll
     set yf 0
-    set row 0
+    set row {}
+    set selectfirst 0
     if {$selid ne {} && [info exists commitrow($n,$selid)]} {
        set row $commitrow($n,$selid)
        # try to get the selected row in the same position on the screen
@@ -1738,16 +1955,23 @@ proc showview {n} {
     }
     allcanvs yview moveto $yf
     drawvisible
-    selectline $row 0
+    if {$row ne {}} {
+       selectline $row 0
+    } elseif {$selid ne {}} {
+       set pending_select $selid
+    } else {
+       set row [first_real_row]
+       if {$row < $numcommits} {
+           selectline $row 0
+       } else {
+           set selectfirst 1
+       }
+    }
     if {$phase ne {}} {
        if {$phase eq "getcommits"} {
            show_status "Reading commits..."
        }
-       if {[info exists commfd($n)]} {
-           layoutmore {}
-       } else {
-           finishcommits
-       }
+       run chewcommits $n
     } elseif {$numcommits == 0} {
        show_status "No commits selected"
     }
@@ -1825,7 +2049,6 @@ proc addvhighlight {n} {
     if {$n != $curview && ![info exists viewdata($n)]} {
        set viewdata($n) [list getcommits {{}} {{}} {} {} {} 0 0 0 {}]
        set vparentlist($n) {}
-       set vchildlist($n) {}
        set vdisporder($n) {}
        set vcmitlisted($n) {}
        start_rev_list $n
@@ -1935,7 +2158,7 @@ proc do_file_hl {serial} {
     set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
     set filehighlight [open $cmd r+]
     fconfigure $filehighlight -blocking 0
-    fileevent $filehighlight readable readfhighlight
+    filerun $filehighlight readfhighlight
     set fhl_list {}
     drawvisible
     flushhighlights
@@ -1963,7 +2186,11 @@ proc readfhighlight {} {
     global filehighlight fhighlights commitrow curview mainfont iddrawn
     global fhl_list
 
-    while {[gets $filehighlight line] >= 0} {
+    if {![info exists filehighlight]} {
+       return 0
+    }
+    set nr 0
+    while {[incr nr] <= 100 && [gets $filehighlight line] >= 0} {
        set line [string trim $line]
        set i [lsearch -exact $fhl_list $line]
        if {$i < 0} continue
@@ -1987,8 +2214,10 @@ proc readfhighlight {} {
        puts "oops, git diff-tree died"
        catch {close $filehighlight}
        unset filehighlight
+       return 0
     }
     next_hlcont
+    return 1
 }
 
 proc find_change {name ix op} {
@@ -2002,6 +2231,7 @@ proc find_change {name ix op} {
     set boldnamerows {}
     catch {unset nhighlights}
     unbolden
+    unmarkmatches
     if {$findtype ne "Regexp"} {
        set e [string map {"*" "\\*" "?" "\\?" "\[" "\\\[" "\\" "\\\\"} \
                   $findstring]
@@ -2010,9 +2240,22 @@ proc find_change {name ix op} {
     drawvisible
 }
 
+proc doesmatch {f} {
+    global findtype findstring findpattern
+
+    if {$findtype eq "Regexp"} {
+       return [regexp $findstring $f]
+    } elseif {$findtype eq "IgnCase"} {
+       return [string match -nocase $findpattern $f]
+    } else {
+       return [string match $findpattern $f]
+    }
+}
+
 proc askfindhighlight {row id} {
     global nhighlights commitinfo iddrawn mainfont
-    global findstring findtype findloc findpattern
+    global findloc
+    global markingmatches
 
     if {![info exists commitinfo($id)]} {
        getcommit $id
@@ -2021,41 +2264,59 @@ proc askfindhighlight {row id} {
     set isbold 0
     set fldtypes {Headline Author Date Committer CDate Comments}
     foreach f $info ty $fldtypes {
-       if {$findloc ne "All fields" && $findloc ne $ty} {
-           continue
-       }
-       if {$findtype eq "Regexp"} {
-           set doesmatch [regexp $findstring $f]
-       } elseif {$findtype eq "IgnCase"} {
-           set doesmatch [string match -nocase $findpattern $f]
-       } else {
-           set doesmatch [string match $findpattern $f]
-       }
-       if {$doesmatch} {
+       if {($findloc eq "All fields" || $findloc eq $ty) &&
+           [doesmatch $f]} {
            if {$ty eq "Author"} {
                set isbold 2
-           } else {
-               set isbold 1
+               break
            }
+           set isbold 1
        }
     }
-    if {[info exists iddrawn($id)]} {
-       if {$isbold && ![ishighlighted $row]} {
-           bolden $row [concat $mainfont bold]
+    if {$isbold && [info exists iddrawn($id)]} {
+       set f [concat $mainfont bold]
+       if {![ishighlighted $row]} {
+           bolden $row $f
+           if {$isbold > 1} {
+               bolden_name $row $f
+           }
        }
-       if {$isbold >= 2} {
-           bolden_name $row [concat $mainfont bold]
+       if {$markingmatches} {
+           markrowmatches $row $id
        }
     }
     set nhighlights($row) $isbold
 }
 
+proc markrowmatches {row id} {
+    global canv canv2 linehtag linentag commitinfo findloc
+
+    set headline [lindex $commitinfo($id) 0]
+    set author [lindex $commitinfo($id) 1]
+    $canv delete match$row
+    $canv2 delete match$row
+    if {$findloc eq "All fields" || $findloc eq "Headline"} {
+       set m [findmatches $headline]
+       if {$m ne {}} {
+           markmatches $canv $row $headline $linehtag($row) $m \
+               [$canv itemcget $linehtag($row) -font] $row
+       }
+    }
+    if {$findloc eq "All fields" || $findloc eq "Author"} {
+       set m [findmatches $author]
+       if {$m ne {}} {
+           markmatches $canv2 $row $author $linentag($row) $m \
+               [$canv2 itemcget $linentag($row) -font] $row
+       }
+    }
+}
+
 proc vrel_change {name ix op} {
     global highlight_related
 
     rhighlight_none
     if {$highlight_related ne "None"} {
-       after idle drawvisible
+       run drawvisible
     }
 }
 
@@ -2070,7 +2331,7 @@ proc rhighlight_sel {a} {
     set anc_todo [list $a]
     if {$highlight_related ne "None"} {
        rhighlight_none
-       after idle drawvisible
+       run drawvisible
     }
 }
 
@@ -2288,17 +2549,15 @@ proc ntimes {n o} {
 }
 
 proc usedinrange {id l1 l2} {
-    global children commitrow childlist curview
+    global children commitrow curview
 
     if {[info exists commitrow($curview,$id)]} {
        set r $commitrow($curview,$id)
        if {$l1 <= $r && $r <= $l2} {
            return [expr {$r - $l1 + 1}]
        }
-       set kids [lindex $childlist $r]
-    } else {
-       set kids $children($curview,$id)
     }
+    set kids $children($curview,$id)
     foreach c $kids {
        set r $commitrow($curview,$c)
        if {$l1 <= $r && $r <= $l2} {
@@ -2341,7 +2600,7 @@ proc sanity {row {full 0}} {
 }
 
 proc makeuparrow {oid x y z} {
-    global rowidlist rowoffsets uparrowlen idrowranges
+    global rowidlist rowoffsets uparrowlen idrowranges displayorder
 
     for {set i 1} {$i < $uparrowlen && $y > 1} {incr i} {
        incr y -1
@@ -2364,7 +2623,7 @@ proc makeuparrow {oid x y z} {
     }
     set tmp [lreplace [lindex $rowoffsets $y] $x $x {}]
     lset rowoffsets $y [incrange $tmp [expr {$x+1}] -1]
-    lappend idrowranges($oid) $y
+    lappend idrowranges($oid) [lindex $displayorder $y]
 }
 
 proc initlayout {} {
@@ -2373,15 +2632,14 @@ proc initlayout {} {
     global idinlist rowchk rowrangelist idrowranges
     global numcommits canvxmax canv
     global nextcolor
-    global parentlist childlist children
+    global parentlist
     global colormap rowtextx
-    global linesegends
+    global selectfirst
 
     set numcommits 0
     set displayorder {}
     set commitlisted {}
     set parentlist {}
-    set childlist {}
     set rowrangelist {}
     set nextcolor 0
     set rowidlist {{}}
@@ -2394,7 +2652,7 @@ proc initlayout {} {
     catch {unset colormap}
     catch {unset rowtextx}
     catch {unset idrowranges}
-    set linesegends {}
+    set selectfirst 1
 }
 
 proc setcanvscroll {} {
@@ -2425,15 +2683,18 @@ proc visiblerows {} {
     return [list $r0 $r1]
 }
 
-proc layoutmore {tmax} {
+proc layoutmore {tmax allread} {
     global rowlaidout rowoptim commitidx numcommits optim_delay
-    global uparrowlen curview
+    global uparrowlen curview rowidlist idinlist
 
+    set showlast 0
+    set showdelay $optim_delay
+    set optdelay [expr {$uparrowlen + 1}]
     while {1} {
-       if {$rowoptim - $optim_delay > $numcommits} {
-           showstuff [expr {$rowoptim - $optim_delay}]
-       } elseif {$rowlaidout - $uparrowlen - 1 > $rowoptim} {
-           set nr [expr {$rowlaidout - $uparrowlen - 1 - $rowoptim}]
+       if {$rowoptim - $showdelay > $numcommits} {
+           showstuff [expr {$rowoptim - $showdelay}] $showlast
+       } elseif {$rowlaidout - $optdelay > $rowoptim} {
+           set nr [expr {$rowlaidout - $optdelay - $rowoptim}]
            if {$nr > 100} {
                set nr 100
            }
@@ -2447,10 +2708,24 @@ proc layoutmore {tmax} {
                set nr 150
            }
            set row $rowlaidout
-           set rowlaidout [layoutrows $row [expr {$row + $nr}] 0]
+           set rowlaidout [layoutrows $row [expr {$row + $nr}] $allread]
            if {$rowlaidout == $row} {
                return 0
            }
+       } elseif {$allread} {
+           set optdelay 0
+           set nrows $commitidx($curview)
+           if {[lindex $rowidlist $nrows] ne {} ||
+               [array names idinlist] ne {}} {
+               layouttail
+               set rowlaidout $commitidx($curview)
+           } elseif {$rowoptim == $nrows} {
+               set showdelay 0
+               set showlast 1
+               if {$numcommits == $nrows} {
+                   return 0
+               }
+           }
        } else {
            return 0
        }
@@ -2460,57 +2735,161 @@ proc layoutmore {tmax} {
     }
 }
 
-proc showstuff {canshow} {
-    global numcommits commitrow pending_select selectedline
-    global linesegends idrowranges idrangedrawn curview
+proc showstuff {canshow last} {
+    global numcommits commitrow pending_select selectedline curview
+    global lookingforhead mainheadid displayorder selectfirst
+    global lastscrollset
 
     if {$numcommits == 0} {
        global phase
        set phase "incrdraw"
        allcanvs delete all
     }
-    set row $numcommits
+    set r0 $numcommits
+    set prev $numcommits
     set numcommits $canshow
-    setcanvscroll
+    set t [clock clicks -milliseconds]
+    if {$prev < 100 || $last || $t - $lastscrollset > 500} {
+       set lastscrollset $t
+       setcanvscroll
+    }
     set rows [visiblerows]
-    set r0 [lindex $rows 0]
     set r1 [lindex $rows 1]
-    set selrow -1
-    for {set r $row} {$r < $canshow} {incr r} {
-       foreach id [lindex $linesegends [expr {$r+1}]] {
-           set i -1
-           foreach {s e} [rowranges $id] {
-               incr i
-               if {$e ne {} && $e < $numcommits && $s <= $r1 && $e >= $r0
-                   && ![info exists idrangedrawn($id,$i)]} {
-                   drawlineseg $id $i
-                   set idrangedrawn($id,$i) 1
-               }
-           }
-       }
+    if {$r1 >= $canshow} {
+       set r1 [expr {$canshow - 1}]
     }
-    if {$canshow > $r1} {
-       set canshow $r1
-    }
-    while {$row < $canshow} {
-       drawcmitrow $row
-       incr row
+    if {$r0 <= $r1} {
+       drawcommits $r0 $r1
     }
     if {[info exists pending_select] &&
        [info exists commitrow($curview,$pending_select)] &&
        $commitrow($curview,$pending_select) < $numcommits} {
        selectline $commitrow($curview,$pending_select) 1
     }
-    if {![info exists selectedline] && ![info exists pending_select]} {
-       selectline 0 1
+    if {$selectfirst} {
+       if {[info exists selectedline] || [info exists pending_select]} {
+           set selectfirst 0
+       } else {
+           set l [first_real_row]
+           selectline $l 1
+           set selectfirst 0
+       }
+    }
+    if {$lookingforhead && [info exists commitrow($curview,$mainheadid)]
+       && ($last || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+       set lookingforhead 0
+       dodiffindex
+    }
+}
+
+proc doshowlocalchanges {} {
+    global lookingforhead curview mainheadid phase commitrow
+
+    if {[info exists commitrow($curview,$mainheadid)] &&
+       ($phase eq {} || $commitrow($curview,$mainheadid) < $numcommits - 1)} {
+       dodiffindex
+    } elseif {$phase ne {}} {
+       set lookingforhead 1
+    }
+}
+
+proc dohidelocalchanges {} {
+    global lookingforhead localfrow localirow lserial
+
+    set lookingforhead 0
+    if {$localfrow >= 0} {
+       removerow $localfrow
+       set localfrow -1
+       if {$localirow > 0} {
+           incr localirow -1
+       }
+    }
+    if {$localirow >= 0} {
+       removerow $localirow
+       set localirow -1
+    }
+    incr lserial
+}
+
+# spawn off a process to do git diff-index --cached HEAD
+proc dodiffindex {} {
+    global localirow localfrow lserial
+
+    incr lserial
+    set localfrow -1
+    set localirow -1
+    set fd [open "|git diff-index --cached HEAD" r]
+    fconfigure $fd -blocking 0
+    filerun $fd [list readdiffindex $fd $lserial]
+}
+
+proc readdiffindex {fd serial} {
+    global localirow commitrow mainheadid nullid2 curview
+    global commitinfo commitdata lserial
+
+    set isdiff 1
+    if {[gets $fd line] < 0} {
+       if {![eof $fd]} {
+           return 1
+       }
+       set isdiff 0
+    }
+    # we only need to see one line and we don't really care what it says...
+    close $fd
+
+    # now see if there are any local changes not checked in to the index
+    if {$serial == $lserial} {
+       set fd [open "|git diff-files" r]
+       fconfigure $fd -blocking 0
+       filerun $fd [list readdifffiles $fd $serial]
+    }
+
+    if {$isdiff && $serial == $lserial && $localirow == -1} {
+       # add the line for the changes in the index to the graph
+       set localirow $commitrow($curview,$mainheadid)
+       set hl "Local changes checked in to index but not committed"
+       set commitinfo($nullid2) [list  $hl {} {} {} {} "    $hl\n"]
+       set commitdata($nullid2) "\n    $hl\n"
+       insertrow $localirow $nullid2
+    }
+    return 0
+}
+
+proc readdifffiles {fd serial} {
+    global localirow localfrow commitrow mainheadid nullid curview
+    global commitinfo commitdata lserial
+
+    set isdiff 1
+    if {[gets $fd line] < 0} {
+       if {![eof $fd]} {
+           return 1
+       }
+       set isdiff 0
+    }
+    # we only need to see one line and we don't really care what it says...
+    close $fd
+
+    if {$isdiff && $serial == $lserial && $localfrow == -1} {
+       # add the line for the local diff to the graph
+       if {$localirow >= 0} {
+           set localfrow $localirow
+           incr localirow
+       } else {
+           set localfrow $commitrow($curview,$mainheadid)
+       }
+       set hl "Local uncommitted changes, not checked in to index"
+       set commitinfo($nullid) [list  $hl {} {} {} {} "    $hl\n"]
+       set commitdata($nullid) "\n    $hl\n"
+       insertrow $localfrow $nullid
     }
+    return 0
 }
 
 proc layoutrows {row endrow last} {
     global rowidlist rowoffsets displayorder
     global uparrowlen downarrowlen maxwidth mingaplen
-    global childlist parentlist
-    global idrowranges linesegends
+    global children parentlist
+    global idrowranges
     global commitidx curview
     global idinlist rowchk rowrangelist
 
@@ -2518,18 +2897,12 @@ proc layoutrows {row endrow last} {
     set offs [lindex $rowoffsets $row]
     while {$row < $endrow} {
        set id [lindex $displayorder $row]
-       set oldolds {}
-       set newolds {}
+       set nev [expr {[llength $idlist] - $maxwidth + 1}]
        foreach p [lindex $parentlist $row] {
-           if {![info exists idinlist($p)]} {
-               lappend newolds $p
-           } elseif {!$idinlist($p)} {
-               lappend oldolds $p
+           if {![info exists idinlist($p)] || !$idinlist($p)} {
+               incr nev
            }
        }
-       set lse {}
-       set nev [expr {[llength $idlist] + [llength $newolds]
-                      + [llength $oldolds] - $maxwidth + 1}]
        if {$nev > 0} {
            if {!$last &&
                $row + $uparrowlen + $mingaplen >= $commitidx($curview)} break
@@ -2544,25 +2917,33 @@ proc layoutrows {row endrow last} {
                        set offs [incrange $offs $x 1]
                        set idinlist($i) 0
                        set rm1 [expr {$row - 1}]
-                       lappend lse $i
-                       lappend idrowranges($i) $rm1
+                       lappend idrowranges($i) [lindex $displayorder $rm1]
                        if {[incr nev -1] <= 0} break
                        continue
                    }
-                   set rowchk($id) [expr {$row + $r}]
+                   set rowchk($i) [expr {$row + $r}]
                }
            }
            lset rowidlist $row $idlist
            lset rowoffsets $row $offs
        }
-       lappend linesegends $lse
+       set oldolds {}
+       set newolds {}
+       foreach p [lindex $parentlist $row] {
+           if {![info exists idinlist($p)]} {
+               lappend newolds $p
+           } elseif {!$idinlist($p)} {
+               lappend oldolds $p
+           }
+           set idinlist($p) 1
+       }
        set col [lsearch -exact $idlist $id]
        if {$col < 0} {
            set col [llength $idlist]
            lappend idlist $id
            lset rowidlist $row $idlist
            set z {}
-           if {[lindex $childlist $row] ne {}} {
+           if {$children($curview,$id) ne {}} {
                set z [expr {[llength [lindex $rowidlist [expr {$row-1}]]] - $col}]
                unset idinlist($id)
            }
@@ -2577,7 +2958,7 @@ proc layoutrows {row endrow last} {
        set ranges {}
        if {[info exists idrowranges($id)]} {
            set ranges $idrowranges($id)
-           lappend ranges $row
+           lappend ranges $id
            unset idrowranges($id)
        }
        lappend rowrangelist $ranges
@@ -2601,12 +2982,10 @@ proc layoutrows {row endrow last} {
            lset offs $col {}
        }
        foreach i $newolds {
-           set idinlist($i) 1
-           set idrowranges($i) $row
+           set idrowranges($i) $id
        }
        incr col $l
        foreach oid $oldolds {
-           set idinlist($oid) 1
            set idlist [linsert $idlist $col $oid]
            set offs [linsert $offs $col $o]
            makeuparrow $oid $col $row $o
@@ -2621,7 +3000,7 @@ proc layoutrows {row endrow last} {
 proc addextraid {id row} {
     global displayorder commitrow commitinfo
     global commitidx commitlisted
-    global parentlist childlist children curview
+    global parentlist children curview
 
     incr commitidx($curview)
     lappend displayorder $id
@@ -2635,7 +3014,6 @@ proc addextraid {id row} {
     if {![info exists children($curview,$id)]} {
        set children($curview,$id) {}
     }
-    lappend childlist $children($curview,$id)
 }
 
 proc layouttail {} {
@@ -2648,8 +3026,8 @@ proc layouttail {} {
        set col [expr {[llength $idlist] - 1}]
        set id [lindex $idlist $col]
        addextraid $id $row
-       unset idinlist($id)
-       lappend idrowranges($id) $row
+       catch {unset idinlist($id)}
+       lappend idrowranges($id) $id
        lappend rowrangelist $idrowranges($id)
        unset idrowranges($id)
        incr row
@@ -2660,11 +3038,12 @@ proc layouttail {} {
     }
 
     foreach id [array names idinlist] {
+       unset idinlist($id)
        addextraid $id $row
        lset rowidlist $row [list $id]
        lset rowoffsets $row 0
        makeuparrow $id 0 $row 0
-       lappend idrowranges($id) $row
+       lappend idrowranges($id) $id
        lappend rowrangelist $idrowranges($id)
        unset idrowranges($id)
        incr row
@@ -2683,7 +3062,7 @@ proc insert_pad {row col npad} {
 }
 
 proc optimize_rows {row col endrow} {
-    global rowidlist rowoffsets idrowranges displayorder
+    global rowidlist rowoffsets displayorder
 
     for {} {$row < $endrow} {incr row} {
        set idlist [lindex $rowidlist $row]
@@ -2707,7 +3086,13 @@ proc optimize_rows {row col endrow} {
                    set isarrow 1
                }
            }
+           # Looking at lines from this row to the previous row,
+           # make them go straight up if they end in an arrow on
+           # the previous row; otherwise make them go straight up
+           # or at 45 degrees.
            if {$z < -1 || ($z < 0 && $isarrow)} {
+               # Line currently goes left too much;
+               # insert pads in the previous row, then optimize it
                set npad [expr {-1 - $z + $isarrow}]
                set offs [incrange $offs $col $npad]
                insert_pad $y0 $x0 $npad
@@ -2718,6 +3103,8 @@ proc optimize_rows {row col endrow} {
                set x0 [expr {$col + $z}]
                set z0 [lindex $rowoffsets $y0 $x0]
            } elseif {$z > 1 || ($z > 0 && $isarrow)} {
+               # Line currently goes right too much;
+               # insert pads in this line and adjust the next's rowoffsets
                set npad [expr {$z - 1 + $isarrow}]
                set y1 [expr {$row + 1}]
                set offs2 [lindex $rowoffsets $y1]
@@ -2748,6 +3135,7 @@ proc optimize_rows {row col endrow} {
                    set z0 [expr {$xc - $x0}]
                }
            }
+           # avoid lines jigging left then immediately right
            if {$z0 ne {} && $z < 0 && $z0 > 0} {
                insert_pad $y0 $x0 1
                set offs [incrange $offs $col 1]
@@ -2756,6 +3144,7 @@ proc optimize_rows {row col endrow} {
        }
        if {!$haspad} {
            set o {}
+           # Find the first column that doesn't have a line going right
            for {set col [llength $idlist]} {[incr col -1] >= 0} {} {
                set o [lindex $offs $col]
                if {$o eq {}} {
@@ -2774,6 +3163,8 @@ proc optimize_rows {row col endrow} {
                }
                if {$o eq {} || $o <= 0} break
            }
+           # Insert a pad at that column as long as it has a line and
+           # isn't the last column, and adjust the next row' offsets
            if {$o ne {} && [incr col] < [llength $idlist]} {
                set y1 [expr {$row + 1}]
                set offs2 [lindex $rowoffsets $y1]
@@ -2827,124 +3218,239 @@ proc rowranges {id} {
     } elseif {[info exists idrowranges($id)]} {
        set ranges $idrowranges($id)
     }
-    return $ranges
+    set linenos {}
+    foreach rid $ranges {
+       lappend linenos $commitrow($curview,$rid)
+    }
+    if {$linenos ne {}} {
+       lset linenos 0 [expr {[lindex $linenos 0] + 1}]
+    }
+    return $linenos
 }
 
-proc drawlineseg {id i} {
-    global rowoffsets rowidlist
-    global displayorder
-    global canv colormap linespc
-    global numcommits commitrow curview
+# work around tk8.4 refusal to draw arrows on diagonal segments
+proc adjarrowhigh {coords} {
+    global linespc
 
-    set ranges [rowranges $id]
-    set downarrow 1
-    if {[info exists commitrow($curview,$id)]
-       && $commitrow($curview,$id) < $numcommits} {
-       set downarrow [expr {$i < [llength $ranges] / 2 - 1}]
-    } else {
-       set downarrow 1
-    }
-    set startrow [lindex $ranges [expr {2 * $i}]]
-    set row [lindex $ranges [expr {2 * $i + 1}]]
-    if {$startrow == $row} return
-    assigncolor $id
-    set coords {}
-    set col [lsearch -exact [lindex $rowidlist $row] $id]
-    if {$col < 0} {
-       puts "oops: drawline: id $id not on row $row"
-       return
-    }
-    set lasto {}
-    set ns 0
-    while {1} {
-       set o [lindex $rowoffsets $row $col]
-       if {$o eq {}} break
-       if {$o ne $lasto} {
-           # changing direction
-           set x [xc $row $col]
-           set y [yc $row]
-           lappend coords $x $y
-           set lasto $o
-       }
-       incr col $o
-       incr row -1
-    }
-    set x [xc $row $col]
-    set y [yc $row]
-    lappend coords $x $y
-    if {$i == 0} {
-       # draw the link to the first child as part of this line
-       incr row -1
-       set child [lindex $displayorder $row]
-       set ccol [lsearch -exact [lindex $rowidlist $row] $child]
-       if {$ccol >= 0} {
-           set x [xc $row $ccol]
-           set y [yc $row]
-           if {$ccol < $col - 1} {
-               lappend coords [xc $row [expr {$col - 1}]] [yc $row]
-           } elseif {$ccol > $col + 1} {
-               lappend coords [xc $row [expr {$col + 1}]] [yc $row]
-           }
-           lappend coords $x $y
-       }
-    }
-    if {[llength $coords] < 4} return
-    if {$downarrow} {
-       # This line has an arrow at the lower end: check if the arrow is
-       # on a diagonal segment, and if so, work around the Tk 8.4
-       # refusal to draw arrows on diagonal lines.
-       set x0 [lindex $coords 0]
-       set x1 [lindex $coords 2]
-       if {$x0 != $x1} {
-           set y0 [lindex $coords 1]
-           set y1 [lindex $coords 3]
-           if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
-               # we have a nearby vertical segment, just trim off the diag bit
-               set coords [lrange $coords 2 end]
-           } else {
-               set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
-               set xi [expr {$x0 - $slope * $linespc / 2}]
-               set yi [expr {$y0 - $linespc / 2}]
-               set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
-           }
+    set x0 [lindex $coords 0]
+    set x1 [lindex $coords 2]
+    if {$x0 != $x1} {
+       set y0 [lindex $coords 1]
+       set y1 [lindex $coords 3]
+       if {$y0 - $y1 <= 2 * $linespc && $x1 == [lindex $coords 4]} {
+           # we have a nearby vertical segment, just trim off the diag bit
+           set coords [lrange $coords 2 end]
+       } else {
+           set slope [expr {($x0 - $x1) / ($y0 - $y1)}]
+           set xi [expr {$x0 - $slope * $linespc / 2}]
+           set yi [expr {$y0 - $linespc / 2}]
+           set coords [lreplace $coords 0 1 $xi $y0 $xi $yi]
        }
     }
-    set arrow [expr {2 * ($i > 0) + $downarrow}]
-    set arrow [lindex {none first last both} $arrow]
-    set t [$canv create line $coords -width [linewidth $id] \
-              -fill $colormap($id) -tags lines.$id -arrow $arrow]
-    $canv lower $t
-    bindline $t $id
+    return $coords
 }
 
-proc drawparentlinks {id row col olds} {
-    global rowidlist canv colormap
+proc drawlineseg {id row endrow arrowlow} {
+    global rowidlist displayorder iddrawn linesegs
+    global canv colormap linespc curview maxlinelen
 
-    set row2 [expr {$row + 1}]
-    set x [xc $row $col]
-    set y [yc $row]
-    set y2 [yc $row2]
-    set ids [lindex $rowidlist $row2]
-    # rmx = right-most X coord used
-    set rmx 0
-    foreach p $olds {
-       set i [lsearch -exact $ids $p]
-       if {$i < 0} {
-           puts "oops, parent $p of $id not in list"
-           continue
+    set cols [list [lsearch -exact [lindex $rowidlist $row] $id]]
+    set le [expr {$row + 1}]
+    set arrowhigh 1
+    while {1} {
+       set c [lsearch -exact [lindex $rowidlist $le] $id]
+       if {$c < 0} {
+           incr le -1
+           break
        }
-       set x2 [xc $row2 $i]
-       if {$x2 > $rmx} {
-           set rmx $x2
+       lappend cols $c
+       set x [lindex $displayorder $le]
+       if {$x eq $id} {
+           set arrowhigh 0
+           break
        }
-       set ranges [rowranges $p]
-       if {$ranges ne {} && $row2 == [lindex $ranges 0]
-           && $row2 < [lindex $ranges 1]} {
-           # drawlineseg will do this one for us
-           continue
+       if {[info exists iddrawn($x)] || $le == $endrow} {
+           set c [lsearch -exact [lindex $rowidlist [expr {$le+1}]] $id]
+           if {$c >= 0} {
+               lappend cols $c
+               set arrowhigh 0
+           }
+           break
        }
-       assigncolor $p
-       # should handle duplicated parents here...
+       incr le
+    }
+    if {$le <= $row} {
+       return $row
+    }
+
+    set lines {}
+    set i 0
+    set joinhigh 0
+    if {[info exists linesegs($id)]} {
+       set lines $linesegs($id)
+       foreach li $lines {
+           set r0 [lindex $li 0]
+           if {$r0 > $row} {
+               if {$r0 == $le && [lindex $li 1] - $row <= $maxlinelen} {
+                   set joinhigh 1
+               }
+               break
+           }
+           incr i
+       }
+    }
+    set joinlow 0
+    if {$i > 0} {
+       set li [lindex $lines [expr {$i-1}]]
+       set r1 [lindex $li 1]
+       if {$r1 == $row && $le - [lindex $li 0] <= $maxlinelen} {
+           set joinlow 1
+       }
+    }
+
+    set x [lindex $cols [expr {$le - $row}]]
+    set xp [lindex $cols [expr {$le - 1 - $row}]]
+    set dir [expr {$xp - $x}]
+    if {$joinhigh} {
+       set ith [lindex $lines $i 2]
+       set coords [$canv coords $ith]
+       set ah [$canv itemcget $ith -arrow]
+       set arrowhigh [expr {$ah eq "first" || $ah eq "both"}]
+       set x2 [lindex $cols [expr {$le + 1 - $row}]]
+       if {$x2 ne {} && $x - $x2 == $dir} {
+           set coords [lrange $coords 0 end-2]
+       }
+    } else {
+       set coords [list [xc $le $x] [yc $le]]
+    }
+    if {$joinlow} {
+       set itl [lindex $lines [expr {$i-1}] 2]
+       set al [$canv itemcget $itl -arrow]
+       set arrowlow [expr {$al eq "last" || $al eq "both"}]
+    } elseif {$arrowlow &&
+             [lsearch -exact [lindex $rowidlist [expr {$row-1}]] $id] >= 0} {
+       set arrowlow 0
+    }
+    set arrow [lindex {none first last both} [expr {$arrowhigh + 2*$arrowlow}]]
+    for {set y $le} {[incr y -1] > $row} {} {
+       set x $xp
+       set xp [lindex $cols [expr {$y - 1 - $row}]]
+       set ndir [expr {$xp - $x}]
+       if {$dir != $ndir || $xp < 0} {
+           lappend coords [xc $y $x] [yc $y]
+       }
+       set dir $ndir
+    }
+    if {!$joinlow} {
+       if {$xp < 0} {
+           # join parent line to first child
+           set ch [lindex $displayorder $row]
+           set xc [lsearch -exact [lindex $rowidlist $row] $ch]
+           if {$xc < 0} {
+               puts "oops: drawlineseg: child $ch not on row $row"
+           } else {
+               if {$xc < $x - 1} {
+                   lappend coords [xc $row [expr {$x-1}]] [yc $row]
+               } elseif {$xc > $x + 1} {
+                   lappend coords [xc $row [expr {$x+1}]] [yc $row]
+               }
+               set x $xc
+           }
+           lappend coords [xc $row $x] [yc $row]
+       } else {
+           set xn [xc $row $xp]
+           set yn [yc $row]
+           # work around tk8.4 refusal to draw arrows on diagonal segments
+           if {$arrowlow && $xn != [lindex $coords end-1]} {
+               if {[llength $coords] < 4 ||
+                   [lindex $coords end-3] != [lindex $coords end-1] ||
+                   [lindex $coords end] - $yn > 2 * $linespc} {
+                   set xn [xc $row [expr {$xp - 0.5 * $dir}]]
+                   set yo [yc [expr {$row + 0.5}]]
+                   lappend coords $xn $yo $xn $yn
+               }
+           } else {
+               lappend coords $xn $yn
+           }
+       }
+       if {!$joinhigh} {
+           if {$arrowhigh} {
+               set coords [adjarrowhigh $coords]
+           }
+           assigncolor $id
+           set t [$canv create line $coords -width [linewidth $id] \
+                      -fill $colormap($id) -tags lines.$id -arrow $arrow]
+           $canv lower $t
+           bindline $t $id
+           set lines [linsert $lines $i [list $row $le $t]]
+       } else {
+           $canv coords $ith $coords
+           if {$arrow ne $ah} {
+               $canv itemconf $ith -arrow $arrow
+           }
+           lset lines $i 0 $row
+       }
+    } else {
+       set xo [lsearch -exact [lindex $rowidlist [expr {$row - 1}]] $id]
+       set ndir [expr {$xo - $xp}]
+       set clow [$canv coords $itl]
+       if {$dir == $ndir} {
+           set clow [lrange $clow 2 end]
+       }
+       set coords [concat $coords $clow]
+       if {!$joinhigh} {
+           lset lines [expr {$i-1}] 1 $le
+           if {$arrowhigh} {
+               set coords [adjarrowhigh $coords]
+           }
+       } else {
+           # coalesce two pieces
+           $canv delete $ith
+           set b [lindex $lines [expr {$i-1}] 0]
+           set e [lindex $lines $i 1]
+           set lines [lreplace $lines [expr {$i-1}] $i [list $b $e $itl]]
+       }
+       $canv coords $itl $coords
+       if {$arrow ne $al} {
+           $canv itemconf $itl -arrow $arrow
+       }
+    }
+
+    set linesegs($id) $lines
+    return $le
+}
+
+proc drawparentlinks {id row} {
+    global rowidlist canv colormap curview parentlist
+    global idpos
+
+    set rowids [lindex $rowidlist $row]
+    set col [lsearch -exact $rowids $id]
+    if {$col < 0} return
+    set olds [lindex $parentlist $row]
+    set row2 [expr {$row + 1}]
+    set x [xc $row $col]
+    set y [yc $row]
+    set y2 [yc $row2]
+    set ids [lindex $rowidlist $row2]
+    # rmx = right-most X coord used
+    set rmx 0
+    foreach p $olds {
+       set i [lsearch -exact $ids $p]
+       if {$i < 0} {
+           puts "oops, parent $p of $id not in list"
+           continue
+       }
+       set x2 [xc $row2 $i]
+       if {$x2 > $rmx} {
+           set rmx $x2
+       }
+       if {[lsearch -exact $rowids $p] < 0} {
+           # drawlineseg will do this one for us
+           continue
+       }
+       assigncolor $p
+       # should handle duplicated parents here...
        set coords [list $x $y]
        if {$i < $col - 1} {
            lappend coords [xc $row [expr {$i + 1}]] $y
@@ -2957,52 +3463,70 @@ proc drawparentlinks {id row col olds} {
        $canv lower $t
        bindline $t $p
     }
-    return $rmx
+    if {$rmx > [lindex $idpos($id) 1]} {
+       lset idpos($id) 1 $rmx
+       redrawtags $id
+    }
 }
 
 proc drawlines {id} {
-    global colormap canv
-    global idrangedrawn
-    global children iddrawn commitrow rowidlist curview
+    global canv
 
-    $canv delete lines.$id
-    set nr [expr {[llength [rowranges $id]] / 2}]
-    for {set i 0} {$i < $nr} {incr i} {
-       if {[info exists idrangedrawn($id,$i)]} {
-           drawlineseg $id $i
-       }
-    }
-    foreach child $children($curview,$id) {
-       if {[info exists iddrawn($child)]} {
-           set row $commitrow($curview,$child)
-           set col [lsearch -exact [lindex $rowidlist $row] $child]
-           if {$col >= 0} {
-               drawparentlinks $child $row $col [list $id]
-           }
-       }
-    }
+    $canv itemconf lines.$id -width [linewidth $id]
 }
 
-proc drawcmittext {id row col rmx} {
-    global linespc canv canv2 canv3 canvy0 fgcolor
-    global commitlisted commitinfo rowidlist
+proc drawcmittext {id row col} {
+    global linespc canv canv2 canv3 canvy0 fgcolor curview
+    global commitlisted commitinfo rowidlist parentlist
     global rowtextx idpos idtags idheads idotherrefs
     global linehtag linentag linedtag
-    global mainfont canvxmax boldrows boldnamerows fgcolor
-
-    set ofill [expr {[lindex $commitlisted $row]? "blue": "white"}]
+    global mainfont canvxmax boldrows boldnamerows fgcolor nullid nullid2
+
+    # listed is 0 for boundary, 1 for normal, 2 for left, 3 for right
+    set listed [lindex $commitlisted $row]
+    if {$id eq $nullid} {
+       set ofill red
+    } elseif {$id eq $nullid2} {
+       set ofill green
+    } else {
+       set ofill [expr {$listed != 0? "blue": "white"}]
+    }
     set x [xc $row $col]
     set y [yc $row]
     set orad [expr {$linespc / 3}]
-    set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
-              [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
-              -fill $ofill -outline $fgcolor -width 1 -tags circle]
+    if {$listed <= 1} {
+       set t [$canv create oval [expr {$x - $orad}] [expr {$y - $orad}] \
+                  [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
+                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
+    } elseif {$listed == 2} {
+       # triangle pointing left for left-side commits
+       set t [$canv create polygon \
+                  [expr {$x - $orad}] $y \
+                  [expr {$x + $orad - 1}] [expr {$y - $orad}] \
+                  [expr {$x + $orad - 1}] [expr {$y + $orad - 1}] \
+                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
+    } else {
+       # triangle pointing right for right-side commits
+       set t [$canv create polygon \
+                  [expr {$x + $orad - 1}] $y \
+                  [expr {$x - $orad}] [expr {$y - $orad}] \
+                  [expr {$x - $orad}] [expr {$y + $orad - 1}] \
+                  -fill $ofill -outline $fgcolor -width 1 -tags circle]
+    }
     $canv raise $t
     $canv bind $t <1> {selcanvline {} %x %y}
-    set xt [xc $row [llength [lindex $rowidlist $row]]]
-    if {$xt < $rmx} {
-       set xt $rmx
+    set rmx [llength [lindex $rowidlist $row]]
+    set olds [lindex $parentlist $row]
+    if {$olds ne {}} {
+       set nextids [lindex $rowidlist [expr {$row + 1}]]
+       foreach p $olds {
+           set i [lsearch -exact $nextids $p]
+           if {$i > $rmx} {
+               set rmx $i
+           }
+       }
     }
+    set xt [xc $row $rmx]
     set rowtextx($row) $xt
     set idpos($id) [list $x $xt $y]
     if {[info exists idtags($id)] || [info exists idheads($id)]
@@ -3040,29 +3564,13 @@ proc drawcmittext {id row col rmx} {
 
 proc drawcmitrow {row} {
     global displayorder rowidlist
-    global idrangedrawn iddrawn
+    global iddrawn markingmatches
     global commitinfo parentlist numcommits
     global filehighlight fhighlights findstring nhighlights
     global hlview vhighlights
     global highlight_related rhighlights
 
     if {$row >= $numcommits} return
-    foreach id [lindex $rowidlist $row] {
-       if {$id eq {}} continue
-       set i -1
-       foreach {s e} [rowranges $id] {
-           incr i
-           if {$row < $s} continue
-           if {$e eq {}} break
-           if {$row <= $e} {
-               if {$e < $numcommits && ![info exists idrangedrawn($id,$i)]} {
-                   drawlineseg $id $i
-                   set idrangedrawn($id,$i) 1
-               }
-               break
-           }
-       }
-    }
 
     set id [lindex $displayorder $row]
     if {[info exists hlview] && ![info exists vhighlights($row)]} {
@@ -3077,45 +3585,97 @@ proc drawcmitrow {row} {
     if {$highlight_related ne "None" && ![info exists rhighlights($row)]} {
        askrelhighlight $row $id
     }
-    if {[info exists iddrawn($id)]} return
-    set col [lsearch -exact [lindex $rowidlist $row] $id]
-    if {$col < 0} {
-       puts "oops, row $row id $id not in list"
-       return
+    if {![info exists iddrawn($id)]} {
+       set col [lsearch -exact [lindex $rowidlist $row] $id]
+       if {$col < 0} {
+           puts "oops, row $row id $id not in list"
+           return
+       }
+       if {![info exists commitinfo($id)]} {
+           getcommit $id
+       }
+       assigncolor $id
+       drawcmittext $id $row $col
+       set iddrawn($id) 1
     }
-    if {![info exists commitinfo($id)]} {
-       getcommit $id
+    if {$markingmatches} {
+       markrowmatches $row $id
     }
-    assigncolor $id
-    set olds [lindex $parentlist $row]
-    if {$olds ne {}} {
-       set rmx [drawparentlinks $id $row $col $olds]
-    } else {
-       set rmx 0
+}
+
+proc drawcommits {row {endrow {}}} {
+    global numcommits iddrawn displayorder curview
+    global parentlist rowidlist
+
+    if {$row < 0} {
+       set row 0
+    }
+    if {$endrow eq {}} {
+       set endrow $row
+    }
+    if {$endrow >= $numcommits} {
+       set endrow [expr {$numcommits - 1}]
+    }
+
+    # make the lines join to already-drawn rows either side
+    set r [expr {$row - 1}]
+    if {$r < 0 || ![info exists iddrawn([lindex $displayorder $r])]} {
+       set r $row
+    }
+    set er [expr {$endrow + 1}]
+    if {$er >= $numcommits ||
+       ![info exists iddrawn([lindex $displayorder $er])]} {
+       set er $endrow
+    }
+    for {} {$r <= $er} {incr r} {
+       set id [lindex $displayorder $r]
+       set wasdrawn [info exists iddrawn($id)]
+       drawcmitrow $r
+       if {$r == $er} break
+       set nextid [lindex $displayorder [expr {$r + 1}]]
+       if {$wasdrawn && [info exists iddrawn($nextid)]} {
+           catch {unset prevlines}
+           continue
+       }
+       drawparentlinks $id $r
+
+       if {[info exists lineends($r)]} {
+           foreach lid $lineends($r) {
+               unset prevlines($lid)
+           }
+       }
+       set rowids [lindex $rowidlist $r]
+       foreach lid $rowids {
+           if {$lid eq {}} continue
+           if {$lid eq $id} {
+               # see if this is the first child of any of its parents
+               foreach p [lindex $parentlist $r] {
+                   if {[lsearch -exact $rowids $p] < 0} {
+                       # make this line extend up to the child
+                       set le [drawlineseg $p $r $er 0]
+                       lappend lineends($le) $p
+                       set prevlines($p) 1
+                   }
+               }
+           } elseif {![info exists prevlines($lid)]} {
+               set le [drawlineseg $lid $r $er 1]
+               lappend lineends($le) $lid
+               set prevlines($lid) 1
+           }
+       }
     }
-    drawcmittext $id $row $col $rmx
-    set iddrawn($id) 1
 }
 
 proc drawfrac {f0 f1} {
-    global numcommits canv
-    global linespc
+    global canv linespc
 
     set ymax [lindex [$canv cget -scrollregion] 3]
     if {$ymax eq {} || $ymax == 0} return
     set y0 [expr {int($f0 * $ymax)}]
     set row [expr {int(($y0 - 3) / $linespc) - 1}]
-    if {$row < 0} {
-       set row 0
-    }
     set y1 [expr {int($f1 * $ymax)}]
     set endrow [expr {int(($y1 - 3) / $linespc) + 1}]
-    if {$endrow >= $numcommits} {
-       set endrow [expr {$numcommits - 1}]
-    }
-    for {} {$row <= $endrow} {incr row} {
-       drawcmitrow $row
-    }
+    drawcommits $row $endrow
 }
 
 proc drawvisible {} {
@@ -3124,12 +3684,12 @@ proc drawvisible {} {
 }
 
 proc clear_display {} {
-    global iddrawn idrangedrawn
+    global iddrawn linesegs
     global vhighlights fhighlights nhighlights rhighlights
 
     allcanvs delete all
     catch {unset iddrawn}
-    catch {unset idrangedrawn}
+    catch {unset linesegs}
     catch {unset vhighlights}
     catch {unset fhighlights}
     catch {unset nhighlights}
@@ -3356,27 +3916,14 @@ proc show_status {msg} {
        -tags text -fill $fgcolor
 }
 
-proc finishcommits {} {
-    global commitidx phase curview
-    global pending_select
-
-    if {$commitidx($curview) > 0} {
-       drawrest
-    } else {
-       show_status "No commits selected"
-    }
-    set phase {}
-    catch {unset pending_select}
-}
-
 # Insert a new commit as the child of the commit on row $row.
 # The new commit will be displayed on row $row and the commits
 # on that row and below will move down one row.
 proc insertrow {row newcmit} {
-    global displayorder parentlist childlist commitlisted
+    global displayorder parentlist commitlisted children
     global commitrow curview rowidlist rowoffsets numcommits
-    global rowrangelist idrowranges rowlaidout rowoptim numcommits
-    global linesegends selectedline
+    global rowrangelist rowlaidout rowoptim numcommits
+    global selectedline rowchk commitidx
 
     if {$row >= $numcommits} {
        puts "oops, inserting new row $row but only have $numcommits rows"
@@ -3385,16 +3932,17 @@ proc insertrow {row newcmit} {
     set p [lindex $displayorder $row]
     set displayorder [linsert $displayorder $row $newcmit]
     set parentlist [linsert $parentlist $row $p]
-    set kids [lindex $childlist $row]
+    set kids $children($curview,$p)
     lappend kids $newcmit
-    lset childlist $row $kids
-    set childlist [linsert $childlist $row {}]
+    set children($curview,$p) $kids
+    set children($curview,$newcmit) {}
     set commitlisted [linsert $commitlisted $row 1]
     set l [llength $displayorder]
     for {set r $row} {$r < $l} {incr r} {
        set id [lindex $displayorder $r]
        set commitrow($curview,$id) $r
     }
+    incr commitidx($curview)
 
     set idlist [lindex $rowidlist $row]
     set offs [lindex $rowoffsets $row]
@@ -3419,47 +3967,18 @@ proc insertrow {row newcmit} {
     set rowoffsets [linsert $rowoffsets [expr {$row+1}] $newoffs]
 
     set rowrangelist [linsert $rowrangelist $row {}]
-    set l [llength $rowrangelist]
-    for {set r 0} {$r < $l} {incr r} {
-       set ranges [lindex $rowrangelist $r]
-       if {$ranges ne {} && [lindex $ranges end] >= $row} {
-           set newranges {}
-           foreach x $ranges {
-               if {$x >= $row} {
-                   lappend newranges [expr {$x + 1}]
-               } else {
-                   lappend newranges $x
-               }
-           }
-           lset rowrangelist $r $newranges
-       }
-    }
     if {[llength $kids] > 1} {
        set rp1 [expr {$row + 1}]
        set ranges [lindex $rowrangelist $rp1]
        if {$ranges eq {}} {
-           set ranges [list $row $rp1]
-       } elseif {[lindex $ranges end-1] == $rp1} {
-           lset ranges end-1 $row
+           set ranges [list $newcmit $p]
+       } elseif {[lindex $ranges end-1] eq $p} {
+           lset ranges end-1 $newcmit
        }
        lset rowrangelist $rp1 $ranges
     }
-    foreach id [array names idrowranges] {
-       set ranges $idrowranges($id)
-       if {$ranges ne {} && [lindex $ranges end] >= $row} {
-           set newranges {}
-           foreach x $ranges {
-               if {$x >= $row} {
-                   lappend newranges [expr {$x + 1}]
-               } else {
-                   lappend newranges $x
-               }
-           }
-           set idrowranges($id) $newranges
-       }
-    }
 
-    set linesegends [linsert $linesegends $row {}]
+    catch {unset rowchk}
 
     incr rowlaidout
     incr rowoptim
@@ -3471,6 +3990,65 @@ proc insertrow {row newcmit} {
     redisplay
 }
 
+# Remove a commit that was inserted with insertrow on row $row.
+proc removerow {row} {
+    global displayorder parentlist commitlisted children
+    global commitrow curview rowidlist rowoffsets numcommits
+    global rowrangelist idrowranges rowlaidout rowoptim numcommits
+    global linesegends selectedline rowchk commitidx
+
+    if {$row >= $numcommits} {
+       puts "oops, removing row $row but only have $numcommits rows"
+       return
+    }
+    set rp1 [expr {$row + 1}]
+    set id [lindex $displayorder $row]
+    set p [lindex $parentlist $row]
+    set displayorder [lreplace $displayorder $row $row]
+    set parentlist [lreplace $parentlist $row $row]
+    set commitlisted [lreplace $commitlisted $row $row]
+    set kids $children($curview,$p)
+    set i [lsearch -exact $kids $id]
+    if {$i >= 0} {
+       set kids [lreplace $kids $i $i]
+       set children($curview,$p) $kids
+    }
+    set l [llength $displayorder]
+    for {set r $row} {$r < $l} {incr r} {
+       set id [lindex $displayorder $r]
+       set commitrow($curview,$id) $r
+    }
+    incr commitidx($curview) -1
+
+    set rowidlist [lreplace $rowidlist $row $row]
+    set rowoffsets [lreplace $rowoffsets $rp1 $rp1]
+    if {$kids ne {}} {
+       set offs [lindex $rowoffsets $row]
+       set offs [lreplace $offs end end]
+       lset rowoffsets $row $offs
+    }
+
+    set rowrangelist [lreplace $rowrangelist $row $row]
+    if {[llength $kids] > 0} {
+       set ranges [lindex $rowrangelist $row]
+       if {[lindex $ranges end-1] eq $id} {
+           set ranges [lreplace $ranges end-1 end]
+           lset rowrangelist $row $ranges
+       }
+    }
+
+    catch {unset rowchk}
+
+    incr rowlaidout -1
+    incr rowoptim -1
+    incr numcommits -1
+
+    if {[info exists selectedline] && $selectedline > $row} {
+       incr selectedline -1
+    }
+    redisplay
+}
+
 # Don't change the text pane cursor if it is currently the hand cursor,
 # showing that we are over a sha1 ID link.
 proc settextcursor {c} {
@@ -3502,125 +4080,172 @@ proc notbusy {what} {
     }
 }
 
-proc drawrest {} {
-    global startmsecs
-    global rowlaidout commitidx curview
-    global pending_select
-
-    set row $rowlaidout
-    layoutrows $rowlaidout $commitidx($curview) 1
-    layouttail
-    optimize_rows $row 0 $commitidx($curview)
-    showstuff $commitidx($curview)
-    if {[info exists pending_select]} {
-       selectline 0 1
-    }
-
-    set drawmsecs [expr {[clock clicks -milliseconds] - $startmsecs}]
-    #global numcommits
-    #puts "overall $drawmsecs ms for $numcommits commits"
-}
-
 proc findmatches {f} {
-    global findtype foundstring foundstrlen
+    global findtype findstring
     if {$findtype == "Regexp"} {
-       set matches [regexp -indices -all -inline $foundstring $f]
+       set matches [regexp -indices -all -inline $findstring $f]
     } else {
+       set fs $findstring
        if {$findtype == "IgnCase"} {
-           set str [string tolower $f]
-       } else {
-           set str $f
+           set f [string tolower $f]
+           set fs [string tolower $fs]
        }
        set matches {}
        set i 0
-       while {[set j [string first $foundstring $str $i]] >= 0} {
-           lappend matches [list $j [expr {$j+$foundstrlen-1}]]
-           set i [expr {$j + $foundstrlen}]
+       set l [string length $fs]
+       while {[set j [string first $fs $f $i]] >= 0} {
+           lappend matches [list $j [expr {$j+$l-1}]]
+           set i [expr {$j + $l}]
        }
     }
     return $matches
 }
 
-proc dofind {} {
-    global findtype findloc findstring markedmatches commitinfo
-    global numcommits displayorder linehtag linentag linedtag
-    global mainfont canv canv2 canv3 selectedline
-    global matchinglines foundstring foundstrlen matchstring
-    global commitdata
+proc dofind {{rev 0}} {
+    global findstring findstartline findcurline selectedline numcommits
 
-    stopfindproc
     unmarkmatches
     cancel_next_highlight
     focus .
-    set matchinglines {}
-    if {$findtype == "IgnCase"} {
-       set foundstring [string tolower $findstring]
+    if {$findstring eq {} || $numcommits == 0} return
+    if {![info exists selectedline]} {
+       set findstartline [lindex [visiblerows] $rev]
     } else {
-       set foundstring $findstring
+       set findstartline $selectedline
     }
-    set foundstrlen [string length $findstring]
-    if {$foundstrlen == 0} return
-    regsub -all {[*?\[\\]} $foundstring {\\&} matchstring
-    set matchstring "*$matchstring*"
-    if {![info exists selectedline]} {
-       set oldsel -1
+    set findcurline $findstartline
+    nowbusy finding
+    if {!$rev} {
+       run findmore
     } else {
-       set oldsel $selectedline
+       if {$findcurline == 0} {
+           set findcurline $numcommits
+       }
+       incr findcurline -1
+       run findmorerev
     }
-    set didsel 0
-    set fldtypes {Headline Author Date Committer CDate Comments}
-    set l -1
-    foreach id $displayorder {
-       set d $commitdata($id)
-       incr l
-       if {$findtype == "Regexp"} {
-           set doesmatch [regexp $foundstring $d]
-       } elseif {$findtype == "IgnCase"} {
-           set doesmatch [string match -nocase $matchstring $d]
+}
+
+proc findnext {restart} {
+    global findcurline
+    if {![info exists findcurline]} {
+       if {$restart} {
+           dofind
        } else {
-           set doesmatch [string match $matchstring $d]
+           bell
        }
-       if {!$doesmatch} continue
+    } else {
+       run findmore
+       nowbusy finding
+    }
+}
+
+proc findprev {} {
+    global findcurline
+    if {![info exists findcurline]} {
+       dofind 1
+    } else {
+       run findmorerev
+       nowbusy finding
+    }
+}
+
+proc findmore {} {
+    global commitdata commitinfo numcommits findstring findpattern findloc
+    global findstartline findcurline displayorder
+
+    set fldtypes {Headline Author Date Committer CDate Comments}
+    set l [expr {$findcurline + 1}]
+    if {$l >= $numcommits} {
+       set l 0
+    }
+    if {$l <= $findstartline} {
+       set lim [expr {$findstartline + 1}]
+    } else {
+       set lim $numcommits
+    }
+    if {$lim - $l > 500} {
+       set lim [expr {$l + 500}]
+    }
+    set last 0
+    for {} {$l < $lim} {incr l} {
+       set id [lindex $displayorder $l]
+       # shouldn't happen unless git log doesn't give all the commits...
+       if {![info exists commitdata($id)]} continue
+       if {![doesmatch $commitdata($id)]} continue
        if {![info exists commitinfo($id)]} {
            getcommit $id
        }
        set info $commitinfo($id)
-       set doesmatch 0
        foreach f $info ty $fldtypes {
-           if {$findloc != "All fields" && $findloc != $ty} {
-               continue
+           if {($findloc eq "All fields" || $findloc eq $ty) &&
+               [doesmatch $f]} {
+               findselectline $l
+               notbusy finding
+               return 0
            }
-           set matches [findmatches $f]
-           if {$matches == {}} continue
-           set doesmatch 1
-           if {$ty == "Headline"} {
-               drawcmitrow $l
-               markmatches $canv $l $f $linehtag($l) $matches $mainfont
-           } elseif {$ty == "Author"} {
-               drawcmitrow $l
-               markmatches $canv2 $l $f $linentag($l) $matches $mainfont
-           } elseif {$ty == "Date"} {
-               drawcmitrow $l
-               markmatches $canv3 $l $f $linedtag($l) $matches $mainfont
-           }
-       }
-       if {$doesmatch} {
-           lappend matchinglines $l
-           if {!$didsel && $l > $oldsel} {
+       }
+    }
+    if {$l == $findstartline + 1} {
+       bell
+       unset findcurline
+       notbusy finding
+       return 0
+    }
+    set findcurline [expr {$l - 1}]
+    return 1
+}
+
+proc findmorerev {} {
+    global commitdata commitinfo numcommits findstring findpattern findloc
+    global findstartline findcurline displayorder
+
+    set fldtypes {Headline Author Date Committer CDate Comments}
+    set l $findcurline
+    if {$l == 0} {
+       set l $numcommits
+    }
+    incr l -1
+    if {$l >= $findstartline} {
+       set lim [expr {$findstartline - 1}]
+    } else {
+       set lim -1
+    }
+    if {$l - $lim > 500} {
+       set lim [expr {$l - 500}]
+    }
+    set last 0
+    for {} {$l > $lim} {incr l -1} {
+       set id [lindex $displayorder $l]
+       if {![doesmatch $commitdata($id)]} continue
+       if {![info exists commitinfo($id)]} {
+           getcommit $id
+       }
+       set info $commitinfo($id)
+       foreach f $info ty $fldtypes {
+           if {($findloc eq "All fields" || $findloc eq $ty) &&
+               [doesmatch $f]} {
                findselectline $l
-               set didsel 1
+               notbusy finding
+               return 0
            }
        }
     }
-    if {$matchinglines == {}} {
+    if {$l == -1} {
        bell
-    } elseif {!$didsel} {
-       findselectline [lindex $matchinglines 0]
+       unset findcurline
+       notbusy finding
+       return 0
     }
+    set findcurline [expr {$l + 1}]
+    return 1
 }
 
 proc findselectline {l} {
-    global findloc commentend ctext
+    global findloc commentend ctext findcurline markingmatches
+
+    set markingmatches 1
+    set findcurline $l
     selectline $l 1
     if {$findloc == "All fields" || $findloc == "Comments"} {
        # highlight the matches in the comments
@@ -3632,75 +4257,13 @@ proc findselectline {l} {
            $ctext tag add found "1.0 + $start c" "1.0 + $end c"
        }
     }
+    drawvisible
 }
 
-proc findnext {restart} {
-    global matchinglines selectedline
-    if {![info exists matchinglines]} {
-       if {$restart} {
-           dofind
-       }
-       return
-    }
-    if {![info exists selectedline]} return
-    foreach l $matchinglines {
-       if {$l > $selectedline} {
-           findselectline $l
-           return
-       }
-    }
-    bell
-}
-
-proc findprev {} {
-    global matchinglines selectedline
-    if {![info exists matchinglines]} {
-       dofind
-       return
-    }
-    if {![info exists selectedline]} return
-    set prev {}
-    foreach l $matchinglines {
-       if {$l >= $selectedline} break
-       set prev $l
-    }
-    if {$prev != {}} {
-       findselectline $prev
-    } else {
-       bell
-    }
-}
-
-proc stopfindproc {{done 0}} {
-    global findprocpid findprocfile findids
-    global ctext findoldcursor phase maincursor textcursor
-    global findinprogress
-
-    catch {unset findids}
-    if {[info exists findprocpid]} {
-       if {!$done} {
-           catch {exec kill $findprocpid}
-       }
-       catch {close $findprocfile}
-       unset findprocpid
-    }
-    catch {unset findinprogress}
-    notbusy find
-}
-
-# mark a commit as matching by putting a yellow background
-# behind the headline
-proc markheadline {l id} {
-    global canv mainfont linehtag
-
-    drawcmitrow $l
-    set bbox [$canv bbox $linehtag($l)]
-    set t [$canv create rect $bbox -outline {} -tags matches -fill yellow]
-    $canv lower $t
-}
+# mark the bits of a headline or author that match a find string
+proc markmatches {canv l str tag matches font row} {
+    global selectedline
 
-# mark the bits of a headline, author or date that match a find string
-proc markmatches {canv l str tag matches font} {
     set bbox [$canv bbox $tag]
     set x0 [lindex $bbox 0]
     set y0 [lindex $bbox 1]
@@ -3713,16 +4276,21 @@ proc markmatches {canv l str tag matches font} {
        set xlen [font measure $font [string range $str 0 [expr {$end}]]]
        set t [$canv create rect [expr {$x0+$xoff}] $y0 \
                   [expr {$x0+$xlen+2}] $y1 \
-                  -outline {} -tags matches -fill yellow]
+                  -outline {} -tags [list match$l matches] -fill yellow]
        $canv lower $t
+       if {[info exists selectedline] && $row == $selectedline} {
+           $canv raise $t secsel
+       }
     }
 }
 
 proc unmarkmatches {} {
-    global matchinglines findids
+    global findids markingmatches findcurline
+
     allcanvs delete matches
-    catch {unset matchinglines}
     catch {unset findids}
+    set markingmatches 0
+    catch {unset findcurline}
 }
 
 proc selcanvline {w x y} {
@@ -3798,75 +4366,106 @@ proc viewnextline {dir} {
 
 # add a list of tag or branch names at position pos
 # returns the number of names inserted
-proc appendrefs {pos tags var} {
-    global ctext commitrow linknum curview $var
+proc appendrefs {pos ids var} {
+    global ctext commitrow linknum curview $var maxrefs
 
     if {[catch {$ctext index $pos}]} {
        return 0
     }
-    set tags [lsort $tags]
-    set sep {}
-    foreach tag $tags {
-       set id [set $var\($tag\)]
-       set lk link$linknum
-       incr linknum
-       $ctext insert $pos $sep
-       $ctext insert $pos $tag $lk
-       $ctext tag conf $lk -foreground blue
-       if {[info exists commitrow($curview,$id)]} {
-           $ctext tag bind $lk <1> \
-               [list selectline $commitrow($curview,$id) 1]
-           $ctext tag conf $lk -underline 1
-           $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
-           $ctext tag bind $lk <Leave> { %W configure -cursor $curtextcursor }
+    $ctext conf -state normal
+    $ctext delete $pos "$pos lineend"
+    set tags {}
+    foreach id $ids {
+       foreach tag [set $var\($id\)] {
+           lappend tags [list $tag $id]
+       }
+    }
+    if {[llength $tags] > $maxrefs} {
+       $ctext insert $pos "many ([llength $tags])"
+    } else {
+       set tags [lsort -index 0 -decreasing $tags]
+       set sep {}
+       foreach ti $tags {
+           set id [lindex $ti 1]
+           set lk link$linknum
+           incr linknum
+           $ctext tag delete $lk
+           $ctext insert $pos $sep
+           $ctext insert $pos [lindex $ti 0] $lk
+           if {[info exists commitrow($curview,$id)]} {
+               $ctext tag conf $lk -foreground blue
+               $ctext tag bind $lk <1> \
+                   [list selectline $commitrow($curview,$id) 1]
+               $ctext tag conf $lk -underline 1
+               $ctext tag bind $lk <Enter> { %W configure -cursor hand2 }
+               $ctext tag bind $lk <Leave> \
+                   { %W configure -cursor $curtextcursor }
+           }
+           set sep ", "
        }
-       set sep ", "
     }
+    $ctext conf -state disabled
     return [llength $tags]
 }
 
-proc taglist {ids} {
-    global idtags
+# called when we have finished computing the nearby tags
+proc dispneartags {delay} {
+    global selectedline currentid showneartags tagphase
 
-    set tags {}
-    foreach id $ids {
-       foreach tag $idtags($id) {
-           lappend tags $tag
-       }
+    if {![info exists selectedline] || !$showneartags} return
+    after cancel dispnexttag
+    if {$delay} {
+       after 200 dispnexttag
+       set tagphase -1
+    } else {
+       after idle dispnexttag
+       set tagphase 0
     }
-    return $tags
 }
 
-# called when we have finished computing the nearby tags
-proc dispneartags {} {
-    global selectedline currentid ctext anc_tags desc_tags showneartags
-    global desc_heads
+proc dispnexttag {} {
+    global selectedline currentid showneartags tagphase ctext
 
     if {![info exists selectedline] || !$showneartags} return
-    set id $currentid
-    $ctext conf -state normal
-    if {[info exists desc_heads($id)]} {
-       if {[appendrefs branch $desc_heads($id) headids] > 1} {
-           $ctext insert "branch -2c" "es"
+    switch -- $tagphase {
+       0 {
+           set dtags [desctags $currentid]
+           if {$dtags ne {}} {
+               appendrefs precedes $dtags idtags
+           }
+       }
+       1 {
+           set atags [anctags $currentid]
+           if {$atags ne {}} {
+               appendrefs follows $atags idtags
+           }
+       }
+       2 {
+           set dheads [descheads $currentid]
+           if {$dheads ne {}} {
+               if {[appendrefs branch $dheads idheads] > 1
+                   && [$ctext get "branch -3c"] eq "h"} {
+                   # turn "Branch" into "Branches"
+                   $ctext conf -state normal
+                   $ctext insert "branch -2c" "es"
+                   $ctext conf -state disabled
+               }
+           }
        }
     }
-    if {[info exists anc_tags($id)]} {
-       appendrefs follows [taglist $anc_tags($id)] tagids
-    }
-    if {[info exists desc_tags($id)]} {
-       appendrefs precedes [taglist $desc_tags($id)] tagids
+    if {[incr tagphase] <= 2} {
+       after idle dispnexttag
     }
-    $ctext conf -state disabled
 }
 
 proc selectline {l isnew} {
     global canv canv2 canv3 ctext commitinfo selectedline
     global displayorder linehtag linentag linedtag
-    global canvy0 linespc parentlist childlist
+    global canvy0 linespc parentlist children curview
     global currentid sha1entry
     global commentend idtags linknum
     global mergemax numcommits pending_select
-    global cmitmode desc_tags anc_tags showneartags allcommits desc_heads
+    global cmitmode showneartags allcommits
 
     catch {unset pending_select}
     $canv delete hover
@@ -3973,7 +4572,7 @@ proc selectline {l isnew} {
        }
     }
 
-    foreach c [lindex $childlist $l] {
+    foreach c $children($curview,$id) {
        append headers "Child:  [commit_descriptor $c]"
     }
 
@@ -3986,30 +4585,22 @@ proc selectline {l isnew} {
        $ctext insert end "Branch: "
        $ctext mark set branch "end -1c"
        $ctext mark gravity branch left
-       if {[info exists desc_heads($id)]} {
-           if {[appendrefs branch $desc_heads($id) headids] > 1} {
-               # turn "Branch" into "Branches"
-               $ctext insert "branch -2c" "es"
-           }
-       }
        $ctext insert end "\nFollows: "
        $ctext mark set follows "end -1c"
        $ctext mark gravity follows left
-       if {[info exists anc_tags($id)]} {
-           appendrefs follows [taglist $anc_tags($id)] tagids
-       }
        $ctext insert end "\nPrecedes: "
        $ctext mark set precedes "end -1c"
        $ctext mark gravity precedes left
-       if {[info exists desc_tags($id)]} {
-           appendrefs precedes [taglist $desc_tags($id)] tagids
-       }
        $ctext insert end "\n"
+       dispneartags 1
     }
     $ctext insert end "\n"
-    appendwithlinks [lindex $info 5] {comment}
+    set comment [lindex $info 5]
+    if {[string first "\r" $comment] >= 0} {
+       set comment [string map {"\r" "\n    "} $comment]
+    }
+    appendwithlinks $comment {comment}
 
-    $ctext tag delete Comments
     $ctext tag remove found 1.0 end
     $ctext conf -state disabled
     set commentend [$ctext index "end - 1c"]
@@ -4038,6 +4629,7 @@ proc sellastline {} {
 
 proc selnextline {dir} {
     global selectedline
+    focus .
     if {![info exists selectedline]} return
     set l [expr {$selectedline + $dir}]
     unmarkmatches
@@ -4118,6 +4710,7 @@ proc godo {elt} {
 
 proc goback {} {
     global history historyindex
+    focus .
 
     if {$historyindex > 1} {
        incr historyindex -1
@@ -4131,6 +4724,7 @@ proc goback {} {
 
 proc goforw {} {
     global history historyindex
+    focus .
 
     if {$historyindex < [llength $history]} {
        set cmd [lindex $history $historyindex]
@@ -4145,19 +4739,27 @@ proc goforw {} {
 
 proc gettree {id} {
     global treefilelist treeidlist diffids diffmergeid treepending
+    global nullid nullid2
 
     set diffids $id
     catch {unset diffmergeid}
     if {![info exists treefilelist($id)]} {
        if {![info exists treepending]} {
-           if {[catch {set gtf [open [concat | git ls-tree -r $id] r]}]} {
+           if {$id eq $nullid} {
+               set cmd [list | git ls-files]
+           } elseif {$id eq $nullid2} {
+               set cmd [list | git ls-files --stage -t]
+           } else {
+               set cmd [list | git ls-tree -r $id]
+           }
+           if {[catch {set gtf [open $cmd r]}]} {
                return
            }
            set treepending $id
            set treefilelist($id) {}
            set treeidlist($id) {}
            fconfigure $gtf -blocking 0
-           fileevent $gtf readable [list gettreeline $gtf $id]
+           filerun $gtf [list gettreeline $gtf $id]
        }
     } else {
        setfilelist $id
@@ -4165,16 +4767,28 @@ proc gettree {id} {
 }
 
 proc gettreeline {gtf id} {
-    global treefilelist treeidlist treepending cmitmode diffids
+    global treefilelist treeidlist treepending cmitmode diffids nullid nullid2
 
-    while {[gets $gtf line] >= 0} {
-       if {[lindex $line 1] ne "blob"} continue
-       set sha1 [lindex $line 2]
-       set fname [lindex $line 3]
+    set nl 0
+    while {[incr nl] <= 1000 && [gets $gtf line] >= 0} {
+       if {$diffids eq $nullid} {
+           set fname $line
+       } else {
+           if {$diffids ne $nullid2 && [lindex $line 1] ne "blob"} continue
+           set i [string first "\t" $line]
+           if {$i < 0} continue
+           set sha1 [lindex $line 2]
+           set fname [string range $line [expr {$i+1}] end]
+           if {[string index $fname 0] eq "\""} {
+               set fname [lindex $fname 0]
+           }
+           lappend treeidlist($id) $sha1
+       }
        lappend treefilelist($id) $fname
-       lappend treeidlist($id) $sha1
     }
-    if {![eof $gtf]} return
+    if {![eof $gtf]} {
+       return [expr {$nl >= 1000? 2: 1}]
+    }
     close $gtf
     unset treepending
     if {$cmitmode ne "tree"} {
@@ -4186,10 +4800,11 @@ proc gettreeline {gtf id} {
     } else {
        setfilelist $id
     }
+    return 0
 }
 
 proc showfile {f} {
-    global treefilelist treeidlist diffids
+    global treefilelist treeidlist diffids nullid nullid2
     global ctext commentend
 
     set i [lsearch -exact $treefilelist($diffids) $f]
@@ -4197,13 +4812,20 @@ proc showfile {f} {
        puts "oops, $f not in list for id $diffids"
        return
     }
-    set blob [lindex $treeidlist($diffids) $i]
-    if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
-       puts "oops, error reading blob $blob: $err"
-       return
+    if {$diffids eq $nullid} {
+       if {[catch {set bf [open $f r]} err]} {
+           puts "oops, can't read $f: $err"
+           return
+       }
+    } else {
+       set blob [lindex $treeidlist($diffids) $i]
+       if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
+           puts "oops, error reading blob $blob: $err"
+           return
+       }
     }
     fconfigure $bf -blocking 0
-    fileevent $bf readable [list getblobline $bf $diffids]
+    filerun $bf [list getblobline $bf $diffids]
     $ctext config -state normal
     clear_ctext $commentend
     $ctext insert end "\n"
@@ -4217,18 +4839,21 @@ proc getblobline {bf id} {
 
     if {$id ne $diffids || $cmitmode ne "tree"} {
        catch {close $bf}
-       return
+       return 0
     }
     $ctext config -state normal
-    while {[gets $bf line] >= 0} {
+    set nl 0
+    while {[incr nl] <= 1000 && [gets $bf line] >= 0} {
        $ctext insert end "$line\n"
     }
     if {[eof $bf]} {
        # delete last newline
        $ctext delete "end - 2c" "end - 1c"
        close $bf
+       return 0
     }
     $ctext config -state disabled
+    return [expr {$nl >= 1000? 2: 1}]
 }
 
 proc mergediff {id l} {
@@ -4248,91 +4873,88 @@ proc mergediff {id l} {
     fconfigure $mdf -blocking 0
     set mdifffd($id) $mdf
     set np [llength [lindex $parentlist $l]]
-    fileevent $mdf readable [list getmergediffline $mdf $id $np]
-    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+    filerun $mdf [list getmergediffline $mdf $id $np]
 }
 
 proc getmergediffline {mdf id np} {
-    global diffmergeid ctext cflist nextupdate mergemax
+    global diffmergeid ctext cflist mergemax
     global difffilestart mdifffd
 
-    set n [gets $mdf line]
-    if {$n < 0} {
-       if {[eof $mdf]} {
+    $ctext conf -state normal
+    set nr 0
+    while {[incr nr] <= 1000 && [gets $mdf line] >= 0} {
+       if {![info exists diffmergeid] || $id != $diffmergeid
+           || $mdf != $mdifffd($id)} {
            close $mdf
+           return 0
        }
-       return
-    }
-    if {![info exists diffmergeid] || $id != $diffmergeid
-       || $mdf != $mdifffd($id)} {
-       return
-    }
-    $ctext conf -state normal
-    if {[regexp {^diff --cc (.*)} $line match fname]} {
-       # start of a new file
-       $ctext insert end "\n"
-       set here [$ctext index "end - 1c"]
-       lappend difffilestart $here
-       add_flist [list $fname]
-       set l [expr {(78 - [string length $fname]) / 2}]
-       set pad [string range "----------------------------------------" 1 $l]
-       $ctext insert end "$pad $fname $pad\n" filesep
-    } elseif {[regexp {^@@} $line]} {
-       $ctext insert end "$line\n" hunksep
-    } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
-       # do nothing
-    } else {
-       # parse the prefix - one ' ', '-' or '+' for each parent
-       set spaces {}
-       set minuses {}
-       set pluses {}
-       set isbad 0
-       for {set j 0} {$j < $np} {incr j} {
-           set c [string range $line $j $j]
-           if {$c == " "} {
-               lappend spaces $j
-           } elseif {$c == "-"} {
-               lappend minuses $j
-           } elseif {$c == "+"} {
-               lappend pluses $j
-           } else {
-               set isbad 1
-               break
+       if {[regexp {^diff --cc (.*)} $line match fname]} {
+           # start of a new file
+           $ctext insert end "\n"
+           set here [$ctext index "end - 1c"]
+           lappend difffilestart $here
+           add_flist [list $fname]
+           set l [expr {(78 - [string length $fname]) / 2}]
+           set pad [string range "----------------------------------------" 1 $l]
+           $ctext insert end "$pad $fname $pad\n" filesep
+       } elseif {[regexp {^@@} $line]} {
+           $ctext insert end "$line\n" hunksep
+       } elseif {[regexp {^[0-9a-f]{40}$} $line] || [regexp {^index} $line]} {
+           # do nothing
+       } else {
+           # parse the prefix - one ' ', '-' or '+' for each parent
+           set spaces {}
+           set minuses {}
+           set pluses {}
+           set isbad 0
+           for {set j 0} {$j < $np} {incr j} {
+               set c [string range $line $j $j]
+               if {$c == " "} {
+                   lappend spaces $j
+               } elseif {$c == "-"} {
+                   lappend minuses $j
+               } elseif {$c == "+"} {
+                   lappend pluses $j
+               } else {
+                   set isbad 1
+                   break
+               }
            }
-       }
-       set tags {}
-       set num {}
-       if {!$isbad && $minuses ne {} && $pluses eq {}} {
-           # line doesn't appear in result, parents in $minuses have the line
-           set num [lindex $minuses 0]
-       } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
-           # line appears in result, parents in $pluses don't have the line
-           lappend tags mresult
-           set num [lindex $spaces 0]
-       }
-       if {$num ne {}} {
-           if {$num >= $mergemax} {
-               set num "max"
+           set tags {}
+           set num {}
+           if {!$isbad && $minuses ne {} && $pluses eq {}} {
+               # line doesn't appear in result, parents in $minuses have the line
+               set num [lindex $minuses 0]
+           } elseif {!$isbad && $pluses ne {} && $minuses eq {}} {
+               # line appears in result, parents in $pluses don't have the line
+               lappend tags mresult
+               set num [lindex $spaces 0]
            }
-           lappend tags m$num
+           if {$num ne {}} {
+               if {$num >= $mergemax} {
+                   set num "max"
+               }
+               lappend tags m$num
+           }
+           $ctext insert end "$line\n" $tags
        }
-       $ctext insert end "$line\n" $tags
     }
     $ctext conf -state disabled
-    if {[clock clicks -milliseconds] >= $nextupdate} {
-       incr nextupdate 100
-       fileevent $mdf readable {}
-       update
-       fileevent $mdf readable [list getmergediffline $mdf $id $np]
+    if {[eof $mdf]} {
+       close $mdf
+       return 0
     }
+    return [expr {$nr >= 1000? 2: 1}]
 }
 
 proc startdiff {ids} {
-    global treediffs diffids treepending diffmergeid
+    global treediffs diffids treepending diffmergeid nullid nullid2
 
     set diffids $ids
     catch {unset diffmergeid}
-    if {![info exists treediffs($ids)]} {
+    if {![info exists treediffs($ids)] ||
+       [lsearch -exact $ids $nullid] >= 0 ||
+       [lsearch -exact $ids $nullid2] >= 0} {
        if {![info exists treepending]} {
            gettreediffs $ids
        }
@@ -4347,59 +4969,102 @@ proc addtocflist {ids} {
     getblobdiffs $ids
 }
 
+proc diffcmd {ids flags} {
+    global nullid nullid2
+
+    set i [lsearch -exact $ids $nullid]
+    set j [lsearch -exact $ids $nullid2]
+    if {$i >= 0} {
+       if {[llength $ids] > 1 && $j < 0} {
+           # comparing working directory with some specific revision
+           set cmd [concat | git diff-index $flags]
+           if {$i == 0} {
+               lappend cmd -R [lindex $ids 1]
+           } else {
+               lappend cmd [lindex $ids 0]
+           }
+       } else {
+           # comparing working directory with index
+           set cmd [concat | git diff-files $flags]
+           if {$j == 1} {
+               lappend cmd -R
+           }
+       }
+    } elseif {$j >= 0} {
+       set cmd [concat | git diff-index --cached $flags]
+       if {[llength $ids] > 1} {
+           # comparing index with specific revision
+           if {$i == 0} {
+               lappend cmd -R [lindex $ids 1]
+           } else {
+               lappend cmd [lindex $ids 0]
+           }
+       } else {
+           # comparing index with HEAD
+           lappend cmd HEAD
+       }
+    } else {
+       set cmd [concat | git diff-tree -r $flags $ids]
+    }
+    return $cmd
+}
+
 proc gettreediffs {ids} {
     global treediff treepending
+
     set treepending $ids
     set treediff {}
-    if {[catch \
-        {set gdtf [open [concat | git diff-tree --no-commit-id -r $ids] r]} \
-       ]} return
+    if {[catch {set gdtf [open [diffcmd $ids {--no-commit-id}] r]}]} return
     fconfigure $gdtf -blocking 0
-    fileevent $gdtf readable [list gettreediffline $gdtf $ids]
+    filerun $gdtf [list gettreediffline $gdtf $ids]
 }
 
 proc gettreediffline {gdtf ids} {
     global treediff treediffs treepending diffids diffmergeid
     global cmitmode
 
-    set n [gets $gdtf line]
-    if {$n < 0} {
-       if {![eof $gdtf]} return
-       close $gdtf
-       set treediffs($ids) $treediff
-       unset treepending
-       if {$cmitmode eq "tree"} {
-           gettree $diffids
-       } elseif {$ids != $diffids} {
-           if {![info exists diffmergeid]} {
-               gettreediffs $diffids
+    set nr 0
+    while {[incr nr] <= 1000 && [gets $gdtf line] >= 0} {
+       set i [string first "\t" $line]
+       if {$i >= 0} {
+           set file [string range $line [expr {$i+1}] end]
+           if {[string index $file 0] eq "\""} {
+               set file [lindex $file 0]
            }
-       } else {
-           addtocflist $ids
+           lappend treediff $file
        }
-       return
     }
-    set file [lindex $line 5]
-    lappend treediff $file
+    if {![eof $gdtf]} {
+       return [expr {$nr >= 1000? 2: 1}]
+    }
+    close $gdtf
+    set treediffs($ids) $treediff
+    unset treepending
+    if {$cmitmode eq "tree"} {
+       gettree $diffids
+    } elseif {$ids != $diffids} {
+       if {![info exists diffmergeid]} {
+           gettreediffs $diffids
+       }
+    } else {
+       addtocflist $ids
+    }
+    return 0
 }
 
 proc getblobdiffs {ids} {
-    global diffopts blobdifffd diffids env curdifftag curtagstart
-    global nextupdate diffinhdr treediffs
+    global diffopts blobdifffd diffids env
+    global diffinhdr treediffs
 
     set env(GIT_DIFF_OPTS) $diffopts
-    set cmd [concat | git diff-tree --no-commit-id -r -p -C $ids]
-    if {[catch {set bdf [open $cmd r]} err]} {
+    if {[catch {set bdf [open [diffcmd $ids {-p -C --no-commit-id}] r]} err]} {
        puts "error getting diffs: $err"
        return
     }
     set diffinhdr 0
     fconfigure $bdf -blocking 0
     set blobdifffd($ids) $bdf
-    set curdifftag Comments
-    set curtagstart 0.0
-    fileevent $bdf readable [list getblobdiffline $bdf $diffids]
-    set nextupdate [expr {[clock clicks -milliseconds] + 100}]
+    filerun $bdf [list getblobdiffline $bdf $diffids]
 }
 
 proc setinlist {var i val} {
@@ -4415,84 +5080,111 @@ proc setinlist {var i val} {
     }
 }
 
+proc makediffhdr {fname ids} {
+    global ctext curdiffstart treediffs
+
+    set i [lsearch -exact $treediffs($ids) $fname]
+    if {$i >= 0} {
+       setinlist difffilestart $i $curdiffstart
+    }
+    set l [expr {(78 - [string length $fname]) / 2}]
+    set pad [string range "----------------------------------------" 1 $l]
+    $ctext insert $curdiffstart "$pad $fname $pad" filesep
+}
+
 proc getblobdiffline {bdf ids} {
-    global diffids blobdifffd ctext curdifftag curtagstart
+    global diffids blobdifffd ctext curdiffstart
     global diffnexthead diffnextnote difffilestart
-    global nextupdate diffinhdr treediffs
+    global diffinhdr treediffs
 
-    set n [gets $bdf line]
-    if {$n < 0} {
-       if {[eof $bdf]} {
+    set nr 0
+    $ctext conf -state normal
+    while {[incr nr] <= 1000 && [gets $bdf line] >= 0} {
+       if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
            close $bdf
-           if {$ids == $diffids && $bdf == $blobdifffd($ids)} {
-               $ctext tag add $curdifftag $curtagstart end
-           }
+           return 0
        }
-       return
-    }
-    if {$ids != $diffids || $bdf != $blobdifffd($ids)} {
-       return
-    }
-    $ctext conf -state normal
-    if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} {
-       # start of a new file
-       $ctext insert end "\n"
-       $ctext tag add $curdifftag $curtagstart end
-       set here [$ctext index "end - 1c"]
-       set curtagstart $here
-       set header $newname
-       set i [lsearch -exact $treediffs($ids) $fname]
-       if {$i >= 0} {
-           setinlist difffilestart $i $here
-       }
-       if {$newname ne $fname} {
-           set i [lsearch -exact $treediffs($ids) $newname]
-           if {$i >= 0} {
-               setinlist difffilestart $i $here
-           }
-       }
-       set curdifftag "f:$fname"
-       $ctext tag delete $curdifftag
-       set l [expr {(78 - [string length $header]) / 2}]
-       set pad [string range "----------------------------------------" 1 $l]
-       $ctext insert end "$pad $header $pad\n" filesep
-       set diffinhdr 1
-    } elseif {$diffinhdr && [string compare -length 3 $line "---"] == 0} {
-       # do nothing
-    } elseif {$diffinhdr && [string compare -length 3 $line "+++"] == 0} {
-       set diffinhdr 0
-    } elseif {[regexp {^@@ -([0-9]+),([0-9]+) \+([0-9]+),([0-9]+) @@(.*)} \
-                  $line match f1l f1c f2l f2c rest]} {
-       $ctext insert end "$line\n" hunksep
-       set diffinhdr 0
-    } else {
-       set x [string range $line 0 0]
-       if {$x == "-" || $x == "+"} {
-           set tag [expr {$x == "+"}]
-           $ctext insert end "$line\n" d$tag
-       } elseif {$x == " "} {
-           $ctext insert end "$line\n"
-       } elseif {$diffinhdr || $x == "\\"} {
-           # e.g. "\ No newline at end of file"
+       if {![string compare -length 11 "diff --git " $line]} {
+           # trim off "diff --git "
+           set line [string range $line 11 end]
+           set diffinhdr 1
+           # start of a new file
+           $ctext insert end "\n"
+           set curdiffstart [$ctext index "end - 1c"]
+           $ctext insert end "\n" filesep
+           # If the name hasn't changed the length will be odd,
+           # the middle char will be a space, and the two bits either
+           # side will be a/name and b/name, or "a/name" and "b/name".
+           # If the name has changed we'll get "rename from" and
+           # "rename to" lines following this, and we'll use them
+           # to get the filenames.
+           # This complexity is necessary because spaces in the filename(s)
+           # don't get escaped.
+           set l [string length $line]
+           set i [expr {$l / 2}]
+           if {!(($l & 1) && [string index $line $i] eq " " &&
+                 [string range $line 2 [expr {$i - 1}]] eq \
+                     [string range $line [expr {$i + 3}] end])} {
+               continue
+           }
+           # unescape if quoted and chop off the a/ from the front
+           if {[string index $line 0] eq "\""} {
+               set fname [string range [lindex $line 0] 2 end]
+           } else {
+               set fname [string range $line 2 [expr {$i - 1}]]
+           }
+           makediffhdr $fname $ids
+
+       } elseif {[regexp {^@@ -([0-9]+)(,[0-9]+)? \+([0-9]+)(,[0-9]+)? @@(.*)} \
+                      $line match f1l f1c f2l f2c rest]} {
+           $ctext insert end "$line\n" hunksep
+           set diffinhdr 0
+
+       } elseif {$diffinhdr} {
+           if {![string compare -length 12 "rename from " $line]} {
+               set fname [string range $line 12 end]
+               if {[string index $fname 0] eq "\""} {
+                   set fname [lindex $fname 0]
+               }
+               set i [lsearch -exact $treediffs($ids) $fname]
+               if {$i >= 0} {
+                   setinlist difffilestart $i $curdiffstart
+               }
+           } elseif {![string compare -length 10 $line "rename to "]} {
+               set fname [string range $line 10 end]
+               if {[string index $fname 0] eq "\""} {
+                   set fname [lindex $fname 0]
+               }
+               makediffhdr $fname $ids
+           } elseif {[string compare -length 3 $line "---"] == 0} {
+               # do nothing
+               continue
+           } elseif {[string compare -length 3 $line "+++"] == 0} {
+               set diffinhdr 0
+               continue
+           }
            $ctext insert end "$line\n" filesep
+
        } else {
-           # Something else we don't recognize
-           if {$curdifftag != "Comments"} {
-               $ctext insert end "\n"
-               $ctext tag add $curdifftag $curtagstart end
-               set curtagstart [$ctext index "end - 1c"]
-               set curdifftag Comments
+           set x [string range $line 0 0]
+           if {$x == "-" || $x == "+"} {
+               set tag [expr {$x == "+"}]
+               $ctext insert end "$line\n" d$tag
+           } elseif {$x == " "} {
+               $ctext insert end "$line\n"
+           } else {
+               # "\ No newline at end of file",
+               # or something else we don't recognize
+               $ctext insert end "$line\n" hunksep
            }
-           $ctext insert end "$line\n" filesep
        }
     }
     $ctext conf -state disabled
-    if {[clock clicks -milliseconds] >= $nextupdate} {
-       incr nextupdate 100
-       fileevent $bdf readable {}
-       update
-       fileevent $bdf readable "getblobdiffline $bdf {$ids}"
+    if {[eof $bdf]} {
+       close $bdf
+       return 0
     }
+    return [expr {$nr >= 1000? 2: 1}]
 }
 
 proc changediffdisp {} {
@@ -4690,13 +5382,15 @@ proc redisplay {} {
 }
 
 proc incrfont {inc} {
-    global mainfont textfont ctext canv phase
+    global mainfont textfont ctext canv phase cflist
+    global charspc tabstop
     global stopped entries
     unmarkmatches
     set mainfont [lreplace $mainfont 1 1 [expr {[lindex $mainfont 1] + $inc}]]
     set textfont [lreplace $textfont 1 1 [expr {[lindex $textfont 1] + $inc}]]
     setcoords
-    $ctext conf -font $textfont
+    $ctext conf -font $textfont -tabs "[expr {$tabstop * $charspc}]"
+    $cflist conf -font $textfont
     $ctext tag conf filesep -font [concat $textfont bold]
     foreach e $entries {
        $e conf -font $mainfont
@@ -4954,18 +5648,25 @@ proc mstime {} {
 
 proc rowmenu {x y id} {
     global rowctxmenu commitrow selectedline rowmenuid curview
+    global nullid nullid2 fakerowmenu mainhead
 
+    set rowmenuid $id
     if {![info exists selectedline]
        || $commitrow($curview,$id) eq $selectedline} {
        set state disabled
     } else {
        set state normal
     }
-    $rowctxmenu entryconfigure "Diff this*" -state $state
-    $rowctxmenu entryconfigure "Diff selected*" -state $state
-    $rowctxmenu entryconfigure "Make patch" -state $state
-    set rowmenuid $id
-    tk_popup $rowctxmenu $x $y
+    if {$id ne $nullid && $id ne $nullid2} {
+       set menu $rowctxmenu
+       $menu entryconfigure 7 -label "Reset $mainhead branch to here"
+    } else {
+       set menu $fakerowmenu
+    }
+    $menu entryconfigure "Diff this*" -state $state
+    $menu entryconfigure "Diff selected*" -state $state
+    $menu entryconfigure "Make patch" -state $state
+    tk_popup $menu $x $y
 }
 
 proc diffvssel {dirn} {
@@ -5005,7 +5706,6 @@ proc doseldiff {oldid newid} {
     $ctext insert end [lindex $commitinfo($newid) 0]
     $ctext insert end "\n"
     $ctext conf -state disabled
-    $ctext tag delete Comments
     $ctext tag remove found 1.0 end
     startdiff [list $oldid $newid]
 }
@@ -5076,12 +5776,14 @@ proc mkpatchrev {} {
 }
 
 proc mkpatchgo {} {
-    global patchtop
+    global patchtop nullid nullid2
 
     set oldid [$patchtop.fromsha1 get]
     set newid [$patchtop.tosha1 get]
     set fname [$patchtop.fname get]
-    if {[catch {exec git diff-tree -p $oldid $newid >$fname &} err]} {
+    set cmd [diffcmd [list $oldid $newid] -p]
+    lappend cmd >$fname &
+    if {[catch {eval exec $cmd} err]} {
        error_popup "Error creating patch: $err"
     }
     catch {destroy $patchtop}
@@ -5158,10 +5860,11 @@ proc domktag {} {
 
 proc redrawtags {id} {
     global canv linehtag commitrow idpos selectedline curview
-    global mainfont canvxmax
+    global mainfont canvxmax iddrawn
 
     if {![info exists commitrow($curview,$id)]} return
-    drawcmitrow $commitrow($curview,$id)
+    if {![info exists iddrawn($id)]} return
+    drawcommits $commitrow($curview,$id)
     $canv delete tag.$id
     set xt [eval drawtags $id $idpos($id)]
     $canv coords $linehtag($commitrow($curview,$id)) $xt [lindex $idpos($id) 2]
@@ -5288,26 +5991,28 @@ proc mkbrgo {top} {
        notbusy newbranch
        error_popup $err
     } else {
+       set headids($name) $id
+       lappend idheads($id) $name
        addedhead $id $name
-       # XXX should update list of heads displayed for selected commit
        notbusy newbranch
        redrawtags $id
+       dispneartags 0
     }
 }
 
 proc cherrypick {} {
     global rowmenuid curview commitrow
-    global mainhead desc_heads anc_tags desc_tags allparents allchildren
+    global mainhead
 
-    if {[info exists desc_heads($rowmenuid)]
-       && [lsearch -exact $desc_heads($rowmenuid) $mainhead] >= 0} {
+    set oldhead [exec git rev-parse HEAD]
+    set dheads [descheads $rowmenuid]
+    if {$dheads ne {} && [lsearch -exact $dheads $oldhead] >= 0} {
        set ok [confirm_popup "Commit [string range $rowmenuid 0 7] is already\
                        included in branch $mainhead -- really re-apply it?"]
        if {!$ok} return
     }
     nowbusy cherrypick
     update
-    set oldhead [exec git rev-parse HEAD]
     # Unfortunately git-cherry-pick writes stuff to stderr even when
     # no error occurs, and exec takes that as an indication of error...
     if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
@@ -5321,16 +6026,11 @@ proc cherrypick {} {
        error_popup "No changes committed"
        return
     }
-    set allparents($newhead) $oldhead
-    lappend allchildren($oldhead) $newhead
-    set desc_heads($newhead) $mainhead
-    if {[info exists anc_tags($oldhead)]} {
-       set anc_tags($newhead) $anc_tags($oldhead)
-    }
-    set desc_tags($newhead) {}
+    addnewchild $newhead $oldhead
     if {[info exists commitrow($curview,$oldhead)]} {
        insertrow $commitrow($curview,$oldhead) $newhead
        if {$mainhead ne {}} {
+           movehead $newhead $mainhead
            movedhead $newhead $mainhead
        }
        redrawtags $oldhead
@@ -5339,48 +6039,146 @@ proc cherrypick {} {
     notbusy cherrypick
 }
 
+proc resethead {} {
+    global mainheadid mainhead rowmenuid confirm_ok resettype
+    global showlocalchanges
+
+    set confirm_ok 0
+    set w ".confirmreset"
+    toplevel $w
+    wm transient $w .
+    wm title $w "Confirm reset"
+    message $w.m -text \
+       "Reset branch $mainhead to [string range $rowmenuid 0 7]?" \
+       -justify center -aspect 1000
+    pack $w.m -side top -fill x -padx 20 -pady 20
+    frame $w.f -relief sunken -border 2
+    message $w.f.rt -text "Reset type:" -aspect 1000
+    grid $w.f.rt -sticky w
+    set resettype mixed
+    radiobutton $w.f.soft -value soft -variable resettype -justify left \
+       -text "Soft: Leave working tree and index untouched"
+    grid $w.f.soft -sticky w
+    radiobutton $w.f.mixed -value mixed -variable resettype -justify left \
+       -text "Mixed: Leave working tree untouched, reset index"
+    grid $w.f.mixed -sticky w
+    radiobutton $w.f.hard -value hard -variable resettype -justify left \
+       -text "Hard: Reset working tree and index\n(discard ALL local changes)"
+    grid $w.f.hard -sticky w
+    pack $w.f -side top -fill x
+    button $w.ok -text OK -command "set confirm_ok 1; destroy $w"
+    pack $w.ok -side left -fill x -padx 20 -pady 20
+    button $w.cancel -text Cancel -command "destroy $w"
+    pack $w.cancel -side right -fill x -padx 20 -pady 20
+    bind $w <Visibility> "grab $w; focus $w"
+    tkwait window $w
+    if {!$confirm_ok} return
+    if {[catch {set fd [open \
+           [list | sh -c "git reset --$resettype $rowmenuid 2>&1"] r]} err]} {
+       error_popup $err
+    } else {
+       dohidelocalchanges
+       set w ".resetprogress"
+       filerun $fd [list readresetstat $fd $w]
+       toplevel $w
+       wm transient $w
+       wm title $w "Reset progress"
+       message $w.m -text "Reset in progress, please wait..." \
+           -justify center -aspect 1000
+       pack $w.m -side top -fill x -padx 20 -pady 5
+       canvas $w.c -width 150 -height 20 -bg white
+       $w.c create rect 0 0 0 20 -fill green -tags rect
+       pack $w.c -side top -fill x -padx 20 -pady 5 -expand 1
+       nowbusy reset
+    }
+}
+
+proc readresetstat {fd w} {
+    global mainhead mainheadid showlocalchanges
+
+    if {[gets $fd line] >= 0} {
+       if {[regexp {([0-9]+)% \(([0-9]+)/([0-9]+)\)} $line match p m n]} {
+           set x [expr {($m * 150) / $n}]
+           $w.c coords rect 0 0 $x 20
+       }
+       return 1
+    }
+    destroy $w
+    notbusy reset
+    if {[catch {close $fd} err]} {
+       error_popup $err
+    }
+    set oldhead $mainheadid
+    set newhead [exec git rev-parse HEAD]
+    if {$newhead ne $oldhead} {
+       movehead $newhead $mainhead
+       movedhead $newhead $mainhead
+       set mainheadid $newhead
+       redrawtags $oldhead
+       redrawtags $newhead
+    }
+    if {$showlocalchanges} {
+       doshowlocalchanges
+    }
+    return 0
+}
+
 # context menu for a head
 proc headmenu {x y id head} {
-    global headmenuid headmenuhead headctxmenu
+    global headmenuid headmenuhead headctxmenu mainhead
 
     set headmenuid $id
     set headmenuhead $head
+    set state normal
+    if {$head eq $mainhead} {
+       set state disabled
+    }
+    $headctxmenu entryconfigure 0 -state $state
+    $headctxmenu entryconfigure 1 -state $state
     tk_popup $headctxmenu $x $y
 }
 
 proc cobranch {} {
     global headmenuid headmenuhead mainhead headids
+    global showlocalchanges mainheadid
 
     # check the tree is clean first??
     set oldmainhead $mainhead
     nowbusy checkout
     update
+    dohidelocalchanges
     if {[catch {
-       exec git checkout $headmenuhead
+       exec git checkout -q $headmenuhead
     } err]} {
        notbusy checkout
        error_popup $err
     } else {
        notbusy checkout
        set mainhead $headmenuhead
+       set mainheadid $headmenuid
        if {[info exists headids($oldmainhead)]} {
            redrawtags $headids($oldmainhead)
        }
        redrawtags $headmenuid
     }
+    if {$showlocalchanges} {
+       dodiffindex
+    }
 }
 
 proc rmbranch {} {
-    global desc_heads headmenuid headmenuhead mainhead
+    global headmenuid headmenuhead mainhead
     global headids idheads
 
     set head $headmenuhead
     set id $headmenuid
+    # this check shouldn't be needed any more...
     if {$head eq $mainhead} {
        error_popup "Cannot delete the currently checked-out branch"
        return
     }
-    if {$desc_heads($id) eq $head} {
+    set dheads [descheads $id]
+    if {$dheads eq $headids($head)} {
        # the stuff on this branch isn't on any other branch
        if {![confirm_popup "The commits on branch $head aren't on any other\
                        branch.\nReally delete branch $head?"]} return
@@ -5392,385 +6190,883 @@ proc rmbranch {} {
        error_popup $err
        return
     }
+    removehead $id $head
     removedhead $id $head
     redrawtags $id
     notbusy rmbranch
+    dispneartags 0
 }
 
 # Stuff for finding nearby tags
 proc getallcommits {} {
-    global allcstart allcommits allcfd allids
+    global allcommits allids nbmp nextarc seeds
+
+    if {![info exists allcommits]} {
+       set allids {}
+       set nbmp 0
+       set nextarc 0
+       set allcommits 0
+       set seeds {}
+    }
 
-    set allids {}
-    set fd [open [concat | git rev-list --all --topo-order --parents] r]
-    set allcfd $fd
+    set cmd [concat | git rev-list --all --parents]
+    foreach id $seeds {
+       lappend cmd "^$id"
+    }
+    set fd [open $cmd r]
     fconfigure $fd -blocking 0
-    set allcommits "reading"
+    incr allcommits
     nowbusy allcommits
-    restartgetall $fd
-}
+    filerun $fd [list getallclines $fd]
+}
+
+# Since most commits have 1 parent and 1 child, we group strings of
+# such commits into "arcs" joining branch/merge points (BMPs), which
+# are commits that either don't have 1 parent or don't have 1 child.
+#
+# arcnos(id) - incoming arcs for BMP, arc we're on for other nodes
+# arcout(id) - outgoing arcs for BMP
+# arcids(a) - list of IDs on arc including end but not start
+# arcstart(a) - BMP ID at start of arc
+# arcend(a) - BMP ID at end of arc
+# growing(a) - arc a is still growing
+# arctags(a) - IDs out of arcids (excluding end) that have tags
+# archeads(a) - IDs out of arcids (excluding end) that have heads
+# The start of an arc is at the descendent end, so "incoming" means
+# coming from descendents, and "outgoing" means going towards ancestors.
+
+proc getallclines {fd} {
+    global allids allparents allchildren idtags idheads nextarc nbmp
+    global arcnos arcids arctags arcout arcend arcstart archeads growing
+    global seeds allcommits
 
-proc discardallcommits {} {
-    global allparents allchildren allcommits allcfd
-    global desc_tags anc_tags alldtags tagisdesc allids desc_heads
+    set nid 0
+    while {[incr nid] <= 1000 && [gets $fd line] >= 0} {
+       set id [lindex $line 0]
+       if {[info exists allparents($id)]} {
+           # seen it already
+           continue
+       }
+       lappend allids $id
+       set olds [lrange $line 1 end]
+       set allparents($id) $olds
+       if {![info exists allchildren($id)]} {
+           set allchildren($id) {}
+           set arcnos($id) {}
+           lappend seeds $id
+       } else {
+           set a $arcnos($id)
+           if {[llength $olds] == 1 && [llength $a] == 1} {
+               lappend arcids($a) $id
+               if {[info exists idtags($id)]} {
+                   lappend arctags($a) $id
+               }
+               if {[info exists idheads($id)]} {
+                   lappend archeads($a) $id
+               }
+               if {[info exists allparents($olds)]} {
+                   # seen parent already
+                   if {![info exists arcout($olds)]} {
+                       splitarc $olds
+                   }
+                   lappend arcids($a) $olds
+                   set arcend($a) $olds
+                   unset growing($a)
+               }
+               lappend allchildren($olds) $id
+               lappend arcnos($olds) $a
+               continue
+           }
+       }
+       incr nbmp
+       foreach a $arcnos($id) {
+           lappend arcids($a) $id
+           set arcend($a) $id
+           unset growing($a)
+       }
 
-    if {![info exists allcommits]} return
-    if {$allcommits eq "reading"} {
-       catch {close $allcfd}
+       set ao {}
+       foreach p $olds {
+           lappend allchildren($p) $id
+           set a [incr nextarc]
+           set arcstart($a) $id
+           set archeads($a) {}
+           set arctags($a) {}
+           set archeads($a) {}
+           set arcids($a) {}
+           lappend ao $a
+           set growing($a) 1
+           if {[info exists allparents($p)]} {
+               # seen it already, may need to make a new branch
+               if {![info exists arcout($p)]} {
+                   splitarc $p
+               }
+               lappend arcids($a) $p
+               set arcend($a) $p
+               unset growing($a)
+           }
+           lappend arcnos($p) $a
+       }
+       set arcout($id) $ao
     }
-    foreach v {allcommits allchildren allparents allids desc_tags anc_tags
-               alldtags tagisdesc desc_heads} {
-       catch {unset $v}
+    if {$nid > 0} {
+       global cached_dheads cached_dtags cached_atags
+       catch {unset cached_dheads}
+       catch {unset cached_dtags}
+       catch {unset cached_atags}
+    }
+    if {![eof $fd]} {
+       return [expr {$nid >= 1000? 2: 1}]
+    }
+    close $fd
+    if {[incr allcommits -1] == 0} {
+       notbusy allcommits
     }
+    dispneartags 0
+    return 0
 }
 
-proc restartgetall {fd} {
-    global allcstart
+proc recalcarc {a} {
+    global arctags archeads arcids idtags idheads
 
-    fileevent $fd readable [list getallclines $fd]
-    set allcstart [clock clicks -milliseconds]
+    set at {}
+    set ah {}
+    foreach id [lrange $arcids($a) 0 end-1] {
+       if {[info exists idtags($id)]} {
+           lappend at $id
+       }
+       if {[info exists idheads($id)]} {
+           lappend ah $id
+       }
+    }
+    set arctags($a) $at
+    set archeads($a) $ah
 }
 
-proc combine_dtags {l1 l2} {
-    global tagisdesc notfirstd
+proc splitarc {p} {
+    global arcnos arcids nextarc nbmp arctags archeads idtags idheads
+    global arcstart arcend arcout allparents growing
 
-    set res [lsort -unique [concat $l1 $l2]]
-    for {set i 0} {$i < [llength $res]} {incr i} {
-       set x [lindex $res $i]
-       for {set j [expr {$i+1}]} {$j < [llength $res]} {} {
-           set y [lindex $res $j]
-           if {[info exists tagisdesc($x,$y)]} {
-               if {$tagisdesc($x,$y) > 0} {
-                   # x is a descendent of y, exclude x
-                   set res [lreplace $res $i $i]
-                   incr i -1
-                   break
-               } else {
-                   # y is a descendent of x, exclude y
-                   set res [lreplace $res $j $j]
-               }
-           } else {
-               # no relation, keep going
-               incr j
+    set a $arcnos($p)
+    if {[llength $a] != 1} {
+       puts "oops splitarc called but [llength $a] arcs already"
+       return
+    }
+    set a [lindex $a 0]
+    set i [lsearch -exact $arcids($a) $p]
+    if {$i < 0} {
+       puts "oops splitarc $p not in arc $a"
+       return
+    }
+    set na [incr nextarc]
+    if {[info exists arcend($a)]} {
+       set arcend($na) $arcend($a)
+    } else {
+       set l [lindex $allparents([lindex $arcids($a) end]) 0]
+       set j [lsearch -exact $arcnos($l) $a]
+       set arcnos($l) [lreplace $arcnos($l) $j $j $na]
+    }
+    set tail [lrange $arcids($a) [expr {$i+1}] end]
+    set arcids($a) [lrange $arcids($a) 0 $i]
+    set arcend($a) $p
+    set arcstart($na) $p
+    set arcout($p) $na
+    set arcids($na) $tail
+    if {[info exists growing($a)]} {
+       set growing($na) 1
+       unset growing($a)
+    }
+    incr nbmp
+
+    foreach id $tail {
+       if {[llength $arcnos($id)] == 1} {
+           set arcnos($id) $na
+       } else {
+           set j [lsearch -exact $arcnos($id) $a]
+           set arcnos($id) [lreplace $arcnos($id) $j $j $na]
+       }
+    }
+
+    # reconstruct tags and heads lists
+    if {$arctags($a) ne {} || $archeads($a) ne {}} {
+       recalcarc $a
+       recalcarc $na
+    } else {
+       set arctags($na) {}
+       set archeads($na) {}
+    }
+}
+
+# Update things for a new commit added that is a child of one
+# existing commit.  Used when cherry-picking.
+proc addnewchild {id p} {
+    global allids allparents allchildren idtags nextarc nbmp
+    global arcnos arcids arctags arcout arcend arcstart archeads growing
+    global seeds
+
+    lappend allids $id
+    set allparents($id) [list $p]
+    set allchildren($id) {}
+    set arcnos($id) {}
+    lappend seeds $id
+    incr nbmp
+    lappend allchildren($p) $id
+    set a [incr nextarc]
+    set arcstart($a) $id
+    set archeads($a) {}
+    set arctags($a) {}
+    set arcids($a) [list $p]
+    set arcend($a) $p
+    if {![info exists arcout($p)]} {
+       splitarc $p
+    }
+    lappend arcnos($p) $a
+    set arcout($id) [list $a]
+}
+
+# Returns 1 if a is an ancestor of b, -1 if b is an ancestor of a,
+# or 0 if neither is true.
+proc anc_or_desc {a b} {
+    global arcout arcstart arcend arcnos cached_isanc
+
+    if {$arcnos($a) eq $arcnos($b)} {
+       # Both are on the same arc(s); either both are the same BMP,
+       # or if one is not a BMP, the other is also not a BMP or is
+       # the BMP at end of the arc (and it only has 1 incoming arc).
+       # Or both can be BMPs with no incoming arcs.
+       if {$a eq $b || $arcnos($a) eq {}} {
+           return 0
+       }
+       # assert {[llength $arcnos($a)] == 1}
+       set arc [lindex $arcnos($a) 0]
+       set i [lsearch -exact $arcids($arc) $a]
+       set j [lsearch -exact $arcids($arc) $b]
+       if {$i < 0 || $i > $j} {
+           return 1
+       } else {
+           return -1
+       }
+    }
+
+    if {![info exists arcout($a)]} {
+       set arc [lindex $arcnos($a) 0]
+       if {[info exists arcend($arc)]} {
+           set aend $arcend($arc)
+       } else {
+           set aend {}
+       }
+       set a $arcstart($arc)
+    } else {
+       set aend $a
+    }
+    if {![info exists arcout($b)]} {
+       set arc [lindex $arcnos($b) 0]
+       if {[info exists arcend($arc)]} {
+           set bend $arcend($arc)
+       } else {
+           set bend {}
+       }
+       set b $arcstart($arc)
+    } else {
+       set bend $b
+    }
+    if {$a eq $bend} {
+       return 1
+    }
+    if {$b eq $aend} {
+       return -1
+    }
+    if {[info exists cached_isanc($a,$bend)]} {
+       if {$cached_isanc($a,$bend)} {
+           return 1
+       }
+    }
+    if {[info exists cached_isanc($b,$aend)]} {
+       if {$cached_isanc($b,$aend)} {
+           return -1
+       }
+       if {[info exists cached_isanc($a,$bend)]} {
+           return 0
+       }
+    }
+
+    set todo [list $a $b]
+    set anc($a) a
+    set anc($b) b
+    for {set i 0} {$i < [llength $todo]} {incr i} {
+       set x [lindex $todo $i]
+       if {$anc($x) eq {}} {
+           continue
+       }
+       foreach arc $arcnos($x) {
+           set xd $arcstart($arc)
+           if {$xd eq $bend} {
+               set cached_isanc($a,$bend) 1
+               set cached_isanc($b,$aend) 0
+               return 1
+           } elseif {$xd eq $aend} {
+               set cached_isanc($b,$aend) 1
+               set cached_isanc($a,$bend) 0
+               return -1
+           }
+           if {![info exists anc($xd)]} {
+               set anc($xd) $anc($x)
+               lappend todo $xd
+           } elseif {$anc($xd) ne $anc($x)} {
+               set anc($xd) {}
            }
        }
     }
-    return $res
+    set cached_isanc($a,$bend) 0
+    set cached_isanc($b,$aend) 0
+    return 0
 }
 
-proc combine_atags {l1 l2} {
-    global tagisdesc
-
-    set res [lsort -unique [concat $l1 $l2]]
-    for {set i 0} {$i < [llength $res]} {incr i} {
-       set x [lindex $res $i]
-       for {set j [expr {$i+1}]} {$j < [llength $res]} {} {
-           set y [lindex $res $j]
-           if {[info exists tagisdesc($x,$y)]} {
-               if {$tagisdesc($x,$y) < 0} {
-                   # x is an ancestor of y, exclude x
-                   set res [lreplace $res $i $i]
-                   incr i -1
-                   break
-               } else {
-                   # y is an ancestor of x, exclude y
-                   set res [lreplace $res $j $j]
+# This identifies whether $desc has an ancestor that is
+# a growing tip of the graph and which is not an ancestor of $anc
+# and returns 0 if so and 1 if not.
+# If we subsequently discover a tag on such a growing tip, and that
+# turns out to be a descendent of $anc (which it could, since we
+# don't necessarily see children before parents), then $desc
+# isn't a good choice to display as a descendent tag of
+# $anc (since it is the descendent of another tag which is
+# a descendent of $anc).  Similarly, $anc isn't a good choice to
+# display as a ancestor tag of $desc.
+#
+proc is_certain {desc anc} {
+    global arcnos arcout arcstart arcend growing problems
+
+    set certain {}
+    if {[llength $arcnos($anc)] == 1} {
+       # tags on the same arc are certain
+       if {$arcnos($desc) eq $arcnos($anc)} {
+           return 1
+       }
+       if {![info exists arcout($anc)]} {
+           # if $anc is partway along an arc, use the start of the arc instead
+           set a [lindex $arcnos($anc) 0]
+           set anc $arcstart($a)
+       }
+    }
+    if {[llength $arcnos($desc)] > 1 || [info exists arcout($desc)]} {
+       set x $desc
+    } else {
+       set a [lindex $arcnos($desc) 0]
+       set x $arcend($a)
+    }
+    if {$x == $anc} {
+       return 1
+    }
+    set anclist [list $x]
+    set dl($x) 1
+    set nnh 1
+    set ngrowanc 0
+    for {set i 0} {$i < [llength $anclist] && ($nnh > 0 || $ngrowanc > 0)} {incr i} {
+       set x [lindex $anclist $i]
+       if {$dl($x)} {
+           incr nnh -1
+       }
+       set done($x) 1
+       foreach a $arcout($x) {
+           if {[info exists growing($a)]} {
+               if {![info exists growanc($x)] && $dl($x)} {
+                   set growanc($x) 1
+                   incr ngrowanc
                }
            } else {
-               # no relation, keep going
-               incr j
+               set y $arcend($a)
+               if {[info exists dl($y)]} {
+                   if {$dl($y)} {
+                       if {!$dl($x)} {
+                           set dl($y) 0
+                           if {![info exists done($y)]} {
+                               incr nnh -1
+                           }
+                           if {[info exists growanc($x)]} {
+                               incr ngrowanc -1
+                           }
+                           set xl [list $y]
+                           for {set k 0} {$k < [llength $xl]} {incr k} {
+                               set z [lindex $xl $k]
+                               foreach c $arcout($z) {
+                                   if {[info exists arcend($c)]} {
+                                       set v $arcend($c)
+                                       if {[info exists dl($v)] && $dl($v)} {
+                                           set dl($v) 0
+                                           if {![info exists done($v)]} {
+                                               incr nnh -1
+                                           }
+                                           if {[info exists growanc($v)]} {
+                                               incr ngrowanc -1
+                                           }
+                                           lappend xl $v
+                                       }
+                                   }
+                               }
+                           }
+                       }
+                   }
+               } elseif {$y eq $anc || !$dl($x)} {
+                   set dl($y) 0
+                   lappend anclist $y
+               } else {
+                   set dl($y) 1
+                   lappend anclist $y
+                   incr nnh
+               }
            }
        }
     }
-    return $res
+    foreach x [array names growanc] {
+       if {$dl($x)} {
+           return 0
+       }
+       return 0
+    }
+    return 1
 }
 
-proc forward_pass {id children} {
-    global idtags desc_tags idheads desc_heads alldtags tagisdesc
+proc validate_arctags {a} {
+    global arctags idtags
 
-    set dtags {}
-    set dheads {}
-    foreach child $children {
-       if {[info exists idtags($child)]} {
-           set ctags [list $child]
-       } else {
-           set ctags $desc_tags($child)
+    set i -1
+    set na $arctags($a)
+    foreach id $arctags($a) {
+       incr i
+       if {![info exists idtags($id)]} {
+           set na [lreplace $na $i $i]
+           incr i -1
+       }
+    }
+    set arctags($a) $na
+}
+
+proc validate_archeads {a} {
+    global archeads idheads
+
+    set i -1
+    set na $archeads($a)
+    foreach id $archeads($a) {
+       incr i
+       if {![info exists idheads($id)]} {
+           set na [lreplace $na $i $i]
+           incr i -1
+       }
+    }
+    set archeads($a) $na
+}
+
+# Return the list of IDs that have tags that are descendents of id,
+# ignoring IDs that are descendents of IDs already reported.
+proc desctags {id} {
+    global arcnos arcstart arcids arctags idtags allparents
+    global growing cached_dtags
+
+    if {![info exists allparents($id)]} {
+       return {}
+    }
+    set t1 [clock clicks -milliseconds]
+    set argid $id
+    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+       # part-way along an arc; check that arc first
+       set a [lindex $arcnos($id) 0]
+       if {$arctags($a) ne {}} {
+           validate_arctags $a
+           set i [lsearch -exact $arcids($a) $id]
+           set tid {}
+           foreach t $arctags($a) {
+               set j [lsearch -exact $arcids($a) $t]
+               if {$j >= $i} break
+               set tid $t
+           }
+           if {$tid ne {}} {
+               return $tid
+           }
        }
-       if {$dtags eq {}} {
-           set dtags $ctags
-       } elseif {$ctags ne $dtags} {
-           set dtags [combine_dtags $dtags $ctags]
+       set id $arcstart($a)
+       if {[info exists idtags($id)]} {
+           return $id
+       }
+    }
+    if {[info exists cached_dtags($id)]} {
+       return $cached_dtags($id)
+    }
+
+    set origid $id
+    set todo [list $id]
+    set queued($id) 1
+    set nc 1
+    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+       set id [lindex $todo $i]
+       set done($id) 1
+       set ta [info exists hastaggedancestor($id)]
+       if {!$ta} {
+           incr nc -1
+       }
+       # ignore tags on starting node
+       if {!$ta && $i > 0} {
+           if {[info exists idtags($id)]} {
+               set tagloc($id) $id
+               set ta 1
+           } elseif {[info exists cached_dtags($id)]} {
+               set tagloc($id) $cached_dtags($id)
+               set ta 1
+           }
        }
-       set cheads $desc_heads($child)
-       if {$dheads eq {}} {
-           set dheads $cheads
-       } elseif {$cheads ne $dheads} {
-           set dheads [lsort -unique [concat $dheads $cheads]]
+       foreach a $arcnos($id) {
+           set d $arcstart($a)
+           if {!$ta && $arctags($a) ne {}} {
+               validate_arctags $a
+               if {$arctags($a) ne {}} {
+                   lappend tagloc($id) [lindex $arctags($a) end]
+               }
+           }
+           if {$ta || $arctags($a) ne {}} {
+               set tomark [list $d]
+               for {set j 0} {$j < [llength $tomark]} {incr j} {
+                   set dd [lindex $tomark $j]
+                   if {![info exists hastaggedancestor($dd)]} {
+                       if {[info exists done($dd)]} {
+                           foreach b $arcnos($dd) {
+                               lappend tomark $arcstart($b)
+                           }
+                           if {[info exists tagloc($dd)]} {
+                               unset tagloc($dd)
+                           }
+                       } elseif {[info exists queued($dd)]} {
+                           incr nc -1
+                       }
+                       set hastaggedancestor($dd) 1
+                   }
+               }
+           }
+           if {![info exists queued($d)]} {
+               lappend todo $d
+               set queued($d) 1
+               if {![info exists hastaggedancestor($d)]} {
+                   incr nc
+               }
+           }
        }
     }
-    set desc_tags($id) $dtags
-    if {[info exists idtags($id)]} {
-       set adt $dtags
-       foreach tag $dtags {
-           set adt [concat $adt $alldtags($tag)]
+    set tags {}
+    foreach id [array names tagloc] {
+       if {![info exists hastaggedancestor($id)]} {
+           foreach t $tagloc($id) {
+               if {[lsearch -exact $tags $t] < 0} {
+                   lappend tags $t
+               }
+           }
        }
-       set adt [lsort -unique $adt]
-       set alldtags($id) $adt
-       foreach tag $adt {
-           set tagisdesc($id,$tag) -1
-           set tagisdesc($tag,$id) 1
+    }
+    set t2 [clock clicks -milliseconds]
+    set loopix $i
+
+    # remove tags that are descendents of other tags
+    for {set i 0} {$i < [llength $tags]} {incr i} {
+       set a [lindex $tags $i]
+       for {set j 0} {$j < $i} {incr j} {
+           set b [lindex $tags $j]
+           set r [anc_or_desc $a $b]
+           if {$r == 1} {
+               set tags [lreplace $tags $j $j]
+               incr j -1
+               incr i -1
+           } elseif {$r == -1} {
+               set tags [lreplace $tags $i $i]
+               incr i -1
+               break
+           }
        }
     }
-    if {[info exists idheads($id)]} {
-       set dheads [concat $dheads $idheads($id)]
+
+    if {[array names growing] ne {}} {
+       # graph isn't finished, need to check if any tag could get
+       # eclipsed by another tag coming later.  Simply ignore any
+       # tags that could later get eclipsed.
+       set ctags {}
+       foreach t $tags {
+           if {[is_certain $t $origid]} {
+               lappend ctags $t
+           }
+       }
+       if {$tags eq $ctags} {
+           set cached_dtags($origid) $tags
+       } else {
+           set tags $ctags
+       }
+    } else {
+       set cached_dtags($origid) $tags
+    }
+    set t3 [clock clicks -milliseconds]
+    if {0 && $t3 - $t1 >= 100} {
+       puts "iterating descendents ($loopix/[llength $todo] nodes) took\
+           [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
     }
-    set desc_heads($id) $dheads
+    return $tags
 }
 
-proc getallclines {fd} {
-    global allparents allchildren allcommits allcstart
-    global desc_tags anc_tags idtags tagisdesc allids
-    global idheads travindex
-
-    while {[gets $fd line] >= 0} {
-       set id [lindex $line 0]
-       lappend allids $id
-       set olds [lrange $line 1 end]
-       set allparents($id) $olds
-       if {![info exists allchildren($id)]} {
-           set allchildren($id) {}
+proc anctags {id} {
+    global arcnos arcids arcout arcend arctags idtags allparents
+    global growing cached_atags
+
+    if {![info exists allparents($id)]} {
+       return {}
+    }
+    set t1 [clock clicks -milliseconds]
+    set argid $id
+    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+       # part-way along an arc; check that arc first
+       set a [lindex $arcnos($id) 0]
+       if {$arctags($a) ne {}} {
+           validate_arctags $a
+           set i [lsearch -exact $arcids($a) $id]
+           foreach t $arctags($a) {
+               set j [lsearch -exact $arcids($a) $t]
+               if {$j > $i} {
+                   return $t
+               }
+           }
        }
-       foreach p $olds {
-           lappend allchildren($p) $id
+       if {![info exists arcend($a)]} {
+           return {}
+       }
+       set id $arcend($a)
+       if {[info exists idtags($id)]} {
+           return $id
+       }
+    }
+    if {[info exists cached_atags($id)]} {
+       return $cached_atags($id)
+    }
+
+    set origid $id
+    set todo [list $id]
+    set queued($id) 1
+    set taglist {}
+    set nc 1
+    for {set i 0} {$i < [llength $todo] && $nc > 0} {incr i} {
+       set id [lindex $todo $i]
+       set done($id) 1
+       set td [info exists hastaggeddescendent($id)]
+       if {!$td} {
+           incr nc -1
+       }
+       # ignore tags on starting node
+       if {!$td && $i > 0} {
+           if {[info exists idtags($id)]} {
+               set tagloc($id) $id
+               set td 1
+           } elseif {[info exists cached_atags($id)]} {
+               set tagloc($id) $cached_atags($id)
+               set td 1
+           }
        }
-       # compute nearest tagged descendents as we go
-       # also compute descendent heads
-       forward_pass $id $allchildren($id)
-       if {[clock clicks -milliseconds] - $allcstart >= 50} {
-           fileevent $fd readable {}
-           after idle restartgetall $fd
-           return
+       foreach a $arcout($id) {
+           if {!$td && $arctags($a) ne {}} {
+               validate_arctags $a
+               if {$arctags($a) ne {}} {
+                   lappend tagloc($id) [lindex $arctags($a) 0]
+               }
+           }
+           if {![info exists arcend($a)]} continue
+           set d $arcend($a)
+           if {$td || $arctags($a) ne {}} {
+               set tomark [list $d]
+               for {set j 0} {$j < [llength $tomark]} {incr j} {
+                   set dd [lindex $tomark $j]
+                   if {![info exists hastaggeddescendent($dd)]} {
+                       if {[info exists done($dd)]} {
+                           foreach b $arcout($dd) {
+                               if {[info exists arcend($b)]} {
+                                   lappend tomark $arcend($b)
+                               }
+                           }
+                           if {[info exists tagloc($dd)]} {
+                               unset tagloc($dd)
+                           }
+                       } elseif {[info exists queued($dd)]} {
+                           incr nc -1
+                       }
+                       set hastaggeddescendent($dd) 1
+                   }
+               }
+           }
+           if {![info exists queued($d)]} {
+               lappend todo $d
+               set queued($d) 1
+               if {![info exists hastaggeddescendent($d)]} {
+                   incr nc
+               }
+           }
        }
     }
-    if {[eof $fd]} {
-       set travindex [llength $allids]
-       set allcommits "traversing"
-       after idle restartatags
-       if {[catch {close $fd} err]} {
-           error_popup "Error reading full commit graph: $err.\n\
-                        Results may be incomplete."
+    set t2 [clock clicks -milliseconds]
+    set loopix $i
+    set tags {}
+    foreach id [array names tagloc] {
+       if {![info exists hastaggeddescendent($id)]} {
+           foreach t $tagloc($id) {
+               if {[lsearch -exact $tags $t] < 0} {
+                   lappend tags $t
+               }
+           }
        }
     }
-}
 
-# walk backward through the tree and compute nearest tagged ancestors
-proc restartatags {} {
-    global allids allparents idtags anc_tags travindex
-
-    set t0 [clock clicks -milliseconds]
-    set i $travindex
-    while {[incr i -1] >= 0} {
-       set id [lindex $allids $i]
-       set atags {}
-       foreach p $allparents($id) {
-           if {[info exists idtags($p)]} {
-               set ptags [list $p]
-           } else {
-               set ptags $anc_tags($p)
+    # remove tags that are ancestors of other tags
+    for {set i 0} {$i < [llength $tags]} {incr i} {
+       set a [lindex $tags $i]
+       for {set j 0} {$j < $i} {incr j} {
+           set b [lindex $tags $j]
+           set r [anc_or_desc $a $b]
+           if {$r == -1} {
+               set tags [lreplace $tags $j $j]
+               incr j -1
+               incr i -1
+           } elseif {$r == 1} {
+               set tags [lreplace $tags $i $i]
+               incr i -1
+               break
            }
-           if {$atags eq {}} {
-               set atags $ptags
-           } elseif {$ptags ne $atags} {
-               set atags [combine_atags $atags $ptags]
+       }
+    }
+
+    if {[array names growing] ne {}} {
+       # graph isn't finished, need to check if any tag could get
+       # eclipsed by another tag coming later.  Simply ignore any
+       # tags that could later get eclipsed.
+       set ctags {}
+       foreach t $tags {
+           if {[is_certain $origid $t]} {
+               lappend ctags $t
            }
        }
-       set anc_tags($id) $atags
-       if {[clock clicks -milliseconds] - $t0 >= 50} {
-           set travindex $i
-           after idle restartatags
-           return
+       if {$tags eq $ctags} {
+           set cached_atags($origid) $tags
+       } else {
+           set tags $ctags
        }
+    } else {
+       set cached_atags($origid) $tags
     }
-    set allcommits "done"
-    set travindex 0
-    notbusy allcommits
-    dispneartags
+    set t3 [clock clicks -milliseconds]
+    if {0 && $t3 - $t1 >= 100} {
+       puts "iterating ancestors ($loopix/[llength $todo] nodes) took\
+           [expr {$t2-$t1}]+[expr {$t3-$t2}]ms, $nc candidates left"
+    }
+    return $tags
 }
 
-# update the desc_tags and anc_tags arrays for a new tag just added
-proc addedtag {id} {
-    global desc_tags anc_tags allparents allchildren allcommits
-    global idtags tagisdesc alldtags
-
-    if {![info exists desc_tags($id)]} return
-    set adt $desc_tags($id)
-    foreach t $desc_tags($id) {
-       set adt [concat $adt $alldtags($t)]
-    }
-    set adt [lsort -unique $adt]
-    set alldtags($id) $adt
-    foreach t $adt {
-       set tagisdesc($id,$t) -1
-       set tagisdesc($t,$id) 1
-    }
-    if {[info exists anc_tags($id)]} {
-       set todo $anc_tags($id)
-       while {$todo ne {}} {
-           set do [lindex $todo 0]
-           set todo [lrange $todo 1 end]
-           if {[info exists tagisdesc($id,$do)]} continue
-           set tagisdesc($do,$id) -1
-           set tagisdesc($id,$do) 1
-           if {[info exists anc_tags($do)]} {
-               set todo [concat $todo $anc_tags($do)]
-           }
-       }
-    }
-
-    set lastold $desc_tags($id)
-    set lastnew [list $id]
-    set nup 0
-    set nch 0
-    set todo $allparents($id)
-    while {$todo ne {}} {
-       set do [lindex $todo 0]
-       set todo [lrange $todo 1 end]
-       if {![info exists desc_tags($do)]} continue
-       if {$desc_tags($do) ne $lastold} {
-           set lastold $desc_tags($do)
-           set lastnew [combine_dtags $lastold [list $id]]
-           incr nch
-       }
-       if {$lastold eq $lastnew} continue
-       set desc_tags($do) $lastnew
-       incr nup
-       if {![info exists idtags($do)]} {
-           set todo [concat $todo $allparents($do)]
-       }
-    }
-
-    if {![info exists anc_tags($id)]} return
-    set lastold $anc_tags($id)
-    set lastnew [list $id]
-    set nup 0
-    set nch 0
-    set todo $allchildren($id)
-    while {$todo ne {}} {
-       set do [lindex $todo 0]
-       set todo [lrange $todo 1 end]
-       if {![info exists anc_tags($do)]} continue
-       if {$anc_tags($do) ne $lastold} {
-           set lastold $anc_tags($do)
-           set lastnew [combine_atags $lastold [list $id]]
-           incr nch
-       }
-       if {$lastold eq $lastnew} continue
-       set anc_tags($do) $lastnew
-       incr nup
-       if {![info exists idtags($do)]} {
-           set todo [concat $todo $allchildren($do)]
-       }
-    }
-}
-
-# update the desc_heads array for a new head just added
-proc addedhead {hid head} {
-    global desc_heads allparents headids idheads
-
-    set headids($head) $hid
-    lappend idheads($hid) $head
-
-    set todo [list $hid]
-    while {$todo ne {}} {
-       set do [lindex $todo 0]
-       set todo [lrange $todo 1 end]
-       if {![info exists desc_heads($do)] ||
-           [lsearch -exact $desc_heads($do) $head] >= 0} continue
-       set oldheads $desc_heads($do)
-       lappend desc_heads($do) $head
-       set heads $desc_heads($do)
-       while {1} {
-           set p $allparents($do)
-           if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
-               $desc_heads($p) ne $oldheads} break
-           set do $p
-           set desc_heads($do) $heads
+# Return the list of IDs that have heads that are descendents of id,
+# including id itself if it has a head.
+proc descheads {id} {
+    global arcnos arcstart arcids archeads idheads cached_dheads
+    global allparents
+
+    if {![info exists allparents($id)]} {
+       return {}
+    }
+    set aret {}
+    if {[llength $arcnos($id)] == 1 && [llength $allparents($id)] == 1} {
+       # part-way along an arc; check it first
+       set a [lindex $arcnos($id) 0]
+       if {$archeads($a) ne {}} {
+           validate_archeads $a
+           set i [lsearch -exact $arcids($a) $id]
+           foreach t $archeads($a) {
+               set j [lsearch -exact $arcids($a) $t]
+               if {$j > $i} break
+               lappend aret $t
+           }
+       }
+       set id $arcstart($a)
+    }
+    set origid $id
+    set todo [list $id]
+    set seen($id) 1
+    set ret {}
+    for {set i 0} {$i < [llength $todo]} {incr i} {
+       set id [lindex $todo $i]
+       if {[info exists cached_dheads($id)]} {
+           set ret [concat $ret $cached_dheads($id)]
+       } else {
+           if {[info exists idheads($id)]} {
+               lappend ret $id
+           }
+           foreach a $arcnos($id) {
+               if {$archeads($a) ne {}} {
+                   validate_archeads $a
+                   if {$archeads($a) ne {}} {
+                       set ret [concat $ret $archeads($a)]
+                   }
+               }
+               set d $arcstart($a)
+               if {![info exists seen($d)]} {
+                   lappend todo $d
+                   set seen($d) 1
+               }
+           }
        }
-       set todo [concat $todo $p]
     }
+    set ret [lsort -unique $ret]
+    set cached_dheads($origid) $ret
+    return [concat $ret $aret]
 }
 
-# update the desc_heads array for a head just removed
-proc removedhead {hid head} {
-    global desc_heads allparents headids idheads
+proc addedtag {id} {
+    global arcnos arcout cached_dtags cached_atags
 
-    unset headids($head)
-    if {$idheads($hid) eq $head} {
-       unset idheads($hid)
-    } else {
-       set i [lsearch -exact $idheads($hid) $head]
-       if {$i >= 0} {
-           set idheads($hid) [lreplace $idheads($hid) $i $i]
-       }
+    if {![info exists arcnos($id)]} return
+    if {![info exists arcout($id)]} {
+       recalcarc [lindex $arcnos($id) 0]
     }
+    catch {unset cached_dtags}
+    catch {unset cached_atags}
+}
 
-    set todo [list $hid]
-    while {$todo ne {}} {
-       set do [lindex $todo 0]
-       set todo [lrange $todo 1 end]
-       if {![info exists desc_heads($do)]} continue
-       set i [lsearch -exact $desc_heads($do) $head]
-       if {$i < 0} continue
-       set oldheads $desc_heads($do)
-       set heads [lreplace $desc_heads($do) $i $i]
-       while {1} {
-           set desc_heads($do) $heads
-           set p $allparents($do)
-           if {[llength $p] != 1 || ![info exists desc_heads($p)] ||
-               $desc_heads($p) ne $oldheads} break
-           set do $p
-       }
-       set todo [concat $todo $p]
+proc addedhead {hid head} {
+    global arcnos arcout cached_dheads
+
+    if {![info exists arcnos($hid)]} return
+    if {![info exists arcout($hid)]} {
+       recalcarc [lindex $arcnos($hid) 0]
     }
+    catch {unset cached_dheads}
 }
 
-# update things for a head moved to a child of its previous location
-proc movedhead {id name} {
-    global headids idheads
+proc removedhead {hid head} {
+    global cached_dheads
 
-    set oldid $headids($name)
-    set headids($name) $id
-    if {$idheads($oldid) eq $name} {
-       unset idheads($oldid)
-    } else {
-       set i [lsearch -exact $idheads($oldid) $name]
-       if {$i >= 0} {
-           set idheads($oldid) [lreplace $idheads($oldid) $i $i]
-       }
-    }
-    lappend idheads($id) $name
+    catch {unset cached_dheads}
 }
 
-proc changedrefs {} {
-    global desc_heads desc_tags anc_tags allcommits allids
-    global allchildren allparents idtags travindex
+proc movedhead {hid head} {
+    global arcnos arcout cached_dheads
 
-    if {![info exists allcommits]} return
-    catch {unset desc_heads}
-    catch {unset desc_tags}
-    catch {unset anc_tags}
-    catch {unset alldtags}
-    catch {unset tagisdesc}
-    foreach id $allids {
-       forward_pass $id $allchildren($id)
+    if {![info exists arcnos($hid)]} return
+    if {![info exists arcout($hid)]} {
+       recalcarc [lindex $arcnos($hid) 0]
     }
-    if {$allcommits ne "reading"} {
-       set travindex [llength $allids]
-       if {$allcommits ne "traversing"} {
-           set allcommits "traversing"
-           after idle restartatags
+    catch {unset cached_dheads}
+}
+
+proc changedrefs {} {
+    global cached_dheads cached_dtags cached_atags
+    global arctags archeads arcnos arcout idheads idtags
+
+    foreach id [concat [array names idheads] [array names idtags]] {
+       if {[info exists arcnos($id)] && ![info exists arcout($id)]} {
+           set a [lindex $arcnos($id) 0]
+           if {![info exists donearc($a)]} {
+               recalcarc $a
+               set donearc($a) 1
+           }
        }
     }
+    catch {unset cached_dtags}
+    catch {unset cached_atags}
+    catch {unset cached_dheads}
 }
 
 proc rereadrefs {} {
@@ -5817,7 +7113,7 @@ proc listrefs {id} {
 }
 
 proc showtag {tag isnew} {
-    global ctext tagcontents tagids linknum
+    global ctext tagcontents tagids linknum tagobjid
 
     if {$isnew} {
        addtohistory [list showtag $tag 0]
@@ -5825,6 +7121,11 @@ proc showtag {tag isnew} {
     $ctext conf -state normal
     clear_ctext
     set linknum 0
+    if {![info exists tagcontents($tag)]} {
+       catch {
+           set tagcontents($tag) [exec git cat-file tag $tagobjid($tag)]
+       }
+    }
     if {[info exists tagcontents($tag)]} {
        set text $tagcontents($tag)
     } else {
@@ -5844,9 +7145,9 @@ proc doquit {} {
 
 proc doprefs {} {
     global maxwidth maxgraphpct diffopts
-    global oldprefs prefstop showneartags
-    global bgcolor fgcolor ctext diffcolors
-    global uifont
+    global oldprefs prefstop showneartags showlocalchanges
+    global bgcolor fgcolor ctext diffcolors selectbgcolor
+    global uifont tabstop
 
     set top .gitkprefs
     set prefstop $top
@@ -5854,7 +7155,7 @@ proc doprefs {} {
        raise $top
        return
     }
-    foreach v {maxwidth maxgraphpct diffopts showneartags} {
+    foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
        set oldprefs($v) [set $v]
     }
     toplevel $top
@@ -5871,6 +7172,11 @@ proc doprefs {} {
        -font optionfont
     spinbox $top.maxpct -from 1 -to 100 -width 4 -textvariable maxgraphpct
     grid x $top.maxpctl $top.maxpct -sticky w
+    frame $top.showlocal
+    label $top.showlocal.l -text "Show local changes" -font optionfont
+    checkbutton $top.showlocal.b -variable showlocalchanges
+    pack $top.showlocal.b $top.showlocal.l -side left
+    grid x $top.showlocal -sticky w
 
     label $top.ddisp -text "Diff display options"
     $top.ddisp configure -font $uifont
@@ -5884,6 +7190,9 @@ proc doprefs {} {
     checkbutton $top.ntag.b -variable showneartags
     pack $top.ntag.b $top.ntag.l -side left
     grid x $top.ntag -sticky w
+    label $top.tabstopl -text "tabstop" -font optionfont
+    spinbox $top.tabstop -from 1 -to 20 -width 4 -textvariable tabstop
+    grid x $top.tabstopl $top.tabstop -sticky w
 
     label $top.cdisp -text "Colors: press to choose"
     $top.cdisp configure -font $uifont
@@ -5912,6 +7221,10 @@ proc doprefs {} {
                      "diff hunk header" \
                      [list $ctext tag conf hunksep -foreground]]
     grid x $top.hunksepbut $top.hunksep -sticky w
+    label $top.selbgsep -padx 40 -relief sunk -background $selectbgcolor
+    button $top.selbgbut -text "Select bg" -font optionfont \
+       -command [list choosecolor selectbgcolor 0 $top.selbgsep background setselbg]
+    grid x $top.selbgbut $top.selbgsep -sticky w
 
     frame $top.buts
     button $top.buts.ok -text "OK" -command prefsok -default active
@@ -5936,6 +7249,16 @@ proc choosecolor {v vi w x cmd} {
     eval $cmd $c
 }
 
+proc setselbg {c} {
+    global bglist cflist
+    foreach w $bglist {
+       $w configure -selectbackground $c
+    }
+    $cflist tag configure highlight \
+       -background [$cflist cget -selectbackground]
+    allcanvs itemconf secsel -fill $c
+}
+
 proc setbg {c} {
     global bglist
 
@@ -5956,9 +7279,9 @@ proc setfg {c} {
 
 proc prefscan {} {
     global maxwidth maxgraphpct diffopts
-    global oldprefs prefstop showneartags
+    global oldprefs prefstop showneartags showlocalchanges
 
-    foreach v {maxwidth maxgraphpct diffopts showneartags} {
+    foreach v {maxwidth maxgraphpct diffopts showneartags showlocalchanges} {
        set $v $oldprefs($v)
     }
     catch {destroy $prefstop}
@@ -5967,10 +7290,19 @@ proc prefscan {} {
 
 proc prefsok {} {
     global maxwidth maxgraphpct
-    global oldprefs prefstop showneartags
+    global oldprefs prefstop showneartags showlocalchanges
+    global charspc ctext tabstop
 
     catch {destroy $prefstop}
     unset prefstop
+    $ctext configure -tabs "[expr {$tabstop * $charspc}]"
+    if {$showlocalchanges != $oldprefs(showlocalchanges)} {
+       if {$showlocalchanges} {
+           doshowlocalchanges
+       } else {
+           dohidelocalchanges
+       }
+    }
     if {$maxwidth != $oldprefs(maxwidth)
        || $maxgraphpct != $oldprefs(maxgraphpct)} {
        redisplay
@@ -5980,7 +7312,10 @@ proc prefsok {} {
 }
 
 proc formatdate {d} {
-    return [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+    if {$d ne {}} {
+       set d [clock format $d -format "%Y-%m-%d %H:%M:%S"]
+    }
+    return $d
 }
 
 # This list of encoding names and aliases is distilled from
@@ -6276,6 +7611,7 @@ if {$tclencoding == {}} {
 set mainfont {Helvetica 9}
 set textfont {Courier 9}
 set uifont {Helvetica 9 bold}
+set tabstop 8
 set findmergefiles 0
 set maxgraphpct 50
 set maxwidth 16
@@ -6287,45 +7623,65 @@ set mingaplen 30
 set cmitmode "patch"
 set wrapcomment "none"
 set showneartags 1
+set maxrefs 20
+set maxlinelen 200
+set showlocalchanges 1
 
 set colors {green red blue magenta darkgrey brown orange}
 set bgcolor white
 set fgcolor black
 set diffcolors {red "#00a000" blue}
+set selectbgcolor gray85
 
 catch {source ~/.gitk}
 
 font create optionfont -family sans-serif -size -12
 
+# check that we can find a .git directory somewhere...
+if {[catch {set gitdir [gitdir]}]} {
+    show_error {} . "Cannot find a git repository here."
+    exit 1
+}
+if {![file isdirectory $gitdir]} {
+    show_error {} . "Cannot find the git directory \"$gitdir\"."
+    exit 1
+}
+
 set revtreeargs {}
+set cmdline_files {}
+set i 0
 foreach arg $argv {
-    switch -regexp -- $arg {
-       "^$" { }
-       "^-d" { set datemode 1 }
+    switch -- $arg {
+       "" { }
+       "-d" { set datemode 1 }
+       "--" {
+           set cmdline_files [lrange $argv [expr {$i + 1}] end]
+           break
+       }
        default {
            lappend revtreeargs $arg
        }
     }
+    incr i
 }
 
-# check that we can find a .git directory somewhere...
-set gitdir [gitdir]
-if {![file isdirectory $gitdir]} {
-    show_error {} . "Cannot find the git directory \"$gitdir\"."
-    exit 1
-}
-
-set cmdline_files {}
-set i [lsearch -exact $revtreeargs "--"]
-if {$i >= 0} {
-    set cmdline_files [lrange $revtreeargs [expr {$i + 1}] end]
-    set revtreeargs [lrange $revtreeargs 0 [expr {$i - 1}]]
-} elseif {$revtreeargs ne {}} {
+if {$i >= [llength $argv] && $revtreeargs ne {}} {
+    # no -- on command line, but some arguments (other than -d)
     if {[catch {
        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
        set cmdline_files [split $f "\n"]
        set n [llength $cmdline_files]
        set revtreeargs [lrange $revtreeargs 0 end-$n]
+       # Unfortunately git rev-parse doesn't produce an error when
+       # something is both a revision and a filename.  To be consistent
+       # with git log and git rev-list, check revtreeargs for filenames.
+       foreach arg $revtreeargs {
+           if {[file exists $arg]} {
+               show_error {} . "Ambiguous argument '$arg': both revision\
+                                and filename"
+               exit 1
+           }
+       }
     } err]} {
        # unfortunately we get both stdout and stderr in $err,
        # so look for "fatal:".
@@ -6338,6 +7694,11 @@ if {$i >= 0} {
     }
 }
 
+set nullid "0000000000000000000000000000000000000000"
+set nullid2 "0000000000000000000000000000000000000001"
+
+
+set runq {}
 set history {}
 set historyindex 0
 set fh_serial 0
@@ -6347,6 +7708,7 @@ set searchdirn -forwards
 set boldrows {}
 set boldnamerows {}
 set diffelide {0 0}
+set markingmatches 0
 
 set optim_delay 16
 
@@ -6362,8 +7724,14 @@ set cmdlineok 0
 set stopped 0
 set stuffsaved 0
 set patchnum 0
+set lookingforhead 0
+set localirow -1
+set localfrow -1
+set lserial 0
 setcoords
 makewindow
+# wait for the window to become visible
+tkwait visibility .
 wm title . "[file tail $argv0]: [file tail [pwd]]"
 readrefs
 
index e02e90f0429be0d2a69b76571101f20b8f75530f..7186cede2fad816ef4d497ac2c863869d5db76a0 100644 (file)
@@ -79,4 +79,3 @@ Originally written by:
 
 Any comment/question/concern to:
   Git mailing list <git@vger.kernel.org>
-
index 9f0822fab3d10fd5a83234f761847d7818c6da0a..096313bec0a3f39596fda96d2f968181a7b7cd84 100644 (file)
@@ -181,10 +181,15 @@ table.diff_tree {
        font-family: monospace;
 }
 
+table.combined.diff_tree th {
+       text-align: center;
+}
+
 table.combined.diff_tree td {
        padding-right: 24px;
 }
 
+table.combined.diff_tree th.link,
 table.combined.diff_tree td.link {
        padding: 0px 2px;
 }
@@ -276,6 +281,12 @@ table.diff_tree span.file_status.copied {
   color: #70a070;
 }
 
+/* noage: "No commits" */
+table.project_list td.noage {
+       color: #808080;
+       font-style: italic;
+}
+
 /* age2: 60*60*24*2 <= age */
 table.project_list td.age2, table.blame td.age2 {
        font-style: italic;
index 5c7011a37b2e4dab8c38de54a3e531d2fa16ddbe..f282a677aaed805528770a6df594cbd452b484a4 100755 (executable)
@@ -71,6 +71,9 @@ our $logo_label = "git homepage";
 # source of projects list
 our $projects_list = "++GITWEB_LIST++";
 
+# the width (in characters) of the projects list "Description" column
+our $projects_list_description_width = 25;
+
 # default order of projects list
 # valid values are none, project, descr, owner, and age
 our $default_projects_order = "project";
@@ -94,6 +97,66 @@ our $default_text_plain_charset  = undef;
 # (relative to the current git repository)
 our $mimetypes_file = undef;
 
+# assume this charset if line contains non-UTF-8 characters;
+# it should be valid encoding (see Encoding::Supported(3pm) for list),
+# for which encoding all byte sequences are valid, for example
+# 'iso-8859-1' aka 'latin1' (it is decoded without checking, so it
+# could be even 'utf-8' for the old behavior)
+our $fallback_encoding = 'latin1';
+
+# rename detection options for git-diff and git-diff-tree
+# - default is '-M', with the cost proportional to
+#   (number of removed files) * (number of new files).
+# - more costly is '-C' (which implies '-M'), with the cost proportional to
+#   (number of changed files + number of removed files) * (number of new files)
+# - even more costly is '-C', '--find-copies-harder' with cost
+#   (number of files in the original tree) * (number of new files)
+# - one might want to include '-B' option, e.g. '-B', '-M'
+our @diff_opts = ('-M'); # taken from git_commit
+
+# information about snapshot formats that gitweb is capable of serving
+our %known_snapshot_formats = (
+       # name => {
+       #       'display' => display name,
+       #       'type' => mime type,
+       #       'suffix' => filename suffix,
+       #       'format' => --format for git-archive,
+       #       'compressor' => [compressor command and arguments]
+       #                       (array reference, optional)}
+       #
+       'tgz' => {
+               'display' => 'tar.gz',
+               'type' => 'application/x-gzip',
+               'suffix' => '.tar.gz',
+               'format' => 'tar',
+               'compressor' => ['gzip']},
+
+       'tbz2' => {
+               'display' => 'tar.bz2',
+               'type' => 'application/x-bzip2',
+               'suffix' => '.tar.bz2',
+               'format' => 'tar',
+               'compressor' => ['bzip2']},
+
+       'zip' => {
+               'display' => 'zip',
+               'type' => 'application/x-zip',
+               'suffix' => '.zip',
+               'format' => 'zip'},
+);
+
+# Aliases so we understand old gitweb.snapshot values in repository
+# configuration.
+our %known_snapshot_format_aliases = (
+       'gzip'  => 'tgz',
+       'bzip2' => 'tbz2',
+
+       # backward compatibility: legacy gitweb config support
+       'x-gzip' => undef, 'gz' => undef,
+       'x-bzip2' => undef, 'bz2' => undef,
+       'x-zip' => undef, '' => undef,
+);
+
 # You define site-wide feature defaults here; override them with
 # $GITWEB_CONFIG as necessary.
 our %feature = (
@@ -124,20 +187,22 @@ our %feature = (
                'override' => 0,
                'default' => [0]},
 
-       # Enable the 'snapshot' link, providing a compressed tarball of any
+       # Enable the 'snapshot' link, providing a compressed archive of any
        # tree. This can potentially generate high traffic if you have large
        # project.
 
+       # Value is a list of formats defined in %known_snapshot_formats that
+       # you wish to offer.
        # To disable system wide have in $GITWEB_CONFIG
-       # $feature{'snapshot'}{'default'} = [undef];
+       # $feature{'snapshot'}{'default'} = [];
        # To have project specific config enable override in $GITWEB_CONFIG
        # $feature{'snapshot'}{'override'} = 1;
-       # and in project config gitweb.snapshot = none|gzip|bzip2;
+       # and in project config, a comma-separated list of formats or "none"
+       # to disable.  Example: gitweb.snapshot = tbz2,zip;
        'snapshot' => {
                'sub' => \&feature_snapshot,
                'override' => 0,
-               #         => [content-encoding, suffix, program]
-               'default' => ['x-gzip', 'gz', 'gzip']},
+               'default' => ['tgz']},
 
        # Enable text search, which will list the commits which match author,
        # committer or commit text to a given string.  Enabled by default.
@@ -236,26 +301,15 @@ sub feature_blame {
 }
 
 sub feature_snapshot {
-       my ($ctype, $suffix, $command) = @_;
+       my (@fmts) = @_;
 
        my ($val) = git_get_project_config('snapshot');
 
-       if ($val eq 'gzip') {
-               return ('x-gzip', 'gz', 'gzip');
-       } elsif ($val eq 'bzip2') {
-               return ('x-bzip2', 'bz2', 'bzip2');
-       } elsif ($val eq 'none') {
-               return ();
+       if ($val) {
+               @fmts = ($val eq 'none' ? () : split /\s*[,\s]\s*/, $val);
        }
 
-       return ($ctype, $suffix, $command);
-}
-
-sub gitweb_have_snapshot {
-       my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
-       my $have_snapshot = (defined $ctype && defined $suffix);
-
-       return $have_snapshot;
+       return @fmts;
 }
 
 sub feature_grep {
@@ -298,15 +352,17 @@ sub check_export_ok {
                (!$export_ok || -e "$dir/$export_ok"));
 }
 
-# rename detection options for git-diff and git-diff-tree
-# - default is '-M', with the cost proportional to
-#   (number of removed files) * (number of new files).
-# - more costly is '-C' (or '-C', '-M'), with the cost proportional to
-#   (number of changed files + number of removed files) * (number of new files)
-# - even more costly is '-C', '--find-copies-harder' with cost
-#   (number of files in the original tree) * (number of new files)
-# - one might want to include '-B' option, e.g. '-B', '-M'
-our @diff_opts = ('-M'); # taken from git_commit
+# process alternate names for backward compatibility
+# filter out unsupported (unknown) snapshot formats
+sub filter_snapshot_fmts {
+       my @fmts = @_;
+
+       @fmts = map {
+               exists $known_snapshot_format_aliases{$_} ?
+                      $known_snapshot_format_aliases{$_} : $_} @fmts;
+       @fmts = grep(exists $known_snapshot_formats{$_}, @fmts);
+
+}
 
 our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++";
 do $GITWEB_CONFIG if -e $GITWEB_CONFIG;
@@ -374,6 +430,22 @@ if (defined $hash_base) {
        }
 }
 
+my %allowed_options = (
+       "--no-merges" => [ qw(rss atom log shortlog history) ],
+);
+
+our @extra_options = $cgi->param('opt');
+if (defined @extra_options) {
+       foreach my $opt (@extra_options) {
+               if (not exists $allowed_options{$opt}) {
+                       die_error(undef, "Invalid option parameter");
+               }
+               if (not grep(/^$action$/, @{$allowed_options{$opt}})) {
+                       die_error(undef, "Invalid option parameter for this action");
+               }
+       }
+}
+
 our $hash_parent_base = $cgi->param('hpb');
 if (defined $hash_parent_base) {
        if (!validate_refname($hash_parent_base)) {
@@ -533,6 +605,8 @@ sub href(%) {
                order => "o",
                searchtext => "s",
                searchtype => "st",
+               snapshot_format => "sf",
+               extra_options => "opt",
        );
        my %mapping = @mapping;
 
@@ -555,7 +629,13 @@ sub href(%) {
        for (my $i = 0; $i < @mapping; $i += 2) {
                my ($name, $symbol) = ($mapping[$i], $mapping[$i+1]);
                if (defined $params{$name}) {
-                       push @result, $symbol . "=" . esc_param($params{$name});
+                       if (ref($params{$name}) eq "ARRAY") {
+                               foreach my $par (@{$params{$name}}) {
+                                       push @result, $symbol . "=" . esc_param($par);
+                               }
+                       } else {
+                               push @result, $symbol . "=" . esc_param($params{$name});
+                       }
                }
        }
        $href .= "?" . join(';', @result) if scalar @result;
@@ -600,6 +680,20 @@ sub validate_refname {
        return $input;
 }
 
+# decode sequences of octets in utf8 into Perl's internal form,
+# which is utf-8 with utf8 flag set if needed.  gitweb writes out
+# in utf-8 thanks to "binmode STDOUT, ':utf8'" at beginning
+sub to_utf8 {
+       my $str = shift;
+       my $res;
+       eval { $res = decode_utf8($str, Encode::FB_CROAK); };
+       if (defined $res) {
+               return $res;
+       } else {
+               return decode($fallback_encoding, $str, Encode::FB_DEFAULT);
+       }
+}
+
 # quote unsafe chars, but keep the slash, even when it's not
 # correct, but quoted slashes look too horrible in bookmarks
 sub esc_param {
@@ -624,7 +718,7 @@ sub esc_html ($;%) {
        my $str = shift;
        my %opts = @_;
 
-       $str = decode_utf8($str);
+       $str = to_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
                $str =~ s/ /&nbsp;/g;
@@ -638,7 +732,7 @@ sub esc_path {
        my $str = shift;
        my %opts = @_;
 
-       $str = decode_utf8($str);
+       $str = to_utf8($str);
        $str = $cgi->escapeHTML($str);
        if ($opts{'-nbsp'}) {
                $str =~ s/ /&nbsp;/g;
@@ -801,11 +895,25 @@ sub age_string {
        return $age_str;
 }
 
+use constant {
+       S_IFINVALID => 0030000,
+       S_IFGITLINK => 0160000,
+};
+
+# submodule/subproject, a commit object reference
+sub S_ISGITLINK($) {
+       my $mode = shift;
+
+       return (($mode & S_IFMT) == S_IFGITLINK)
+}
+
 # convert file mode in octal to symbolic file mode string
 sub mode_str {
        my $mode = oct shift;
 
-       if (S_ISDIR($mode & S_IFMT)) {
+       if (S_ISGITLINK($mode)) {
+               return 'm---------';
+       } elsif (S_ISDIR($mode & S_IFMT)) {
                return 'drwxr-xr-x';
        } elsif (S_ISLNK($mode)) {
                return 'lrwxrwxrwx';
@@ -831,7 +939,9 @@ sub file_type {
                $mode = oct $mode;
        }
 
-       if (S_ISDIR($mode & S_IFMT)) {
+       if (S_ISGITLINK($mode)) {
+               return "submodule";
+       } elsif (S_ISDIR($mode & S_IFMT)) {
                return "directory";
        } elsif (S_ISLNK($mode)) {
                return "symlink";
@@ -852,7 +962,9 @@ sub file_type_long {
                $mode = oct $mode;
        }
 
-       if (S_ISDIR($mode & S_IFMT)) {
+       if (S_ISGITLINK($mode)) {
+               return "submodule";
+       } elsif (S_ISDIR($mode & S_IFMT)) {
                return "directory";
        } elsif (S_ISLNK($mode)) {
                return "symlink";
@@ -923,7 +1035,7 @@ sub format_subject_html {
 
        if (length($short) < length($long)) {
                return $cgi->a({-href => $href, -class => "list subject",
-                               -title => decode_utf8($long)},
+                               -title => to_utf8($long)},
                       esc_html($short) . $extra);
        } else {
                return $cgi->a({-href => $href, -class => "list subject"},
@@ -931,7 +1043,200 @@ sub format_subject_html {
        }
 }
 
-# format patch (diff) line (rather not to be used for diff headers)
+# format git diff header line, i.e. "diff --(git|combined|cc) ..."
+sub format_git_diff_header_line {
+       my $line = shift;
+       my $diffinfo = shift;
+       my ($from, $to) = @_;
+
+       if ($diffinfo->{'nparents'}) {
+               # combined diff
+               $line =~ s!^(diff (.*?) )"?.*$!$1!;
+               if ($to->{'href'}) {
+                       $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+                                        esc_path($to->{'file'}));
+               } else { # file was deleted (no href)
+                       $line .= esc_path($to->{'file'});
+               }
+       } else {
+               # "ordinary" diff
+               $line =~ s!^(diff (.*?) )"?a/.*$!$1!;
+               if ($from->{'href'}) {
+                       $line .= $cgi->a({-href => $from->{'href'}, -class => "path"},
+                                        'a/' . esc_path($from->{'file'}));
+               } else { # file was added (no href)
+                       $line .= 'a/' . esc_path($from->{'file'});
+               }
+               $line .= ' ';
+               if ($to->{'href'}) {
+                       $line .= $cgi->a({-href => $to->{'href'}, -class => "path"},
+                                        'b/' . esc_path($to->{'file'}));
+               } else { # file was deleted
+                       $line .= 'b/' . esc_path($to->{'file'});
+               }
+       }
+
+       return "<div class=\"diff header\">$line</div>\n";
+}
+
+# format extended diff header line, before patch itself
+sub format_extended_diff_header_line {
+       my $line = shift;
+       my $diffinfo = shift;
+       my ($from, $to) = @_;
+
+       # match <path>
+       if ($line =~ s!^((copy|rename) from ).*$!$1! && $from->{'href'}) {
+               $line .= $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+                                      esc_path($from->{'file'}));
+       }
+       if ($line =~ s!^((copy|rename) to ).*$!$1! && $to->{'href'}) {
+               $line .= $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+                                esc_path($to->{'file'}));
+       }
+       # match single <mode>
+       if ($line =~ m/\s(\d{6})$/) {
+               $line .= '<span class="info"> (' .
+                        file_type_long($1) .
+                        ')</span>';
+       }
+       # match <hash>
+       if ($line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
+               # can match only for combined diff
+               $line = 'index ';
+               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                       if ($from->{'href'}[$i]) {
+                               $line .= $cgi->a({-href=>$from->{'href'}[$i],
+                                                 -class=>"hash"},
+                                                substr($diffinfo->{'from_id'}[$i],0,7));
+                       } else {
+                               $line .= '0' x 7;
+                       }
+                       # separator
+                       $line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
+               }
+               $line .= '..';
+               if ($to->{'href'}) {
+                       $line .= $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+                                        substr($diffinfo->{'to_id'},0,7));
+               } else {
+                       $line .= '0' x 7;
+               }
+
+       } elsif ($line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
+               # can match only for ordinary diff
+               my ($from_link, $to_link);
+               if ($from->{'href'}) {
+                       $from_link = $cgi->a({-href=>$from->{'href'}, -class=>"hash"},
+                                            substr($diffinfo->{'from_id'},0,7));
+               } else {
+                       $from_link = '0' x 7;
+               }
+               if ($to->{'href'}) {
+                       $to_link = $cgi->a({-href=>$to->{'href'}, -class=>"hash"},
+                                          substr($diffinfo->{'to_id'},0,7));
+               } else {
+                       $to_link = '0' x 7;
+               }
+               my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
+               $line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+       }
+
+       return $line . "<br/>\n";
+}
+
+# format from-file/to-file diff header
+sub format_diff_from_to_header {
+       my ($from_line, $to_line, $diffinfo, $from, $to, @parents) = @_;
+       my $line;
+       my $result = '';
+
+       $line = $from_line;
+       #assert($line =~ m/^---/) if DEBUG;
+       # no extra formatting for "^--- /dev/null"
+       if (! $diffinfo->{'nparents'}) {
+               # ordinary (single parent) diff
+               if ($line =~ m!^--- "?a/!) {
+                       if ($from->{'href'}) {
+                               $line = '--- a/' .
+                                       $cgi->a({-href=>$from->{'href'}, -class=>"path"},
+                                               esc_path($from->{'file'}));
+                       } else {
+                               $line = '--- a/' .
+                                       esc_path($from->{'file'});
+                       }
+               }
+               $result .= qq!<div class="diff from_file">$line</div>\n!;
+
+       } else {
+               # combined diff (merge commit)
+               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                       if ($from->{'href'}[$i]) {
+                               $line = '--- ' .
+                                       $cgi->a({-href=>href(action=>"blobdiff",
+                                                            hash_parent=>$diffinfo->{'from_id'}[$i],
+                                                            hash_parent_base=>$parents[$i],
+                                                            file_parent=>$from->{'file'}[$i],
+                                                            hash=>$diffinfo->{'to_id'},
+                                                            hash_base=>$hash,
+                                                            file_name=>$to->{'file'}),
+                                                -class=>"path",
+                                                -title=>"diff" . ($i+1)},
+                                               $i+1) .
+                                       '/' .
+                                       $cgi->a({-href=>$from->{'href'}[$i], -class=>"path"},
+                                               esc_path($from->{'file'}[$i]));
+                       } else {
+                               $line = '--- /dev/null';
+                       }
+                       $result .= qq!<div class="diff from_file">$line</div>\n!;
+               }
+       }
+
+       $line = $to_line;
+       #assert($line =~ m/^\+\+\+/) if DEBUG;
+       # no extra formatting for "^+++ /dev/null"
+       if ($line =~ m!^\+\+\+ "?b/!) {
+               if ($to->{'href'}) {
+                       $line = '+++ b/' .
+                               $cgi->a({-href=>$to->{'href'}, -class=>"path"},
+                                       esc_path($to->{'file'}));
+               } else {
+                       $line = '+++ b/' .
+                               esc_path($to->{'file'});
+               }
+       }
+       $result .= qq!<div class="diff to_file">$line</div>\n!;
+
+       return $result;
+}
+
+# create note for patch simplified by combined diff
+sub format_diff_cc_simplified {
+       my ($diffinfo, @parents) = @_;
+       my $result = '';
+
+       $result .= "<div class=\"diff header\">" .
+                  "diff --cc ";
+       if (!is_deleted($diffinfo)) {
+               $result .= $cgi->a({-href => href(action=>"blob",
+                                                 hash_base=>$hash,
+                                                 hash=>$diffinfo->{'to_id'},
+                                                 file_name=>$diffinfo->{'to_file'}),
+                                   -class => "path"},
+                                  esc_path($diffinfo->{'to_file'}));
+       } else {
+               $result .= esc_path($diffinfo->{'to_file'});
+       }
+       $result .= "</div>\n" . # class="diff header"
+                  "<div class=\"diff nodifferences\">" .
+                  "Simple merge" .
+                  "</div>\n"; # class="diff nodifferences"
+
+       return $result;
+}
+
+# format patch (diff) line (not to be used for diff headers)
 sub format_diff_line {
        my $line = shift;
        my ($from, $to) = @_;
@@ -1020,6 +1325,43 @@ sub format_diff_line {
        return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
 }
 
+# Generates undef or something like "_snapshot_" or "snapshot (_tbz2_ _zip_)",
+# linked.  Pass the hash of the tree/commit to snapshot.
+sub format_snapshot_links {
+       my ($hash) = @_;
+       my @snapshot_fmts = gitweb_check_feature('snapshot');
+       @snapshot_fmts = filter_snapshot_fmts(@snapshot_fmts);
+       my $num_fmts = @snapshot_fmts;
+       if ($num_fmts > 1) {
+               # A parenthesized list of links bearing format names.
+               # e.g. "snapshot (_tar.gz_ _zip_)"
+               return "snapshot (" . join(' ', map
+                       $cgi->a({
+                               -href => href(
+                                       action=>"snapshot",
+                                       hash=>$hash,
+                                       snapshot_format=>$_
+                               )
+                       }, $known_snapshot_formats{$_}{'display'})
+               , @snapshot_fmts) . ")";
+       } elsif ($num_fmts == 1) {
+               # A single "snapshot" link whose tooltip bears the format name.
+               # i.e. "_snapshot_"
+               my ($fmt) = @snapshot_fmts;
+               return
+                       $cgi->a({
+                               -href => href(
+                                       action=>"snapshot",
+                                       hash=>$hash,
+                                       snapshot_format=>$fmt
+                               ),
+                               -title => "in format: $known_snapshot_formats{$fmt}{'display'}"
+                       }, "snapshot");
+       } else { # $num_fmts == 0
+               return undef;
+       }
+}
+
 ## ----------------------------------------------------------------------
 ## git utility subroutines, invoking git commands
 
@@ -1173,6 +1515,7 @@ sub git_get_projects_list {
 
                File::Find::find({
                        follow_fast => 1, # follow symbolic links
+                       follow_skip => 2, # ignore duplicates
                        dangling_symlinks => 0, # ignore dangling symlinks, silently
                        wanted => sub {
                                # skip project-list toplevel, if we get it.
@@ -1237,7 +1580,7 @@ sub git_get_projects_list {
                        if (check_export_ok("$projectroot/$path")) {
                                my $pr = {
                                        path => $path,
-                                       owner => decode_utf8($owner),
+                                       owner => to_utf8($owner),
                                };
                                push @list, $pr;
                                (my $forks_path = $path) =~ s/\.git$//;
@@ -1249,12 +1592,12 @@ sub git_get_projects_list {
        return @list;
 }
 
-sub git_get_project_owner {
-       my $project = shift;
-       my $owner;
+our $gitweb_project_owner = undef;
+sub git_get_project_list_from_file {
 
-       return undef unless $project;
+       return if (defined $gitweb_project_owner);
 
+       $gitweb_project_owner = {};
        # read from file (url-encoded):
        # 'git%2Fgit.git Linus+Torvalds'
        # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin'
@@ -1266,13 +1609,25 @@ sub git_get_project_owner {
                        my ($pr, $ow) = split ' ', $line;
                        $pr = unescape($pr);
                        $ow = unescape($ow);
-                       if ($pr eq $project) {
-                               $owner = decode_utf8($ow);
-                               last;
-                       }
+                       $gitweb_project_owner->{$pr} = to_utf8($ow);
                }
                close $fd;
        }
+}
+
+sub git_get_project_owner {
+       my $project = shift;
+       my $owner;
+
+       return undef unless $project;
+
+       if (!defined $gitweb_project_owner) {
+               git_get_project_list_from_file();
+       }
+
+       if (exists $gitweb_project_owner->{$project}) {
+               $owner = $gitweb_project_owner->{$project};
+       }
        if (!defined $owner) {
                $owner = get_file_owner("$projectroot/$project");
        }
@@ -1298,6 +1653,7 @@ sub git_get_last_activity {
                my $age = time - $timestamp;
                return ($age, age_string($age));
        }
+       return (undef, undef);
 }
 
 sub git_get_references {
@@ -1541,6 +1897,7 @@ sub parse_commits {
                ($arg ? ($arg) : ()),
                ("--max-count=" . $maxcount),
                ("--skip=" . $skip),
+               @extra_options,
                $commit_id,
                "--",
                ($filename ? ($filename) : ())
@@ -1657,6 +2014,48 @@ sub parse_ls_tree_line ($;%) {
        return wantarray ? %res : \%res;
 }
 
+# generates _two_ hashes, references to which are passed as 2 and 3 argument
+sub parse_from_to_diffinfo {
+       my ($diffinfo, $from, $to, @parents) = @_;
+
+       if ($diffinfo->{'nparents'}) {
+               # combined diff
+               $from->{'file'} = [];
+               $from->{'href'} = [];
+               fill_from_file_info($diffinfo, @parents)
+                       unless exists $diffinfo->{'from_file'};
+               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
+                       $from->{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
+                       if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
+                               $from->{'href'}[$i] = href(action=>"blob",
+                                                          hash_base=>$parents[$i],
+                                                          hash=>$diffinfo->{'from_id'}[$i],
+                                                          file_name=>$from->{'file'}[$i]);
+                       } else {
+                               $from->{'href'}[$i] = undef;
+                       }
+               }
+       } else {
+               $from->{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
+               if ($diffinfo->{'status'} ne "A") { # not new (added) file
+                       $from->{'href'} = href(action=>"blob", hash_base=>$hash_parent,
+                                              hash=>$diffinfo->{'from_id'},
+                                              file_name=>$from->{'file'});
+               } else {
+                       delete $from->{'href'};
+               }
+       }
+
+       $to->{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
+       if (!is_deleted($diffinfo)) { # file exists in result
+               $to->{'href'} = href(action=>"blob", hash_base=>$hash,
+                                    hash=>$diffinfo->{'to_id'},
+                                    file_name=>$to->{'file'});
+       } else {
+               delete $to->{'href'};
+       }
+}
+
 ## ......................................................................
 ## parse to array of hashes functions
 
@@ -1757,7 +2156,7 @@ sub get_file_owner {
        }
        my $owner = $gcos;
        $owner =~ s/[,;].*$//;
-       return decode_utf8($owner);
+       return to_utf8($owner);
 }
 
 ## ......................................................................
@@ -1840,7 +2239,7 @@ sub git_header_html {
 
        my $title = "$site_name";
        if (defined $project) {
-               $title .= " - " . decode_utf8($project);
+               $title .= " - " . to_utf8($project);
                if (defined $action) {
                        $title .= "/$action";
                        if (defined $file_name) {
@@ -1892,9 +2291,17 @@ EOF
                printf('<link rel="alternate" title="%s log RSS feed" '.
                       'href="%s" type="application/rss+xml" />'."\n",
                       esc_param($project), href(action=>"rss"));
+               printf('<link rel="alternate" title="%s log RSS feed (no merges)" '.
+                      'href="%s" type="application/rss+xml" />'."\n",
+                      esc_param($project), href(action=>"rss",
+                                                extra_options=>"--no-merges"));
                printf('<link rel="alternate" title="%s log Atom feed" '.
                       'href="%s" type="application/atom+xml" />'."\n",
                       esc_param($project), href(action=>"atom"));
+               printf('<link rel="alternate" title="%s log Atom feed (no merges)" '.
+                      'href="%s" type="application/atom+xml" />'."\n",
+                      esc_param($project), href(action=>"atom",
+                                                extra_options=>"--no-merges"));
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
                       'href="%s" type="text/plain; charset=utf-8"/>'."\n",
@@ -1943,12 +2350,18 @@ EOF
                } else {
                        $search_hash = "HEAD";
                }
+               my $action = $my_uri;
+               my ($use_pathinfo) = gitweb_check_feature('pathinfo');
+               if ($use_pathinfo) {
+                       $action .= "/$project";
+               } else {
+                       $cgi->param("p", $project);
+               }
                $cgi->param("a", "search");
                $cgi->param("h", $search_hash);
-               $cgi->param("p", $project);
-               print $cgi->startform(-method => "get", -action => $my_uri) .
+               print $cgi->startform(-method => "get", -action => $action) .
                      "<div class=\"search\">\n" .
-                     $cgi->hidden(-name => "p") . "\n" .
+                     (!$use_pathinfo && $cgi->hidden(-name => "p") . "\n") .
                      $cgi->hidden(-name => "a") . "\n" .
                      $cgi->hidden(-name => "h") . "\n" .
                      $cgi->popup_menu(-name => 'st', -default => 'commit',
@@ -2114,7 +2527,7 @@ sub git_print_page_path {
 
        print "<div class=\"page_path\">";
        print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
-                     -title => 'tree root'}, decode_utf8("[$project]"));
+                     -title => 'tree root'}, to_utf8("[$project]"));
        print " / ";
        if (defined $name) {
                my @dirname = split '/', $name;
@@ -2326,6 +2739,20 @@ sub git_print_tree_entry {
                                      "history");
                }
                print "</td>\n";
+       } else {
+               # unknown object: we can only present history for it
+               # (this includes 'commit' object, i.e. submodule support)
+               print "<td class=\"list\">" .
+                     esc_path($t->{'name'}) .
+                     "</td>\n";
+               print "<td class=\"link\">";
+               if (defined $hash_base) {
+                       print $cgi->a({-href => href(action=>"history",
+                                                    hash_base=>$hash_base,
+                                                    file_name=>"$basedir$t->{'name'}")},
+                                     "history");
+               }
+               print "</td>\n";
        }
 }
 
@@ -2364,6 +2791,11 @@ sub from_ids_eq {
        }
 }
 
+sub is_deleted {
+       my $diffinfo = shift;
+
+       return $diffinfo->{'to_id'} eq ('0' x 40);
+}
 
 sub git_difftree_body {
        my ($difftree, $hash, @parents) = @_;
@@ -2378,6 +2810,26 @@ sub git_difftree_body {
        print "<table class=\"" .
              (@parents > 1 ? "combined " : "") .
              "diff_tree\">\n";
+
+       # header only for combined diff in 'commitdiff' view
+       my $has_header = @parents > 1 && $action eq 'commitdiff';
+       if ($has_header) {
+               # table header
+               print "<thead><tr>\n" .
+                      "<th></th><th></th>\n"; # filename, patchN link
+               for (my $i = 0; $i < @parents; $i++) {
+                       my $par = $parents[$i];
+                       print "<th>" .
+                             $cgi->a({-href => href(action=>"commitdiff",
+                                                    hash=>$hash, hash_parent=>$par),
+                                      -title => 'commitdiff to parent number ' .
+                                                 ($i+1) . ': ' . substr($par,0,7)},
+                                     $i+1) .
+                             "&nbsp;</th>\n";
+               }
+               print "</tr></thead>\n<tbody>\n";
+       }
+
        my $alternate = 1;
        my $patchno = 0;
        foreach my $line (@{$difftree}) {
@@ -2401,7 +2853,7 @@ sub git_difftree_body {
                        fill_from_file_info($diff, @parents)
                                unless exists $diff->{'from_file'};
 
-                       if ($diff->{'to_id'} ne ('0' x 40)) {
+                       if (!is_deleted($diff)) {
                                # file exists in the result (child) commit
                                print "<td>" .
                                      $cgi->a({-href => href(action=>"blob", hash=>$diff->{'to_id'},
@@ -2650,6 +3102,7 @@ sub git_difftree_body {
                } # we should not encounter Unmerged (U) or Unknown (X) status
                print "</tr>\n";
        }
+       print "</tbody>" if $has_header;
        print "</table>\n";
 }
 
@@ -2714,13 +3167,35 @@ sub git_patchset_body {
                        # advance raw git-diff output if needed
                        $patch_idx++ if defined $diffinfo;
 
-                       # read and prepare patch information
-                       if (ref($difftree->[$patch_idx]) eq "HASH") {
-                               # pre-parsed (or generated by hand)
-                               $diffinfo = $difftree->[$patch_idx];
-                       } else {
-                               $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+                       # compact combined diff output can have some patches skipped
+                       # find which patch (using pathname of result) we are at now
+                       my $to_name;
+                       if ($diff_header[0] =~ m!^diff --cc "?(.*)"?$!) {
+                               $to_name = $1;
                        }
+
+                       do {
+                               # read and prepare patch information
+                               if (ref($difftree->[$patch_idx]) eq "HASH") {
+                                       # pre-parsed (or generated by hand)
+                                       $diffinfo = $difftree->[$patch_idx];
+                               } else {
+                                       $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+                               }
+
+                               # check if current raw line has no patch (it got simplified)
+                               if (defined $to_name && $to_name ne $diffinfo->{'to_file'}) {
+                                       print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
+                                             format_diff_cc_simplified($diffinfo, @hash_parents) .
+                                             "</div>\n";  # class="patch"
+
+                                       $patch_idx++;
+                                       $patch_number++;
+                               }
+                       } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} ||
+                                $patch_idx > $#$difftree);
+                       # modifies %from, %to hashes
+                       parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
                        if ($diffinfo->{'nparents'}) {
                                # combined diff
                                $from{'file'} = [];
@@ -2750,7 +3225,7 @@ sub git_patchset_body {
                        }
 
                        $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
-                       if ($diffinfo->{'to_id'} ne ('0' x 40)) { # file exists in result
+                       if (!is_deleted($diffinfo)) { # file exists in result
                                $to{'href'} = href(action=>"blob", hash_base=>$hash,
                                                   hash=>$diffinfo->{'to_id'},
                                                   file_name=>$to{'file'});
@@ -2764,105 +3239,15 @@ sub git_patchset_body {
 
                # print "git diff" header
                $patch_line = shift @diff_header;
-               if ($diffinfo->{'nparents'}) {
-
-                       # combined diff
-                       $patch_line =~ s!^(diff (.*?) )"?.*$!$1!;
-                       if ($to{'href'}) {
-                               $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
-                                                      esc_path($to{'file'}));
-                       } else { # file was deleted
-                               $patch_line .= esc_path($to{'file'});
-                       }
-
-               } else {
-
-                       $patch_line =~ s!^(diff (.*?) )"?a/.*$!$1!;
-                       if ($from{'href'}) {
-                               $patch_line .= $cgi->a({-href => $from{'href'}, -class => "path"},
-                                                      'a/' . esc_path($from{'file'}));
-                       } else { # file was added
-                               $patch_line .= 'a/' . esc_path($from{'file'});
-                       }
-                       $patch_line .= ' ';
-                       if ($to{'href'}) {
-                               $patch_line .= $cgi->a({-href => $to{'href'}, -class => "path"},
-                                                      'b/' . esc_path($to{'file'}));
-                       } else { # file was deleted
-                               $patch_line .= 'b/' . esc_path($to{'file'});
-                       }
-
-               }
-               print "<div class=\"diff header\">$patch_line</div>\n";
+               print format_git_diff_header_line($patch_line, $diffinfo,
+                                                 \%from, \%to);
 
                # print extended diff header
                print "<div class=\"diff extended_header\">\n" if (@diff_header > 0);
        EXTENDED_HEADER:
                foreach $patch_line (@diff_header) {
-                       # match <path>
-                       if ($patch_line =~ s!^((copy|rename) from ).*$!$1! && $from{'href'}) {
-                               $patch_line .= $cgi->a({-href=>$from{'href'}, -class=>"path"},
-                                                      esc_path($from{'file'}));
-                       }
-                       if ($patch_line =~ s!^((copy|rename) to ).*$!$1! && $to{'href'}) {
-                               $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"path"},
-                                                      esc_path($to{'file'}));
-                       }
-                       # match single <mode>
-                       if ($patch_line =~ m/\s(\d{6})$/) {
-                               $patch_line .= '<span class="info"> (' .
-                                              file_type_long($1) .
-                                              ')</span>';
-                       }
-                       # match <hash>
-                       if ($patch_line =~ m/^index [0-9a-fA-F]{40},[0-9a-fA-F]{40}/) {
-                               # can match only for combined diff
-                               $patch_line = 'index ';
-                               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
-                                       if ($from{'href'}[$i]) {
-                                               $patch_line .= $cgi->a({-href=>$from{'href'}[$i],
-                                                                       -class=>"hash"},
-                                                                      substr($diffinfo->{'from_id'}[$i],0,7));
-                                       } else {
-                                               $patch_line .= '0' x 7;
-                                       }
-                                       # separator
-                                       $patch_line .= ',' if ($i < $diffinfo->{'nparents'} - 1);
-                               }
-                               $patch_line .= '..';
-                               if ($to{'href'}) {
-                                       $patch_line .= $cgi->a({-href=>$to{'href'}, -class=>"hash"},
-                                                              substr($diffinfo->{'to_id'},0,7));
-                               } else {
-                                       $patch_line .= '0' x 7;
-                               }
-
-                       } elsif ($patch_line =~ m/^index [0-9a-fA-F]{40}..[0-9a-fA-F]{40}/) {
-                               # can match only for ordinary diff
-                               my ($from_link, $to_link);
-                               if ($from{'href'}) {
-                                       $from_link = $cgi->a({-href=>$from{'href'}, -class=>"hash"},
-                                                            substr($diffinfo->{'from_id'},0,7));
-                               } else {
-                                       $from_link = '0' x 7;
-                               }
-                               if ($to{'href'}) {
-                                       $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
-                                                          substr($diffinfo->{'to_id'},0,7));
-                               } else {
-                                       $to_link = '0' x 7;
-                               }
-                               #affirm {
-                               #       my ($from_hash, $to_hash) =
-                               #               ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/);
-                               #       my ($from_id, $to_id) =
-                               #               ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
-                               #       ($from_hash eq $from_id) && ($to_hash eq $to_id);
-                               #} if DEBUG;
-                               my ($from_id, $to_id) = ($diffinfo->{'from_id'}, $diffinfo->{'to_id'});
-                               $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
-                       }
-                       print $patch_line . "<br/>\n";
+                       print format_extended_diff_header_line($patch_line, $diffinfo,
+                                                              \%from, \%to);
                }
                print "</div>\n"  if (@diff_header > 0); # class="diff extended_header"
 
@@ -2874,24 +3259,15 @@ sub git_patchset_body {
                }
                next PATCH if ($patch_line =~ m/^diff /);
                #assert($patch_line =~ m/^---/) if DEBUG;
-               if (!$diffinfo->{'nparents'} && # not from-file line for combined diff
-                   $from{'href'} && $patch_line =~ m!^--- "?a/!) {
-                       $patch_line = '--- a/' .
-                                     $cgi->a({-href=>$from{'href'}, -class=>"path"},
-                                             esc_path($from{'file'}));
-               }
-               print "<div class=\"diff from_file\">$patch_line</div>\n";
+               #assert($patch_line eq $last_patch_line) if DEBUG;
 
                $patch_line = <$fd>;
                chomp $patch_line;
+               #assert($patch_line =~ m/^\+\+\+/) if DEBUG;
 
-               #assert($patch_line =~ m/^+++/) if DEBUG;
-               if ($to{'href'} && $patch_line =~ m!^\+\+\+ "?b/!) {
-                       $patch_line = '+++ b/' .
-                                     $cgi->a({-href=>$to{'href'}, -class=>"path"},
-                                             esc_path($to{'file'}));
-               }
-               print "<div class=\"diff to_file\">$patch_line</div>\n";
+               print format_diff_from_to_header($last_patch_line, $patch_line,
+                                                $diffinfo, \%from, \%to,
+                                                @hash_parents);
 
                # the patch itself
        LINE:
@@ -2907,6 +3283,27 @@ sub git_patchset_body {
                print "</div>\n"; # class="patch"
        }
 
+       # for compact combined (--cc) format, with chunk and patch simpliciaction
+       # patchset might be empty, but there might be unprocessed raw lines
+       for ($patch_idx++ if $patch_number > 0;
+            $patch_idx < @$difftree;
+            $patch_idx++) {
+               # read and prepare patch information
+               if (ref($difftree->[$patch_idx]) eq "HASH") {
+                       # pre-parsed (or generated by hand)
+                       $diffinfo = $difftree->[$patch_idx];
+               } else {
+                       $diffinfo = parse_difftree_raw_line($difftree->[$patch_idx]);
+               }
+
+               # generate anchor for "patch" links in difftree / whatchanged part
+               print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n" .
+                     format_diff_cc_simplified($diffinfo, @hash_parents) .
+                     "</div>\n";  # class="patch"
+
+               $patch_number++;
+       }
+
        if ($patch_number == 0) {
                if (@hash_parents > 1) {
                        print "<div class=\"diff nodifferences\">Trivial merge</div>\n";
@@ -2934,11 +3331,11 @@ sub git_project_list_body {
                ($pr->{'age'}, $pr->{'age_string'}) = @aa;
                if (!defined $pr->{'descr'}) {
                        my $descr = git_get_project_description($pr->{'path'}) || "";
-                       $pr->{'descr_long'} = decode_utf8($descr);
-                       $pr->{'descr'} = chop_str($descr, 25, 5);
+                       $pr->{'descr_long'} = to_utf8($descr);
+                       $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5);
                }
                if (!defined $pr->{'owner'}) {
-                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}") || "";
+                       $pr->{'owner'} = git_get_project_owner("$pr->{'path'}") || "";
                }
                if ($check_forks) {
                        my $pname = $pr->{'path'};
@@ -3052,8 +3449,6 @@ sub git_shortlog_body {
        # uses global variable $project
        my ($commitlist, $from, $to, $refs, $extra) = @_;
 
-       my $have_snapshot = gitweb_have_snapshot();
-
        $from = 0 unless defined $from;
        $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
 
@@ -3080,8 +3475,9 @@ sub git_shortlog_body {
                      $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " .
                      $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " .
                      $cgi->a({-href => href(action=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
-               if ($have_snapshot) {
-                       print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+               my $snapshot_links = format_snapshot_links($commit);
+               if (defined $snapshot_links) {
+                       print " | " . $snapshot_links;
                }
                print "</td>\n" .
                      "</tr>\n";
@@ -3362,7 +3758,7 @@ sub git_project_index {
 
        foreach my $pr (@projects) {
                if (!exists $pr->{'owner'}) {
-                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}");
+                       $pr->{'owner'} = git_get_project_owner("$pr->{'path'}");
                }
 
                my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
@@ -3863,8 +4259,6 @@ sub git_blob {
 }
 
 sub git_tree {
-       my $have_snapshot = gitweb_have_snapshot();
-
        if (!defined $hash_base) {
                $hash_base = "HEAD";
        }
@@ -3898,11 +4292,10 @@ sub git_tree {
                                                       hash_base=>"HEAD", file_name=>$file_name)},
                                        "HEAD"),
                }
-               if ($have_snapshot) {
+               my $snapshot_links = format_snapshot_links($hash);
+               if (defined $snapshot_links) {
                        # FIXME: Should be available when we have no hash base as well.
-                       push @views_nav,
-                               $cgi->a({-href => href(action=>"snapshot", hash=>$hash)},
-                                       "snapshot");
+                       push @views_nav, $snapshot_links;
                }
                git_print_page_nav('tree','', $hash_base, undef, undef, join(' | ', @views_nav));
                git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base);
@@ -3966,34 +4359,53 @@ sub git_tree {
 }
 
 sub git_snapshot {
-       my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
-       my $have_snapshot = (defined $ctype && defined $suffix);
-       if (!$have_snapshot) {
+       my @supported_fmts = gitweb_check_feature('snapshot');
+       @supported_fmts = filter_snapshot_fmts(@supported_fmts);
+
+       my $format = $cgi->param('sf');
+       if (!@supported_fmts) {
                die_error('403 Permission denied', "Permission denied");
        }
+       # default to first supported snapshot format
+       $format ||= $supported_fmts[0];
+       if ($format !~ m/^[a-z0-9]+$/) {
+               die_error(undef, "Invalid snapshot format parameter");
+       } elsif (!exists($known_snapshot_formats{$format})) {
+               die_error(undef, "Unknown snapshot format");
+       } elsif (!grep($_ eq $format, @supported_fmts)) {
+               die_error(undef, "Unsupported snapshot format");
+       }
 
        if (!defined $hash) {
                $hash = git_get_head_hash($project);
        }
 
-       my $filename = decode_utf8(basename($project)) . "-$hash.tar.$suffix";
+       my $git_command = git_cmd_str();
+       my $name = $project;
+       $name =~ s,([^/])/*\.git$,$1,;
+       $name = basename($name);
+       my $filename = to_utf8($name);
+       $name =~ s/\047/\047\\\047\047/g;
+       my $cmd;
+       $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}";
+       $cmd = "$git_command archive " .
+               "--format=$known_snapshot_formats{$format}{'format'} " .
+               "--prefix=\'$name\'/ $hash";
+       if (exists $known_snapshot_formats{$format}{'compressor'}) {
+               $cmd .= ' | ' . join ' ', @{$known_snapshot_formats{$format}{'compressor'}};
+       }
 
        print $cgi->header(
-               -type => "application/$ctype",
+               -type => $known_snapshot_formats{$format}{'type'},
                -content_disposition => 'inline; filename="' . "$filename" . '"',
                -status => '200 OK');
 
-       my $git = git_cmd_str();
-       my $name = $project;
-       $name =~ s/\047/\047\\\047\047/g;
-       open my $fd, "-|",
-               "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
-               or die_error(undef, "Execute git-tar-tree failed");
+       open my $fd, "-|", $cmd
+               or die_error(undef, "Execute git-archive failed");
        binmode STDOUT, ':raw';
        print <$fd>;
        binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
        close $fd;
-
 }
 
 sub git_log {
@@ -4112,8 +4524,6 @@ sub git_commit {
        my $refs = git_get_references();
        my $ref = format_ref_marker($refs, $co{'id'});
 
-       my $have_snapshot = gitweb_have_snapshot();
-
        git_header_html(undef, $expires);
        git_print_page_nav('commit', '',
                           $hash, $co{'tree'}, $hash,
@@ -4152,9 +4562,9 @@ sub git_commit {
              "<td class=\"link\">" .
              $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)},
                      "tree");
-       if ($have_snapshot) {
-               print " | " .
-                     $cgi->a({-href => href(action=>"snapshot", hash=>$hash)}, "snapshot");
+       my $snapshot_links = format_snapshot_links($hash);
+       if (defined $snapshot_links) {
+               print " | " . $snapshot_links;
        }
        print "</td>" .
              "</tr>\n";
@@ -4413,7 +4823,11 @@ sub git_commitdiff {
                die_error(undef, "Unknown commit object");
        }
 
-       # we need to prepare $formats_nav before any parameter munging
+       # choose format for commitdiff for merge
+       if (! defined $hash_parent && @{$co{'parents'}} > 1) {
+               $hash_parent = '--cc';
+       }
+       # we need to prepare $formats_nav before almost any parameter munging
        my $formats_nav;
        if ($format eq 'html') {
                $formats_nav =
@@ -4421,14 +4835,22 @@ sub git_commitdiff {
                                               hash=>$hash, hash_parent=>$hash_parent)},
                                "raw");
 
-               if (defined $hash_parent) {
+               if (defined $hash_parent &&
+                   $hash_parent ne '-c' && $hash_parent ne '--cc') {
                        # commitdiff with two commits given
                        my $hash_parent_short = $hash_parent;
                        if ($hash_parent =~ m/^[0-9a-fA-F]{40}$/) {
                                $hash_parent_short = substr($hash_parent, 0, 7);
                        }
                        $formats_nav .=
-                               ' (from: ' .
+                               ' (from';
+                       for (my $i = 0; $i < @{$co{'parents'}}; $i++) {
+                               if ($co{'parents'}[$i] eq $hash_parent) {
+                                       $formats_nav .= ' parent ' . ($i+1);
+                                       last;
+                               }
+                       }
+                       $formats_nav .= ': ' .
                                $cgi->a({-href => href(action=>"commitdiff",
                                                       hash=>$hash_parent)},
                                        esc_html($hash_parent_short)) .
@@ -4446,6 +4868,17 @@ sub git_commitdiff {
                                ')';
                } else {
                        # merge commit
+                       if ($hash_parent eq '--cc') {
+                               $formats_nav .= ' | ' .
+                                       $cgi->a({-href => href(action=>"commitdiff",
+                                                              hash=>$hash, hash_parent=>'-c')},
+                                               'combined');
+                       } else { # $hash_parent eq '-c'
+                               $formats_nav .= ' | ' .
+                                       $cgi->a({-href => href(action=>"commitdiff",
+                                                              hash=>$hash, hash_parent=>'--cc')},
+                                               'compact');
+                       }
                        $formats_nav .=
                                ' (merge: ' .
                                join(' ', map {
@@ -4458,9 +4891,10 @@ sub git_commitdiff {
        }
 
        my $hash_parent_param = $hash_parent;
-       if (!defined $hash_parent) {
+       if (!defined $hash_parent_param) {
+               # --cc for multiple parents, --root for parentless
                $hash_parent_param =
-                       @{$co{'parents'}} > 1 ? '-c' : $co{'parent'} || '--root';
+                       @{$co{'parents'}} > 1 ? '--cc' : $co{'parent'} || '--root';
        }
 
        # read commitdiff
@@ -4537,10 +4971,14 @@ TEXT
 
        # write patch
        if ($format eq 'html') {
-               git_difftree_body(\@difftree, $hash, $hash_parent || @{$co{'parents'}});
+               my $use_parents = !defined $hash_parent ||
+                       $hash_parent eq '-c' || $hash_parent eq '--cc';
+               git_difftree_body(\@difftree, $hash,
+                                 $use_parents ? @{$co{'parents'}} : $hash_parent);
                print "<br/>\n";
 
-               git_patchset_body($fd, \@difftree, $hash, $hash_parent || @{$co{'parents'}});
+               git_patchset_body($fd, \@difftree, $hash,
+                                 $use_parents ? @{$co{'parents'}} : $hash_parent);
                close $fd;
                print "</div>\n"; # class="page_body"
                git_footer_html();
@@ -4928,7 +5366,7 @@ sub git_feed {
 
        # log/feed of current (HEAD) branch, log of given branch, history of file/directory
        my $head = $hash || 'HEAD';
-       my @commitlist = parse_commits($head, 150);
+       my @commitlist = parse_commits($head, 150, 0, undef, $file_name);
 
        my %latest_commit;
        my %latest_date;
diff --git a/help.c b/help.c
index 6a9af4d175f2fe9724a2844df63e4011ba91f1b3..1cd33ece6bcec6f71c6749205c18c0f413034d89 100644 (file)
--- a/help.c
+++ b/help.c
@@ -219,5 +219,3 @@ int cmd_help(int argc, const char **argv, const char *prefix)
 
        return 0;
 }
-
-
index 09baedc18ae320a2f09fa61d9065bedb69c02a41..202fae0ba8b348aeca6ec6c61045f39aa0d1fa66 100644 (file)
@@ -828,7 +828,7 @@ static void abort_object_request(struct object_request *obj_req)
        }
        unlink(obj_req->tmpfile);
        if (obj_req->slot) {
-               release_active_slot(obj_req->slot);
+               release_active_slot(obj_req->slot);
                obj_req->slot = NULL;
        }
        release_object_request(obj_req);
index e3f767582bfc71fc3d1b90be8fa76ab3ad7e1664..7c3720f602bb8f50ed54a4f7e7a85c7e08d1c07b 100644 (file)
@@ -9,6 +9,7 @@
 #include "diff.h"
 #include "revision.h"
 #include "exec_cmd.h"
+#include "remote.h"
 
 #include <expat.h>
 
@@ -517,7 +518,7 @@ static void start_put(struct transfer_request *request)
        request->buffer.size = stream.total_out;
        request->buffer.posn = 0;
 
-       request->url = xmalloc(strlen(remote->url) + 
+       request->url = xmalloc(strlen(remote->url) +
                               strlen(request->lock->token) + 51);
        strcpy(request->url, remote->url);
        posn = request->url + strlen(remote->url);
diff --git a/http.c b/http.c
index ae27e0c9409b93558e50318f2dd0a4fe758293b5..c6fb8ace9f9f43935f4128fc223b01e6cb9fa605 100644 (file)
--- a/http.c
+++ b/http.c
@@ -137,7 +137,7 @@ static int http_options(const char *var, const char *value)
                return 0;
        }
 
-#ifdef USE_CURL_MULTI  
+#ifdef USE_CURL_MULTI
        if (!strcmp("http.maxrequests", var)) {
                if (max_requests == -1)
                        max_requests = git_config_int(var, value);
diff --git a/ident.c b/ident.c
index 3d49608e6f9afe22f24a2faf70f2d6cee50b8919..9b2a852cb00327a607946caa864fcf31184da28e 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -83,11 +83,18 @@ static void setup_ident(void)
        }
 
        if (!git_default_email[0]) {
-               if (!pw)
-                       pw = getpwuid(getuid());
-               if (!pw)
-                       die("You don't exist. Go away!");
-               copy_email(pw);
+               const char *email = getenv("EMAIL");
+
+               if (email && email[0])
+                       strlcpy(git_default_email, email,
+                               sizeof(git_default_email));
+               else {
+                       if (!pw)
+                               pw = getpwuid(getuid());
+                       if (!pw)
+                               die("You don't exist. Go away!");
+                       copy_email(pw);
+               }
        }
 
        /* And set the default date */
@@ -178,11 +185,11 @@ static const char *env_hint =
 "\n"
 "Run\n"
 "\n"
-"  git config user.email \"you@email.com\"\n"
-"  git config user.name \"Your Name\"\n"
+"  git config --global user.email \"you@email.com\"\n"
+"  git config --global user.name \"Your Name\"\n"
 "\n"
-"To set the identity in this repository.\n"
-"Add --global to set your account\'s default\n"
+"to set your account\'s default identity.\n"
+"Omit --global to set the identity only in this repository.\n"
 "\n";
 
 const char *fmt_ident(const char *name, const char *email,
@@ -197,8 +204,6 @@ const char *fmt_ident(const char *name, const char *email,
                name = git_default_name;
        if (!email)
                email = git_default_email;
-       if (!email)
-               email = getenv("EMAIL");
 
        if (!*name) {
                struct passwd *pw;
index 4283a4acdaf28a8edc2f595e6137cc396f4988dc..a5a069608419a8ecc42be63eb7998efd262f0ddc 100644 (file)
@@ -1239,7 +1239,7 @@ split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs )
        msg->data[ msg->len ] = 0;
 
        *ofs += msg->len;
-       return 1;
+       return 1;
 }
 
 static imap_server_conf_t server =
index 58c4a9c41dd7a05b86d40e6eeee33ba0a3fb6c4f..db58e050410197f6eda4b91705e20a8bfc87b088 100644 (file)
@@ -13,13 +13,11 @@ static const char index_pack_usage[] =
 
 struct object_entry
 {
-       off_t offset;
+       struct pack_idx_entry idx;
        unsigned long size;
        unsigned int hdr_size;
-       uint32_t crc32;
        enum object_type type;
        enum object_type real_type;
-       unsigned char sha1[20];
 };
 
 union delta_base {
@@ -116,7 +114,7 @@ static const char *open_pack_file(const char *pack_name)
                        static char tmpfile[PATH_MAX];
                        snprintf(tmpfile, sizeof(tmpfile),
                                 "%s/tmp_pack_XXXXXX", get_object_directory());
-                       output_fd = mkstemp(tmpfile);
+                       output_fd = xmkstemp(tmpfile);
                        pack_name = xstrdup(tmpfile);
                } else
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
@@ -197,7 +195,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        unsigned shift;
        void *data;
 
-       obj->offset = consumed_bytes;
+       obj->idx.offset = consumed_bytes;
        input_crc32 = crc32(0, Z_NULL, 0);
 
        p = fill(1);
@@ -229,15 +227,15 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
                while (c & 128) {
                        base_offset += 1;
                        if (!base_offset || MSB(base_offset, 7))
-                               bad_object(obj->offset, "offset value overflow for delta base object");
+                               bad_object(obj->idx.offset, "offset value overflow for delta base object");
                        p = fill(1);
                        c = *p;
                        use(1);
                        base_offset = (base_offset << 7) + (c & 127);
                }
-               delta_base->offset = obj->offset - base_offset;
-               if (delta_base->offset >= obj->offset)
-                       bad_object(obj->offset, "delta base offset is out of bound");
+               delta_base->offset = obj->idx.offset - base_offset;
+               if (delta_base->offset >= obj->idx.offset)
+                       bad_object(obj->idx.offset, "delta base offset is out of bound");
                break;
        case OBJ_COMMIT:
        case OBJ_TREE:
@@ -245,19 +243,19 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        case OBJ_TAG:
                break;
        default:
-               bad_object(obj->offset, "unknown object type %d", obj->type);
+               bad_object(obj->idx.offset, "unknown object type %d", obj->type);
        }
-       obj->hdr_size = consumed_bytes - obj->offset;
+       obj->hdr_size = consumed_bytes - obj->idx.offset;
 
-       data = unpack_entry_data(obj->offset, obj->size);
-       obj->crc32 = input_crc32;
+       data = unpack_entry_data(obj->idx.offset, obj->size);
+       obj->idx.crc32 = input_crc32;
        return data;
 }
 
 static void *get_data_from_pack(struct object_entry *obj)
 {
-       unsigned long from = obj[0].offset + obj[0].hdr_size;
-       unsigned long len = obj[1].offset - from;
+       unsigned long from = obj[0].idx.offset + obj[0].hdr_size;
+       unsigned long len = obj[1].idx.offset - from;
        unsigned long rdy = 0;
        unsigned char *src, *data;
        z_stream stream;
@@ -360,11 +358,11 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data,
                             &result_size);
        free(delta_data);
        if (!result)
-               bad_object(delta_obj->offset, "failed to apply delta");
-       sha1_object(result, result_size, type, delta_obj->sha1);
+               bad_object(delta_obj->idx.offset, "failed to apply delta");
+       sha1_object(result, result_size, type, delta_obj->idx.sha1);
        nr_resolved_deltas++;
 
-       hashcpy(delta_base.sha1, delta_obj->sha1);
+       hashcpy(delta_base.sha1, delta_obj->idx.sha1);
        if (!find_delta_children(&delta_base, &first, &last)) {
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
@@ -374,7 +372,7 @@ static void resolve_delta(struct object_entry *delta_obj, void *base_data,
        }
 
        memset(&delta_base, 0, sizeof(delta_base));
-       delta_base.offset = delta_obj->offset;
+       delta_base.offset = delta_obj->idx.offset;
        if (!find_delta_children(&delta_base, &first, &last)) {
                for (j = first; j <= last; j++) {
                        struct object_entry *child = objects + deltas[j].obj_no;
@@ -418,12 +416,12 @@ static void parse_pack_objects(unsigned char *sha1)
                        delta->obj_no = i;
                        delta++;
                } else
-                       sha1_object(data, obj->size, obj->type, obj->sha1);
+                       sha1_object(data, obj->size, obj->type, obj->idx.sha1);
                free(data);
                if (verbose)
                        display_progress(&progress, i+1);
        }
-       objects[i].offset = consumed_bytes;
+       objects[i].idx.offset = consumed_bytes;
        if (verbose)
                stop_progress(&progress);
 
@@ -465,10 +463,10 @@ static void parse_pack_objects(unsigned char *sha1)
 
                if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
                        continue;
-               hashcpy(base.sha1, obj->sha1);
+               hashcpy(base.sha1, obj->idx.sha1);
                ref = !find_delta_children(&base, &ref_first, &ref_last);
                memset(&base, 0, sizeof(base));
-               base.offset = obj->offset;
+               base.offset = obj->idx.offset;
                ofs = !find_delta_children(&base, &ofs_first, &ofs_last);
                if (!ref && !ofs)
                        continue;
@@ -535,11 +533,11 @@ static void append_obj_to_pack(const unsigned char *sha1, void *buf,
        }
        header[n++] = c;
        write_or_die(output_fd, header, n);
-       obj[0].crc32 = crc32(0, Z_NULL, 0);
-       obj[0].crc32 = crc32(obj[0].crc32, header, n);
-       obj[1].offset = obj[0].offset + n;
-       obj[1].offset += write_compressed(output_fd, buf, size, &obj[0].crc32);
-       hashcpy(obj->sha1, sha1);
+       obj[0].idx.crc32 = crc32(0, Z_NULL, 0);
+       obj[0].idx.crc32 = crc32(obj[0].idx.crc32, header, n);
+       obj[1].idx.offset = obj[0].idx.offset + n;
+       obj[1].idx.offset += write_compressed(output_fd, buf, size, &obj[0].idx.crc32);
+       hashcpy(obj->idx.sha1, sha1);
 }
 
 static int delta_pos_compare(const void *_a, const void *_b)
@@ -602,145 +600,6 @@ static void fix_unresolved_deltas(int nr_unresolved)
        free(sorted_by_pos);
 }
 
-static uint32_t index_default_version = 1;
-static uint32_t index_off32_limit = 0x7fffffff;
-
-static int sha1_compare(const void *_a, const void *_b)
-{
-       struct object_entry *a = *(struct object_entry **)_a;
-       struct object_entry *b = *(struct object_entry **)_b;
-       return hashcmp(a->sha1, b->sha1);
-}
-
-/*
- * On entry *sha1 contains the pack content SHA1 hash, on exit it is
- * the SHA1 hash of sorted object names.
- */
-static const char *write_index_file(const char *index_name, unsigned char *sha1)
-{
-       struct sha1file *f;
-       struct object_entry **sorted_by_sha, **list, **last;
-       uint32_t array[256];
-       int i, fd;
-       SHA_CTX ctx;
-       uint32_t index_version;
-
-       if (nr_objects) {
-               sorted_by_sha =
-                       xcalloc(nr_objects, sizeof(struct object_entry *));
-               list = sorted_by_sha;
-               last = sorted_by_sha + nr_objects;
-               for (i = 0; i < nr_objects; ++i)
-                       sorted_by_sha[i] = &objects[i];
-               qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
-                     sha1_compare);
-       }
-       else
-               sorted_by_sha = list = last = NULL;
-
-       if (!index_name) {
-               static char tmpfile[PATH_MAX];
-               snprintf(tmpfile, sizeof(tmpfile),
-                        "%s/tmp_idx_XXXXXX", get_object_directory());
-               fd = mkstemp(tmpfile);
-               index_name = xstrdup(tmpfile);
-       } else {
-               unlink(index_name);
-               fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
-       }
-       if (fd < 0)
-               die("unable to create %s: %s", index_name, strerror(errno));
-       f = sha1fd(fd, index_name);
-
-       /* if last object's offset is >= 2^31 we should use index V2 */
-       index_version = (objects[nr_objects-1].offset >> 31) ? 2 : index_default_version;
-
-       /* index versions 2 and above need a header */
-       if (index_version >= 2) {
-               struct pack_idx_header hdr;
-               hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
-               hdr.idx_version = htonl(index_version);
-               sha1write(f, &hdr, sizeof(hdr));
-       }
-
-       /*
-        * Write the first-level table (the list is sorted,
-        * but we use a 256-entry lookup to be able to avoid
-        * having to do eight extra binary search iterations).
-        */
-       for (i = 0; i < 256; i++) {
-               struct object_entry **next = list;
-               while (next < last) {
-                       struct object_entry *obj = *next;
-                       if (obj->sha1[0] != i)
-                               break;
-                       next++;
-               }
-               array[i] = htonl(next - sorted_by_sha);
-               list = next;
-       }
-       sha1write(f, array, 256 * 4);
-
-       /* compute the SHA1 hash of sorted object names. */
-       SHA1_Init(&ctx);
-
-       /*
-        * Write the actual SHA1 entries..
-        */
-       list = sorted_by_sha;
-       for (i = 0; i < nr_objects; i++) {
-               struct object_entry *obj = *list++;
-               if (index_version < 2) {
-                       uint32_t offset = htonl(obj->offset);
-                       sha1write(f, &offset, 4);
-               }
-               sha1write(f, obj->sha1, 20);
-               SHA1_Update(&ctx, obj->sha1, 20);
-       }
-
-       if (index_version >= 2) {
-               unsigned int nr_large_offset = 0;
-
-               /* write the crc32 table */
-               list = sorted_by_sha;
-               for (i = 0; i < nr_objects; i++) {
-                       struct object_entry *obj = *list++;
-                       uint32_t crc32_val = htonl(obj->crc32);
-                       sha1write(f, &crc32_val, 4);
-               }
-
-               /* write the 32-bit offset table */
-               list = sorted_by_sha;
-               for (i = 0; i < nr_objects; i++) {
-                       struct object_entry *obj = *list++;
-                       uint32_t offset = (obj->offset <= index_off32_limit) ?
-                               obj->offset : (0x80000000 | nr_large_offset++);
-                       offset = htonl(offset);
-                       sha1write(f, &offset, 4);
-               }
-
-               /* write the large offset table */
-               list = sorted_by_sha;
-               while (nr_large_offset) {
-                       struct object_entry *obj = *list++;
-                       uint64_t offset = obj->offset;
-                       if (offset > index_off32_limit) {
-                               uint32_t split[2];
-                               split[0]        = htonl(offset >> 32);
-                               split[1] = htonl(offset & 0xffffffff);
-                               sha1write(f, split, 8);
-                               nr_large_offset--;
-                       }
-               }
-       }
-
-       sha1write(f, sha1, 20);
-       sha1close(f, NULL, 1);
-       free(sorted_by_sha);
-       SHA1_Final(sha1, &ctx);
-       return index_name;
-}
-
 static void final(const char *final_pack_name, const char *curr_pack_name,
                  const char *final_index_name, const char *curr_index_name,
                  const char *keep_name, const char *keep_msg,
@@ -775,7 +634,8 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                                write_or_die(keep_fd, keep_msg, keep_msg_len);
                                write_or_die(keep_fd, "\n", 1);
                        }
-                       close(keep_fd);
+                       if (close(keep_fd) != 0)
+                               die("cannot write keep file");
                        report = "keep";
                }
        }
@@ -830,6 +690,7 @@ int main(int argc, char **argv)
        const char *curr_index, *index_name = NULL;
        const char *keep_name = NULL, *keep_msg = NULL;
        char *index_name_buf = NULL, *keep_name_buf = NULL;
+       struct pack_idx_entry **idx_objects;
        unsigned char sha1[20];
 
        for (i = 1; i < argc; i++) {
@@ -865,12 +726,12 @@ int main(int argc, char **argv)
                                index_name = argv[++i];
                        } else if (!prefixcmp(arg, "--index-version=")) {
                                char *c;
-                               index_default_version = strtoul(arg + 16, &c, 10);
-                               if (index_default_version > 2)
+                               pack_idx_default_version = strtoul(arg + 16, &c, 10);
+                               if (pack_idx_default_version > 2)
                                        die("bad %s", arg);
                                if (*c == ',')
-                                       index_off32_limit = strtoul(c+1, &c, 0);
-                               if (*c || index_off32_limit & 0x80000000)
+                                       pack_idx_off32_limit = strtoul(c+1, &c, 0);
+                               if (*c || pack_idx_off32_limit & 0x80000000)
                                        die("bad %s", arg);
                        } else
                                usage(index_pack_usage);
@@ -940,7 +801,13 @@ int main(int argc, char **argv)
                            nr_deltas - nr_resolved_deltas);
        }
        free(deltas);
-       curr_index = write_index_file(index_name, sha1);
+
+       idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
+       for (i = 0; i < nr_objects; i++)
+               idx_objects[i] = &objects[i].idx;
+       curr_index = write_idx_file(index_name, idx_objects, nr_objects, sha1);
+       free(idx_objects);
+
        final(pack_name, curr_pack,
                index_name, curr_index,
                keep_name, keep_msg,
index fb30694f4741147bba62350f704111d3afbf8133..00826778fc3d760a9b001423cd9c26e7972c126f 100644 (file)
@@ -44,33 +44,33 @@ void interp_clear_table(struct interp *table, int ninterps)
  *        { "%%", "%"},
  *    }
  *
- * Returns 1 on a successful substitution pass that fits in result,
- * Returns 0 on a failed or overflowing substitution pass.
+ * Returns 0 on a successful substitution pass that fits in result,
+ * Returns a number of bytes needed to hold the full substituted
+ * string otherwise.
  */
 
-int interpolate(char *result, int reslen,
+unsigned long interpolate(char *result, unsigned long reslen,
                const char *orig,
                const struct interp *interps, int ninterps)
 {
        const char *src = orig;
        char *dest = result;
-       int newlen = 0;
+       unsigned long newlen = 0;
        const char *name, *value;
-       int namelen, valuelen;
+       unsigned long namelen, valuelen;
        int i;
        char c;
 
         memset(result, 0, reslen);
 
-       while ((c = *src) && newlen < reslen - 1) {
+       while ((c = *src)) {
                if (c == '%') {
                        /* Try to match an interpolation string. */
                        for (i = 0; i < ninterps; i++) {
                                name = interps[i].name;
                                namelen = strlen(name);
-                               if (strncmp(src, name, namelen) == 0) {
+                               if (strncmp(src, name, namelen) == 0)
                                        break;
-                               }
                        }
 
                        /* Check for valid interpolation. */
@@ -78,29 +78,25 @@ int interpolate(char *result, int reslen,
                                value = interps[i].value;
                                valuelen = strlen(value);
 
-                               if (newlen + valuelen < reslen - 1) {
+                               if (newlen + valuelen + 1 < reslen) {
                                        /* Substitute. */
                                        strncpy(dest, value, valuelen);
-                                       newlen += valuelen;
                                        dest += valuelen;
-                                       src += namelen;
-                               } else {
-                                       /* Something's not fitting. */
-                                       return 0;
                                }
-
-                       } else {
-                               /* Skip bogus interpolation. */
-                               *dest++ = *src++;
-                               newlen++;
+                               newlen += valuelen;
+                               src += namelen;
+                               continue;
                        }
-
-               } else {
-                       /* Straight copy one non-interpolation character. */
-                       *dest++ = *src++;
-                       newlen++;
                }
+               /* Straight copy one non-interpolation character. */
+               if (newlen + 1 < reslen)
+                       *dest++ = *src;
+               src++;
+               newlen++;
        }
 
-       return newlen < reslen - 1;
+       if (newlen + 1 < reslen)
+               return 0;
+       else
+               return newlen + 2;
 }
index 16a26b998699a8a5a5b3472f4b57a574a292beef..77407e67dca97eb85274c69e2e7469e1d4d40b3b 100644 (file)
@@ -19,8 +19,8 @@ struct interp {
 extern void interp_set_entry(struct interp *table, int slot, const char *value);
 extern void interp_clear_table(struct interp *table, int ninterps);
 
-extern int interpolate(char *result, int reslen,
-                      const char *orig,
-                      const struct interp *interps, int ninterps);
+extern unsigned long interpolate(char *result, unsigned long reslen,
+                                const char *orig,
+                                const struct interp *interps, int ninterps);
 
 #endif /* INTERPOLATE_H */
index 310f8d39082a12d2c3daddd1fca454686e7425c3..e5c88c278fe23eefbf4cb4dd0c66251b208e48bd 100644 (file)
@@ -87,7 +87,7 @@ static void process_tree(struct rev_info *revs,
                        process_tree(revs,
                                     lookup_tree(entry.sha1),
                                     p, &me, entry.path);
-               else if (S_ISDIRLNK(entry.mode))
+               else if (S_ISGITLINK(entry.mode))
                        process_gitlink(revs, entry.sha1,
                                        p, &me, entry.path);
                else
index 4b650efa8b19424a61566b7ec87e7808874a6721..bf7ec6c2a3e25d09d3a43047d0cf3e1e6f883178 100644 (file)
@@ -114,7 +114,7 @@ static int fetch_pack(const unsigned char *sha1)
                return -1;
        target = find_sha1_pack(sha1, packs);
        if (!target)
-               return error("Couldn't find %s: not separate or in any pack", 
+               return error("Couldn't find %s: not separate or in any pack",
                             sha1_to_hex(sha1));
        if (get_verbosely) {
                fprintf(stderr, "Getting pack %s\n",
@@ -122,11 +122,11 @@ static int fetch_pack(const unsigned char *sha1)
                fprintf(stderr, " which contains %s\n",
                        sha1_to_hex(sha1));
        }
-       sprintf(filename, "%s/objects/pack/pack-%s.pack", 
+       sprintf(filename, "%s/objects/pack/pack-%s.pack",
                path, sha1_to_hex(target->sha1));
        copy_file(filename, sha1_pack_name(target->sha1),
                  sha1_to_hex(target->sha1), 1);
-       sprintf(filename, "%s/objects/pack/pack-%s.idx", 
+       sprintf(filename, "%s/objects/pack/pack-%s.idx",
                path, sha1_to_hex(target->sha1));
        copy_file(filename, sha1_pack_index_name(target->sha1),
                  sha1_to_hex(target->sha1), 1);
@@ -141,7 +141,7 @@ static int fetch_file(const unsigned char *sha1)
        char *hex = sha1_to_hex(sha1);
        char *dest_filename = sha1_file_name(sha1);
 
-       if (object_name_start < 0) {
+       if (object_name_start < 0) {
                strcpy(filename, path); /* e.g. git.git */
                strcat(filename, "/objects/");
                object_name_start = strlen(filename);
index 23db35aff21d7c33197726128e2f120291e6e9f0..9a1f64d8d71d13ee6be48c539d79764066288f07 100644 (file)
@@ -25,22 +25,123 @@ static void remove_lock_file_on_signal(int signo)
        raise(signo);
 }
 
+/*
+ * p = absolute or relative path name
+ *
+ * Return a pointer into p showing the beginning of the last path name
+ * element.  If p is empty or the root directory ("/"), just return p.
+ */
+static char *last_path_elm(char *p)
+{
+       /* r starts pointing to null at the end of the string */
+       char *r = strchr(p, '\0');
+
+       if (r == p)
+               return p; /* just return empty string */
+
+       r--; /* back up to last non-null character */
+
+       /* back up past trailing slashes, if any */
+       while (r > p && *r == '/')
+               r--;
+
+       /*
+        * then go backwards until I hit a slash, or the beginning of
+        * the string
+        */
+       while (r > p && *(r-1) != '/')
+               r--;
+       return r;
+}
+
+
+/* We allow "recursive" symbolic links. Only within reason, though */
+#define MAXDEPTH 5
+
+/*
+ * p = path that may be a symlink
+ * s = full size of p
+ *
+ * If p is a symlink, attempt to overwrite p with a path to the real
+ * file or directory (which may or may not exist), following a chain of
+ * symlinks if necessary.  Otherwise, leave p unmodified.
+ *
+ * This is a best-effort routine.  If an error occurs, p will either be
+ * left unmodified or will name a different symlink in a symlink chain
+ * that started with p's initial contents.
+ *
+ * Always returns p.
+ */
+
+static char *resolve_symlink(char *p, size_t s)
+{
+       int depth = MAXDEPTH;
+
+       while (depth--) {
+               char link[PATH_MAX];
+               int link_len = readlink(p, link, sizeof(link));
+               if (link_len < 0) {
+                       /* not a symlink anymore */
+                       return p;
+               }
+               else if (link_len < sizeof(link))
+                       /* readlink() never null-terminates */
+                       link[link_len] = '\0';
+               else {
+                       warning("%s: symlink too long", p);
+                       return p;
+               }
+
+               if (link[0] == '/') {
+                       /* absolute path simply replaces p */
+                       if (link_len < s)
+                               strcpy(p, link);
+                       else {
+                               warning("%s: symlink too long", p);
+                               return p;
+                       }
+               } else {
+                       /*
+                        * link is a relative path, so I must replace the
+                        * last element of p with it.
+                        */
+                       char *r = (char*)last_path_elm(p);
+                       if (r - p + link_len < s)
+                               strcpy(r, link);
+                       else {
+                               warning("%s: symlink too long", p);
+                               return p;
+                       }
+               }
+       }
+       return p;
+}
+
+
 static int lock_file(struct lock_file *lk, const char *path)
 {
        int fd;
-       sprintf(lk->filename, "%s.lock", path);
+
+       if (strlen(path) >= sizeof(lk->filename)) return -1;
+       strcpy(lk->filename, path);
+       /*
+        * subtract 5 from size to make sure there's room for adding
+        * ".lock" for the lock file name
+        */
+       resolve_symlink(lk->filename, sizeof(lk->filename)-5);
+       strcat(lk->filename, ".lock");
        fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
        if (0 <= fd) {
+               if (!lock_file_list) {
+                       signal(SIGINT, remove_lock_file_on_signal);
+                       atexit(remove_lock_file);
+               }
                lk->owner = getpid();
                if (!lk->on_list) {
                        lk->next = lock_file_list;
                        lock_file_list = lk;
                        lk->on_list = 1;
                }
-               if (lock_file_list) {
-                       signal(SIGINT, remove_lock_file_on_signal);
-                       atexit(remove_lock_file);
-               }
                if (adjust_shared_perm(lk->filename))
                        return error("cannot fix permission bits on %s",
                                     lk->filename);
@@ -97,4 +198,3 @@ void rollback_lock_file(struct lock_file *lk)
                unlink(lk->filename);
        lk->filename[0] = 0;
 }
-
index 4bef909144b2b5b402ee7cf0d959a0995c00ff0e..a6423718e76be34e62bc76dcf3c93b32e38aa152 100644 (file)
@@ -79,16 +79,25 @@ static int detect_any_signoff(char *letter, int size)
        return seen_head && seen_name;
 }
 
-static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
+static unsigned long append_signoff(char **buf_p, unsigned long *buf_sz_p,
+                                   unsigned long at, const char *signoff)
 {
        static const char signed_off_by[] = "Signed-off-by: ";
-       int signoff_len = strlen(signoff);
+       size_t signoff_len = strlen(signoff);
        int has_signoff = 0;
-       char *cp = buf;
-
-       /* Do we have enough space to add it? */
-       if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3)
-               return at;
+       char *cp;
+       char *buf;
+       unsigned long buf_sz;
+
+       buf = *buf_p;
+       buf_sz = *buf_sz_p;
+       if (buf_sz <= at + strlen(signed_off_by) + signoff_len + 3) {
+               buf_sz += strlen(signed_off_by) + signoff_len + 3;
+               buf = xrealloc(buf, buf_sz);
+               *buf_p = buf;
+               *buf_sz_p = buf_sz;
+       }
+       cp = buf;
 
        /* First see if we already have the sign-off by the signer */
        while ((cp = strstr(cp, signed_off_by))) {
@@ -133,7 +142,8 @@ static unsigned int digits_in_number(unsigned int number)
 
 void show_log(struct rev_info *opt, const char *sep)
 {
-       static char this_header[16384];
+       char *msgbuf = NULL;
+       unsigned long msgbuf_len = 0;
        struct log_info *log = opt->loginfo;
        struct commit *commit = log->commit, *parent = log->parent;
        int abbrev = opt->diffopt.abbrev;
@@ -190,7 +200,7 @@ void show_log(struct rev_info *opt, const char *sep)
                                        digits_in_number(opt->total),
                                        opt->nr, opt->total);
                        subject = buffer;
-               } else if (opt->total == 0) {
+               } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
                        static char buffer[256];
                        snprintf(buffer, sizeof(buffer),
                                        "Subject: [%s] ",
@@ -278,14 +288,18 @@ void show_log(struct rev_info *opt, const char *sep)
        /*
         * And then the pretty-printed message itself
         */
-       len = pretty_print_commit(opt->commit_format, commit, ~0u, this_header,
-                                 sizeof(this_header), abbrev, subject,
+       len = pretty_print_commit(opt->commit_format, commit, ~0u,
+                                 &msgbuf, &msgbuf_len, abbrev, subject,
                                  extra_headers, opt->date_mode);
 
        if (opt->add_signoff)
-               len = append_signoff(this_header, sizeof(this_header), len,
+               len = append_signoff(&msgbuf, &msgbuf_len, len,
                                     opt->add_signoff);
-       printf("%s%s%s", this_header, extra, sep);
+       if (opt->show_log_size)
+               printf("log size %i\n", len);
+
+       printf("%s%s%s", msgbuf, extra, sep);
+       free(msgbuf);
 }
 
 int log_tree_diff_flush(struct rev_info *opt)
@@ -397,5 +411,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)
                shown = 1;
        }
        opt->loginfo = NULL;
+       maybe_flush_or_die(stdout, "stdout");
        return shown;
 }
index cb567a2832d9f8c784aea388af9144c12eddc7d5..87141670595c50a4fa7b87a12b85ff0e2b7da8af 100644 (file)
--- a/mailmap.c
+++ b/mailmap.c
@@ -89,4 +89,3 @@ int map_email(struct path_list *map, const char *email, char *name, int maxlen)
        }
        return 0;
 }
-
index 23cafe47b495ba40fbdd46eb35943be5f722a1d7..d7e29c4d1d3e44c85e0eeb28040e8ea945090594 100644 (file)
@@ -301,4 +301,3 @@ void shift_tree(const unsigned char *hash1,
 
        splice_tree(hash1, add_prefix, hash2, shifted);
 }
-
index 748d15c0e04c0d63fbe586ad59c795ddaf3dec92..1e031eafe0c70eec092a53e8ab291b9887622b0e 100644 (file)
@@ -62,6 +62,7 @@ static int generate_common_file(mmfile_t *res, mmfile_t *f1, mmfile_t *f2)
        xdemitcb_t ecb;
 
        xpp.flags = XDF_NEED_MINIMAL;
+       memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = 3;
        xecfg.flags = XDL_EMIT_COMMON;
        ecb.outf = common_outf;
index 5599fd321bb4c09c1c0ce4fc62dfb9ecf5531c2b..fa719cb0b1bd227423587c9e41eed77c755465a4 100644 (file)
@@ -25,7 +25,7 @@ static void run_program(void)
 static int merge_entry(int pos, const char *path)
 {
        int found;
-       
+
        if (pos >= active_nr)
                die("git-merge-index: %s not in the cache", path);
        arguments[0] = pgm;
index 4a82b741ae020376ac67b34d5fce86e8a87a3b5d..16f6a0f98b5df8fbb9bb5263d0d32afca678cbec 100644 (file)
@@ -127,7 +127,7 @@ static void output(int v, const char *fmt, ...)
        va_end(args);
 }
 
-static void flush_output()
+static void flush_output(void)
 {
        struct output_buffer *b, *n;
        for (b = output_list; b; b = n) {
@@ -216,13 +216,19 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
  */
 static int index_only = 0;
 
+static void init_tree_desc_from_tree(struct tree_desc *desc, struct tree *tree)
+{
+       parse_tree(tree);
+       init_tree_desc(desc, tree->buffer, tree->size);
+}
+
 static int git_merge_trees(int index_only,
                           struct tree *common,
                           struct tree *head,
                           struct tree *merge)
 {
        int rc;
-       struct object_list *trees = NULL;
+       struct tree_desc t[3];
        struct unpack_trees_options opts;
 
        memset(&opts, 0, sizeof(opts));
@@ -234,11 +240,11 @@ static int git_merge_trees(int index_only,
        opts.head_idx = 2;
        opts.fn = threeway_merge;
 
-       object_list_append(&common->object, &trees);
-       object_list_append(&head->object, &trees);
-       object_list_append(&merge->object, &trees);
+       init_tree_desc_from_tree(t+0, common);
+       init_tree_desc_from_tree(t+1, head);
+       init_tree_desc_from_tree(t+2, merge);
 
-       rc = unpack_trees(trees, &opts);
+       rc = unpack_trees(3, t, &opts);
        cache_tree_free(&active_cache_tree);
        return rc;
 }
@@ -671,6 +677,26 @@ struct ll_merge_driver {
 /*
  * Built-in low-levels
  */
+static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
+                          const char *path_unused,
+                          mmfile_t *orig,
+                          mmfile_t *src1, const char *name1,
+                          mmfile_t *src2, const char *name2,
+                          mmbuffer_t *result)
+{
+       /*
+        * The tentative merge result is "ours" for the final round,
+        * or common ancestor for an internal merge.  Still return
+        * "conflicted merge" status.
+        */
+       mmfile_t *stolen = index_only ? orig : src1;
+
+       result->ptr = stolen->ptr;
+       result->size = stolen->size;
+       stolen->ptr = NULL;
+       return 1;
+}
+
 static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
                        const char *path_unused,
                        mmfile_t *orig,
@@ -681,10 +707,15 @@ static int ll_xdl_merge(const struct ll_merge_driver *drv_unused,
        xpparam_t xpp;
 
        if (buffer_is_binary(orig->ptr, orig->size) ||
-                       buffer_is_binary(src1->ptr, src1->size) ||
-                       buffer_is_binary(src2->ptr, src2->size))
-               return error("Cannot merge binary files: %s vs. %s\n",
+           buffer_is_binary(src1->ptr, src1->size) ||
+           buffer_is_binary(src2->ptr, src2->size)) {
+               warning("Cannot merge binary files: %s vs. %s\n",
                        name1, name2);
+               return ll_binary_merge(drv_unused, path_unused,
+                                      orig, src1, name1,
+                                      src2, name2,
+                                      result);
+       }
 
        memset(&xpp, 0, sizeof(xpp));
        return xdl_merge(orig,
@@ -737,26 +768,6 @@ static int ll_union_merge(const struct ll_merge_driver *drv_unused,
        return 0;
 }
 
-static int ll_binary_merge(const struct ll_merge_driver *drv_unused,
-                          const char *path_unused,
-                          mmfile_t *orig,
-                          mmfile_t *src1, const char *name1,
-                          mmfile_t *src2, const char *name2,
-                          mmbuffer_t *result)
-{
-       /*
-        * The tentative merge result is "ours" for the final round,
-        * or common ancestor for an internal merge.  Still return
-        * "conflicted merge" status.
-        */
-       mmfile_t *stolen = index_only ? orig : src1;
-
-       result->ptr = stolen->ptr;
-       result->size = stolen->size;
-       stolen->ptr = NULL;
-       return 1;
-}
-
 #define LL_BINARY_MERGE 0
 #define LL_TEXT_MERGE 1
 #define LL_UNION_MERGE 2
@@ -771,9 +782,7 @@ static void create_temp(mmfile_t *src, char *path)
        int fd;
 
        strcpy(path, ".merge_file_XXXXXX");
-       fd = mkstemp(path);
-       if (fd < 0)
-               die("unable to create temp-file");
+       fd = xmkstemp(path);
        if (write_in_full(fd, src->ptr, src->size) != src->size)
                die("unable to write temp-file");
        close(fd);
index 3b8d9e6887ae051bf61cc0833f97d83bb47a0bae..7d4f628444058d80e798e141285d558fb7104c5b 100644 (file)
@@ -106,8 +106,8 @@ static void show_diff(struct merge_list *entry)
        xdemitcb_t ecb;
 
        xpp.flags = XDF_NEED_MINIMAL;
+       memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = 3;
-       xecfg.flags = 0;
        ecb.outf = show_outf;
        ecb.priv = NULL;
 
diff --git a/mktag.c b/mktag.c
index 931011121e7276513b55fc41691c65a0d924119d..38acd5a295d5f06fb2db60633e89a06ba634beb8 100644 (file)
--- a/mktag.c
+++ b/mktag.c
  * The first three lines are guaranteed to be at least 63 bytes:
  * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the
  * shortest possible type-line, and "tag .\n" at 6 bytes is the
- * shortest single-character-tag line. 
- *
- * We also artificially limit the size of the full object to 8kB.
- * Just because I'm a lazy bastard, and if you can't fit a signature
- * in that size, you're doing something wrong.
+ * shortest single-character-tag line.
  */
 
-/* Some random size */
-#define MAXSIZE (8192)
-
 /*
  * We refuse to tag something we can't verify. Just because.
  */
@@ -127,7 +120,7 @@ int main(int argc, char **argv)
 
        setup_git_directory();
 
-       if (read_pipe(0, &buffer, &size)) {
+       if (read_fd(0, &buffer, &size)) {
                free(buffer);
                die("could not read from stdin");
        }
index 847531d19f88e420ad68f95f11ab2f1f77876d08..3f06b835675206912777a774d91c3ba611fa5a06 100644 (file)
@@ -1,29 +1,29 @@
-/* 
+/*
  * The contents of this file are subject to the Mozilla Public
  * License Version 1.1 (the "License"); you may not use this file
  * except in compliance with the License. You may obtain a copy of
  * the License at http://www.mozilla.org/MPL/
- * 
+ *
  * Software distributed under the License is distributed on an "AS
  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  * implied. See the License for the specific language governing
  * rights and limitations under the License.
- * 
+ *
  * The Original Code is SHA 180-1 Reference Implementation (Compact version)
- * 
+ *
  * The Initial Developer of the Original Code is Paul Kocher of
- * Cryptography Research.  Portions created by Paul Kocher are 
+ * Cryptography Research.  Portions created by Paul Kocher are
  * Copyright (C) 1995-9 by Cryptography Research, Inc.  All
  * Rights Reserved.
- * 
+ *
  * Contributor(s):
  *
  *     Paul Kocher
- * 
+ *
  * Alternatively, the contents of this file may be used under the
  * terms of the GNU General Public License Version 2 or later (the
- * "GPL"), in which case the provisions of the GPL are applicable 
- * instead of those above.  If you wish to allow use of your 
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above.  If you wish to allow use of your
  * version of this file only under the terms of the GPL and not to
  * allow others to use your version of this file under the MPL,
  * indicate your decision by deleting the provisions above and
@@ -149,4 +149,3 @@ static void shaHashBlock(SHA_CTX *ctx) {
   ctx->H[3] += D;
   ctx->H[4] += E;
 }
-
index 5d82afa3bdd21a2855c569a73cf3277b5c6a41bc..16f2d3d43ca8bee1eeb306308277bef8c707a972 100644 (file)
@@ -1,29 +1,29 @@
-/* 
+/*
  * The contents of this file are subject to the Mozilla Public
  * License Version 1.1 (the "License"); you may not use this file
  * except in compliance with the License. You may obtain a copy of
  * the License at http://www.mozilla.org/MPL/
- * 
+ *
  * Software distributed under the License is distributed on an "AS
  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  * implied. See the License for the specific language governing
  * rights and limitations under the License.
- * 
+ *
  * The Original Code is SHA 180-1 Header File
- * 
+ *
  * The Initial Developer of the Original Code is Paul Kocher of
- * Cryptography Research.  Portions created by Paul Kocher are 
+ * Cryptography Research.  Portions created by Paul Kocher are
  * Copyright (C) 1995-9 by Cryptography Research, Inc.  All
  * Rights Reserved.
- * 
+ *
  * Contributor(s):
  *
  *     Paul Kocher
- * 
+ *
  * Alternatively, the contents of this file may be used under the
  * terms of the GNU General Public License Version 2 or later (the
- * "GPL"), in which case the provisions of the GPL are applicable 
- * instead of those above.  If you wish to allow use of your 
+ * "GPL"), in which case the provisions of the GPL are applicable
+ * instead of those above.  If you wish to allow use of your
  * version of this file only under the terms of the GPL and not to
  * allow others to use your version of this file under the MPL,
  * indicate your decision by deleting the provisions above and
index 022e8d841c7f5498b6f008647ed37c430826b443..5345671569e9500ad31124dacf9525739687d9be 100644 (file)
@@ -85,5 +85,3 @@ void mark_reachable(struct object *obj, unsigned int mask)
                        mark_reachable(refs->ref[i], mask);
        }
 }
-
-
index 94f19eed86ceebcc85770a379faf7b1270d1fad4..397bbfa090cd281214013c42a5f0b1de6063a861 100644 (file)
--- a/object.h
+++ b/object.h
@@ -66,7 +66,7 @@ void set_object_refs(struct object *obj, struct object_refs *refs);
 
 void mark_reachable(struct object *obj, unsigned int mask);
 
-struct object_list *object_list_insert(struct object *item, 
+struct object_list *object_list_insert(struct object *item,
                                       struct object_list **list_p);
 
 void object_list_append(struct object *item,
index d04536bbff7cba22ca67521d45e690dfa5aa8675..d7dd62bb8346c4cac8dbd7334e999a450c21c5ab 100644 (file)
@@ -1,6 +1,23 @@
 #include "cache.h"
 #include "pack.h"
 
+struct idx_entry
+{
+       const unsigned char *sha1;
+       off_t                offset;
+};
+
+static int compare_entries(const void *e1, const void *e2)
+{
+       const struct idx_entry *entry1 = e1;
+       const struct idx_entry *entry2 = e2;
+       if (entry1->offset < entry2->offset)
+               return -1;
+       if (entry1->offset > entry2->offset)
+               return 1;
+       return 0;
+}
+
 static int verify_packfile(struct packed_git *p,
                struct pack_window **w_curs)
 {
@@ -11,6 +28,7 @@ static int verify_packfile(struct packed_git *p,
        off_t offset = 0, pack_sig = p->pack_size - 20;
        uint32_t nr_objects, i;
        int err;
+       struct idx_entry *entries;
 
        /* Note that the pack header checks are actually performed by
         * use_pack when it first opens the pack file.  If anything
@@ -41,44 +59,48 @@ static int verify_packfile(struct packed_git *p,
         * we do not do scan-streaming check on the pack file.
         */
        nr_objects = p->num_objects;
+       entries = xmalloc(nr_objects * sizeof(*entries));
+       /* first sort entries by pack offset, since unpacking them is more efficient that way */
+       for (i = 0; i < nr_objects; i++) {
+               entries[i].sha1 = nth_packed_object_sha1(p, i);
+               if (!entries[i].sha1)
+                       die("internal error pack-check nth-packed-object");
+               entries[i].offset = find_pack_entry_one(entries[i].sha1, p);
+               if (!entries[i].offset)
+                       die("internal error pack-check find-pack-entry-one");
+       }
+       qsort(entries, nr_objects, sizeof(*entries), compare_entries);
+
        for (i = 0, err = 0; i < nr_objects; i++) {
-               const unsigned char *sha1;
                void *data;
                enum object_type type;
                unsigned long size;
-               off_t offset;
 
-               sha1 = nth_packed_object_sha1(p, i);
-               if (!sha1)
-                       die("internal error pack-check nth-packed-object");
-               offset = find_pack_entry_one(sha1, p);
-               if (!offset)
-                       die("internal error pack-check find-pack-entry-one");
-               data = unpack_entry(p, offset, &type, &size);
+               data = unpack_entry(p, entries[i].offset, &type, &size);
                if (!data) {
                        err = error("cannot unpack %s from %s",
-                                   sha1_to_hex(sha1), p->pack_name);
+                                   sha1_to_hex(entries[i].sha1), p->pack_name);
                        continue;
                }
-               if (check_sha1_signature(sha1, data, size, typename(type))) {
+               if (check_sha1_signature(entries[i].sha1, data, size, typename(type))) {
                        err = error("packed %s from %s is corrupt",
-                                   sha1_to_hex(sha1), p->pack_name);
+                                   sha1_to_hex(entries[i].sha1), p->pack_name);
                        free(data);
                        continue;
                }
                free(data);
        }
+       free(entries);
 
        return err;
 }
 
 
-#define MAX_CHAIN 40
+#define MAX_CHAIN 50
 
 static void show_pack_info(struct packed_git *p)
 {
-       uint32_t nr_objects, i, chain_histogram[MAX_CHAIN];
-
+       uint32_t nr_objects, i, chain_histogram[MAX_CHAIN+1];
        nr_objects = p->num_objects;
        memset(chain_histogram, 0, sizeof(chain_histogram));
 
@@ -109,32 +131,37 @@ static void show_pack_info(struct packed_git *p)
                        printf("%-6s %lu %"PRIuMAX" %u %s\n",
                               type, size, (uintmax_t)offset,
                               delta_chain_length, sha1_to_hex(base_sha1));
-                       if (delta_chain_length < MAX_CHAIN)
+                       if (delta_chain_length <= MAX_CHAIN)
                                chain_histogram[delta_chain_length]++;
                        else
                                chain_histogram[0]++;
                }
        }
 
-       for (i = 0; i < MAX_CHAIN; i++) {
+       for (i = 0; i <= MAX_CHAIN; i++) {
                if (!chain_histogram[i])
                        continue;
-               printf("chain length %s %d: %d object%s\n",
-                      i ? "=" : ">=",
-                      i ? i : MAX_CHAIN,
-                      chain_histogram[i],
-                      1 < chain_histogram[i] ? "s" : "");
+               printf("chain length = %d: %d object%s\n", i,
+                      chain_histogram[i], chain_histogram[i] > 1 ? "s" : "");
        }
+       if (chain_histogram[0])
+               printf("chain length > %d: %d object%s\n", MAX_CHAIN,
+                      chain_histogram[0], chain_histogram[0] > 1 ? "s" : "");
 }
 
 int verify_pack(struct packed_git *p, int verbose)
 {
-       off_t index_size = p->index_size;
-       const unsigned char *index_base = p->index_data;
+       off_t index_size;
+       const unsigned char *index_base;
        SHA_CTX ctx;
        unsigned char sha1[20];
        int ret;
 
+       if (open_pack_index(p))
+               return error("packfile %s index not opened", p->pack_name);
+       index_size = p->index_size;
+       index_base = p->index_data;
+
        ret = 0;
        /* Verify SHA1 sum of the index file */
        SHA1_Init(&ctx);
index 87077e150c1b53a26464089a2cabafafb32e6636..f5cd0ac59e5794a375172b998399a546eaef4ab1 100644 (file)
@@ -81,7 +81,7 @@ static struct llist * llist_copy(struct llist *list)
 {
        struct llist *ret;
        struct llist_item *new, *old, *prev;
-       
+
        llist_init(&ret);
 
        if ((ret->size = list->size) == 0)
@@ -100,7 +100,7 @@ static struct llist * llist_copy(struct llist *list)
        }
        new->next = NULL;
        ret->back = new;
-       
+
        return ret;
 }
 
@@ -550,6 +550,9 @@ static struct pack_list * add_pack(struct packed_git *p)
        l.pack = p;
        llist_init(&l.all_objects);
 
+       if (open_pack_index(p))
+               return NULL;
+
        base = p->index_data;
        base += 256 * 4 + ((p->index_version < 2) ? 4 : 8);
        step = (p->index_version < 2) ? 24 : 20;
index ae2e481e5554cfa66cf137d38c38f961b9aa8ec7..e59b197e5ebb301107f9a18b7765e18097a1c8e3 100644 (file)
@@ -1,5 +1,147 @@
 #include "cache.h"
 #include "pack.h"
+#include "csum-file.h"
+
+uint32_t pack_idx_default_version = 1;
+uint32_t pack_idx_off32_limit = 0x7fffffff;
+
+static int sha1_compare(const void *_a, const void *_b)
+{
+       struct pack_idx_entry *a = *(struct pack_idx_entry **)_a;
+       struct pack_idx_entry *b = *(struct pack_idx_entry **)_b;
+       return hashcmp(a->sha1, b->sha1);
+}
+
+/*
+ * On entry *sha1 contains the pack content SHA1 hash, on exit it is
+ * the SHA1 hash of sorted object names. The objects array passed in
+ * will be sorted by SHA1 on exit.
+ */
+const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1)
+{
+       struct sha1file *f;
+       struct pack_idx_entry **sorted_by_sha, **list, **last;
+       off_t last_obj_offset = 0;
+       uint32_t array[256];
+       int i, fd;
+       SHA_CTX ctx;
+       uint32_t index_version;
+
+       if (nr_objects) {
+               sorted_by_sha = objects;
+               list = sorted_by_sha;
+               last = sorted_by_sha + nr_objects;
+               for (i = 0; i < nr_objects; ++i) {
+                       if (objects[i]->offset > last_obj_offset)
+                               last_obj_offset = objects[i]->offset;
+               }
+               qsort(sorted_by_sha, nr_objects, sizeof(sorted_by_sha[0]),
+                     sha1_compare);
+       }
+       else
+               sorted_by_sha = list = last = NULL;
+
+       if (!index_name) {
+               static char tmpfile[PATH_MAX];
+               snprintf(tmpfile, sizeof(tmpfile),
+                        "%s/tmp_idx_XXXXXX", get_object_directory());
+               fd = xmkstemp(tmpfile);
+               index_name = xstrdup(tmpfile);
+       } else {
+               unlink(index_name);
+               fd = open(index_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+       }
+       if (fd < 0)
+               die("unable to create %s: %s", index_name, strerror(errno));
+       f = sha1fd(fd, index_name);
+
+       /* if last object's offset is >= 2^31 we should use index V2 */
+       index_version = (last_obj_offset >> 31) ? 2 : pack_idx_default_version;
+
+       /* index versions 2 and above need a header */
+       if (index_version >= 2) {
+               struct pack_idx_header hdr;
+               hdr.idx_signature = htonl(PACK_IDX_SIGNATURE);
+               hdr.idx_version = htonl(index_version);
+               sha1write(f, &hdr, sizeof(hdr));
+       }
+
+       /*
+        * Write the first-level table (the list is sorted,
+        * but we use a 256-entry lookup to be able to avoid
+        * having to do eight extra binary search iterations).
+        */
+       for (i = 0; i < 256; i++) {
+               struct pack_idx_entry **next = list;
+               while (next < last) {
+                       struct pack_idx_entry *obj = *next;
+                       if (obj->sha1[0] != i)
+                               break;
+                       next++;
+               }
+               array[i] = htonl(next - sorted_by_sha);
+               list = next;
+       }
+       sha1write(f, array, 256 * 4);
+
+       /* compute the SHA1 hash of sorted object names. */
+       SHA1_Init(&ctx);
+
+       /*
+        * Write the actual SHA1 entries..
+        */
+       list = sorted_by_sha;
+       for (i = 0; i < nr_objects; i++) {
+               struct pack_idx_entry *obj = *list++;
+               if (index_version < 2) {
+                       uint32_t offset = htonl(obj->offset);
+                       sha1write(f, &offset, 4);
+               }
+               sha1write(f, obj->sha1, 20);
+               SHA1_Update(&ctx, obj->sha1, 20);
+       }
+
+       if (index_version >= 2) {
+               unsigned int nr_large_offset = 0;
+
+               /* write the crc32 table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct pack_idx_entry *obj = *list++;
+                       uint32_t crc32_val = htonl(obj->crc32);
+                       sha1write(f, &crc32_val, 4);
+               }
+
+               /* write the 32-bit offset table */
+               list = sorted_by_sha;
+               for (i = 0; i < nr_objects; i++) {
+                       struct pack_idx_entry *obj = *list++;
+                       uint32_t offset = (obj->offset <= pack_idx_off32_limit) ?
+                               obj->offset : (0x80000000 | nr_large_offset++);
+                       offset = htonl(offset);
+                       sha1write(f, &offset, 4);
+               }
+
+               /* write the large offset table */
+               list = sorted_by_sha;
+               while (nr_large_offset) {
+                       struct pack_idx_entry *obj = *list++;
+                       uint64_t offset = obj->offset;
+                       if (offset > pack_idx_off32_limit) {
+                               uint32_t split[2];
+                               split[0] = htonl(offset >> 32);
+                               split[1] = htonl(offset & 0xffffffff);
+                               sha1write(f, split, 8);
+                               nr_large_offset--;
+                       }
+               }
+       }
+
+       sha1write(f, sha1, 20);
+       sha1close(f, NULL, 1);
+       SHA1_Final(sha1, &ctx);
+       return index_name;
+}
 
 void fixup_pack_header_footer(int pack_fd,
                         unsigned char *pack_file_sha1,
diff --git a/pack.h b/pack.h
index d667fb8d5a49b4480beaa6f384f1f6f86b035806..f357c9f4282d5bc8bbcff6f3a44b9812415745a6 100644 (file)
--- a/pack.h
+++ b/pack.h
@@ -34,6 +34,10 @@ struct pack_header {
  */
 #define PACK_IDX_SIGNATURE 0xff744f63  /* "\377tOc" */
 
+/* These may be overridden by command-line parameters */
+extern uint32_t pack_idx_default_version;
+extern uint32_t pack_idx_off32_limit;
+
 /*
  * Packed object index header
  */
@@ -42,6 +46,16 @@ struct pack_idx_header {
        uint32_t idx_version;
 };
 
+/*
+ * Common part of object structure used for write_idx_file
+ */
+struct pack_idx_entry {
+       unsigned char sha1[20];
+       uint32_t crc32;
+       off_t offset;
+};
+
+extern const char *write_idx_file(const char *index_name, struct pack_idx_entry **objects, int nr_objects, unsigned char *sha1);
 
 extern int verify_pack(struct packed_git *, int);
 extern void fixup_pack_header_footer(int, unsigned char *, const char *, uint32_t);
diff --git a/pager.c b/pager.c
index 5f280ab52720772905cacbcba522ecc9c81bb529..8bac9d990381f5664333a92f68b0b8cd97d43855 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -31,6 +31,11 @@ void setup_pager(void)
 
        if (!isatty(1))
                return;
+       if (!pager) {
+               if (!pager_program)
+                       git_config(git_default_config);
+               pager = pager_program;
+       }
        if (!pager)
                pager = getenv("PAGER");
        if (!pager)
index 086d2d9c68835c6a7434932f6e3b430e0906578d..9349bc5580456b378d41da7cc2518e4fa9a7e81a 100644 (file)
@@ -81,4 +81,4 @@ int main(int argc, char **argv)
 
        generate_id_list();
        return 0;
-}      
+}
index caaa5cc57b05e1a27bb4fd8f49bb74e06b9936e3..3d83b7ba9e8c934db8d8ecc9d545cab25be89837 100644 (file)
@@ -76,16 +76,18 @@ struct path_list_item *path_list_lookup(const char *path, struct path_list *list
        return list->items + i;
 }
 
-void path_list_clear(struct path_list *list, int free_items)
+void path_list_clear(struct path_list *list, int free_util)
 {
        if (list->items) {
                int i;
-               if (free_items)
-                       for (i = 0; i < list->nr; i++) {
-                               if (list->strdup_paths)
-                                       free(list->items[i].path);
+               if (list->strdup_paths) {
+                       for (i = 0; i < list->nr; i++)
+                               free(list->items[i].path);
+               }
+               if (free_util) {
+                       for (i = 0; i < list->nr; i++)
                                free(list->items[i].util);
-                       }
+               }
                free(list->items);
        }
        list->items = NULL;
@@ -100,4 +102,3 @@ void print_path_list(const char *text, const struct path_list *p)
        for (i = 0; i < p->nr; i++)
                printf("%s:%p\n", p->items[i].path, p->items[i].util);
 }
-
index ce5ffabcce347b87e48d120f55fa0a8d07cc5193..5931e2cc0ca23bd8b7f7f90d471cf3b8e95df456 100644 (file)
@@ -15,7 +15,7 @@ struct path_list
 void print_path_list(const char *text, const struct path_list *p);
 
 int path_list_has_path(const struct path_list *list, const char *path);
-void path_list_clear(struct path_list *list, int free_items);
+void path_list_clear(struct path_list *list, int free_util);
 struct path_list_item *path_list_insert(const char *path, struct path_list *list);
 struct path_list_item *path_list_lookup(const char *path, struct path_list *list);
 
diff --git a/path.c b/path.c
index 6395cf23098841c16d993ae7e86b2d8dcff01cd7..42609524a55ad017557d48bedf26906cc4405d0a 100644 (file)
--- a/path.c
+++ b/path.c
@@ -71,21 +71,17 @@ char *git_path(const char *fmt, ...)
 /* git_mkstemp() - create tmp file honoring TMPDIR variable */
 int git_mkstemp(char *path, size_t len, const char *template)
 {
-       char *env, *pch = path;
-
-       if ((env = getenv("TMPDIR")) == NULL) {
-               strcpy(pch, "/tmp/");
-               len -= 5;
-               pch += 5;
-       } else {
-               size_t n = snprintf(pch, len, "%s/", env);
-
-               len -= n;
-               pch += n;
+       const char *tmp;
+       size_t n;
+
+       tmp = getenv("TMPDIR");
+       if (!tmp)
+               tmp = "/tmp";
+       n = snprintf(path, len, "%s/%s", tmp, template);
+       if (len <= n) {
+               errno = ENAMETOOLONG;
+               return -1;
        }
-
-       strlcpy(pch, template, len);
-
        return mkstemp(path);
 }
 
@@ -252,7 +248,7 @@ char *enter_repo(char *path, int strict)
 
        if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
            validate_headref("HEAD") == 0) {
-               setenv("GIT_DIR", ".", 1);
+               setenv(GIT_DIR_ENVIRONMENT, ".", 1);
                check_repository_format();
                return path;
        }
@@ -292,3 +288,68 @@ int adjust_shared_perm(const char *path)
                return -2;
        return 0;
 }
+
+/* We allow "recursive" symbolic links. Only within reason, though. */
+#define MAXDEPTH 5
+
+const char *make_absolute_path(const char *path)
+{
+       static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
+       char cwd[1024] = "";
+       int buf_index = 1, len;
+
+       int depth = MAXDEPTH;
+       char *last_elem = NULL;
+       struct stat st;
+
+       if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+               die ("Too long path: %.*s", 60, path);
+
+       while (depth--) {
+               if (stat(buf, &st) || !S_ISDIR(st.st_mode)) {
+                       char *last_slash = strrchr(buf, '/');
+                       if (last_slash) {
+                               *last_slash = '\0';
+                               last_elem = xstrdup(last_slash + 1);
+                       } else
+                               last_elem = xstrdup(buf);
+               }
+
+               if (*buf) {
+                       if (!*cwd && !getcwd(cwd, sizeof(cwd)))
+                               die ("Could not get current working directory");
+
+                       if (chdir(buf))
+                               die ("Could not switch to '%s'", buf);
+               }
+               if (!getcwd(buf, PATH_MAX))
+                       die ("Could not get current working directory");
+
+               if (last_elem) {
+                       int len = strlen(buf);
+                       if (len + strlen(last_elem) + 2 > PATH_MAX)
+                               die ("Too long path name: '%s/%s'",
+                                               buf, last_elem);
+                       buf[len] = '/';
+                       strcpy(buf + len + 1, last_elem);
+                       free(last_elem);
+                       last_elem = NULL;
+               }
+
+               if (!lstat(buf, &st) && S_ISLNK(st.st_mode)) {
+                       len = readlink(buf, next_buf, PATH_MAX);
+                       if (len < 0)
+                               die ("Invalid symlink: %s", buf);
+                       next_buf[len] = '\0';
+                       buf = next_buf;
+                       buf_index = 1 - buf_index;
+                       next_buf = bufs[buf_index];
+               } else
+                       break;
+       }
+
+       if (*cwd && chdir(cwd))
+               die ("Could not change back to '%s'", cwd);
+
+       return buf;
+}
index 96bfac498bdc58c90c9bc136a1df7bac9bfa0d92..ceb787170e17df130e26658e5a189bb63684ab3e 100644 (file)
@@ -64,7 +64,7 @@ int main(int argc, char **argv)
        if (!dest || i != argc - 1)
                usage(peek_remote_usage);
 
-       pid = git_connect(fd, dest, uploadpack);
+       pid = git_connect(fd, dest, uploadpack, 0);
        if (pid < 0)
                return 1;
        ret = peek_remote(fd, flags);
index 0d695fd2f35f91b47455dc382d86b52bb51088d2..5e079ad01126845c39fd9583fa742f54d5658b49 100644 (file)
@@ -40,4 +40,3 @@ endif
 # (even though GIT-CFLAGS aren't used yet. If ever)
 ../GIT-CFLAGS:
        $(MAKE) -C .. GIT-CFLAGS
-
index b60526869a38bd20f800c275f7b42390c8bea1ee..355546a1ad844492234dc7ee91528c525af5610a 100644 (file)
@@ -5,7 +5,7 @@
  * Write a packetized stream, where each line is preceded by
  * its length (including the header) as a 4-byte hex number.
  * A length of 'zero' means end of stream (and a length of 1-3
- * would be an error). 
+ * would be an error).
  *
  * This is all pretty stupid, but we use this packetized line
  * format to make a streaming format possible without ever
index 0820398b004dd8af93a25e7fa26be60cf4dda410..738e36c1e81def4822ccc2a66bc2761402a07f26 100644 (file)
@@ -50,7 +50,7 @@ int SHA1_Update(SHA_CTX *c, const void *ptr, unsigned long n)
                p += nb;
        }
        return 0;
-}      
+}
 
 int SHA1_Final(unsigned char *hash, SHA_CTX *c)
 {
index 05f7890314e0bd596eb2e4110cdb98eeea61c9da..4344f4eed5e46c4e013018af8ef9ab062f740d8f 100644 (file)
@@ -62,11 +62,13 @@ int display_progress(struct progress *progress, unsigned n)
                        fprintf(stderr, "%s%4u%% (%u/%u) done\r",
                                progress->prefix, percent, n, progress->total);
                        progress_update = 0;
+                       progress->need_lf = 1;
                        return 1;
                }
        } else if (progress_update) {
                fprintf(stderr, "%s%u\r", progress->prefix, n);
                progress_update = 0;
+               progress->need_lf = 1;
                return 1;
        }
        return 0;
@@ -80,6 +82,7 @@ void start_progress(struct progress *progress, const char *title,
        progress->total = total;
        progress->last_percent = -1;
        progress->delay = 0;
+       progress->need_lf = 0;
        if (snprintf(buf, sizeof(buf), title, total))
                fprintf(stderr, "%s\n", buf);
        set_progress_signal();
@@ -95,12 +98,13 @@ void start_progress_delay(struct progress *progress, const char *title,
        progress->delayed_percent_treshold = percent_treshold;
        progress->delayed_title = title;
        progress->delay = delay;
+       progress->need_lf = 0;
        set_progress_signal();
 }
 
 void stop_progress(struct progress *progress)
 {
        clear_progress_signal();
-       if (progress->total)
+       if (progress->need_lf)
                fputc('\n', stderr);
 }
index 5ae1a89e5a4669553e44d5bd0cc339c308c60c0f..a7c17ca7c4bdad953508d03c20e73022b03bd25a 100644 (file)
@@ -8,6 +8,7 @@ struct progress {
        unsigned delay;
        unsigned delayed_percent_treshold;
        const char *delayed_title;
+       int need_lf;
 };
 
 int display_progress(struct progress *progress, unsigned n);
diff --git a/quote.c b/quote.c
index fb9e4ca253ea9bcadbcb55dcdd62be614c66758f..d88bf7515932bba96c694478c3b51c85549fa92a 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -20,7 +20,7 @@ static inline int need_bs_quote(char c)
        return (c == '\'' || c == '!');
 }
 
-size_t sq_quote_buf(char *dst, size_t n, const char *src)
+static size_t sq_quote_buf(char *dst, size_t n, const char *src)
 {
        char c;
        char *bp = dst;
@@ -62,18 +62,6 @@ void sq_quote_print(FILE *stream, const char *src)
        fputc('\'', stream);
 }
 
-char *sq_quote(const char *src)
-{
-       char *buf;
-       size_t cnt;
-
-       cnt = sq_quote_buf(NULL, 0, src) + 1;
-       buf = xmalloc(cnt);
-       sq_quote_buf(buf, cnt, src);
-
-       return buf;
-}
-
 char *sq_quote_argv(const char** argv, int count)
 {
        char *buf, *to;
@@ -200,7 +188,8 @@ static int quote_c_style_counted(const char *name, int namelen,
 #define EMITQ() EMIT('\\')
 
        const char *sp;
-       int ch, count = 0, needquote = 0;
+       unsigned char ch;
+       int count = 0, needquote = 0;
 
        if (!no_dq)
                EMIT('"');
@@ -209,7 +198,7 @@ static int quote_c_style_counted(const char *name, int namelen,
                if (!ch)
                        break;
                if ((ch < ' ') || (ch == '"') || (ch == '\\') ||
-                   (ch >= 0177)) {
+                   (quote_path_fully && (ch >= 0177))) {
                        needquote = 1;
                        switch (ch) {
                        case '\a': EMITQ(); ch = 'a'; break;
diff --git a/quote.h b/quote.h
index bdc3610df51000baeda49da1eeaec20ccb2682fb..8a59cc55d1dcfba728614b2d6494272ceafbf3a1 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -28,9 +28,7 @@
  * excluding the final null regardless of the buffer size.
  */
 
-extern char *sq_quote(const char *src);
 extern void sq_quote_print(FILE *stream, const char *src);
-extern size_t sq_quote_buf(char *dst, size_t n, const char *src);
 extern char *sq_quote_argv(const char** argv, int count);
 
 /*
index c1aee603e7df68d6a12ce25b183c478d7da6ab4e..6383401e2dd6bef3251a105ac136312dfdcdeb3c 100644 (file)
@@ -55,7 +55,7 @@ static void process_tree(struct tree *tree,
        while (tree_entry(&desc, &entry)) {
                if (S_ISDIR(entry.mode))
                        process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
-               else if (S_ISDIRLNK(entry.mode))
+               else if (S_ISGITLINK(entry.mode))
                        process_gitlink(entry.sha1, p, &me, entry.path);
                else
                        process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
@@ -176,7 +176,7 @@ static void add_cache_refs(struct rev_info *revs)
                 * lookup_blob() on them, to avoid populating the hash table
                 * with invalid information
                 */
-               if (S_ISDIRLNK(ntohl(active_cache[i]->ce_mode)))
+               if (S_ISGITLINK(ntohl(active_cache[i]->ce_mode)))
                        continue;
 
                lookup_blob(active_cache[i]->sha1);
index d9f46da5cc547d8cfaa34c901be50a13995b120e..8b1c94e0e3f539cd0b507fd130b60aa443b9680b 100644 (file)
@@ -7,6 +7,7 @@
 #include "cache.h"
 #include "cache-tree.h"
 #include "refs.h"
+#include "dir.h"
 
 /* Index extensions.
  *
@@ -92,7 +93,7 @@ static int ce_compare_gitlink(struct cache_entry *ce)
 
        /*
         * We don't actually require that the .git directory
-        * under DIRLNK directory be a valid git directory. It
+        * under GITLINK directory be a valid git directory. It
         * might even be missing (in case nobody populated that
         * sub-project).
         *
@@ -115,7 +116,7 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
                        return DATA_CHANGED;
                break;
        case S_IFDIR:
-               if (S_ISDIRLNK(ntohl(ce->ce_mode)))
+               if (S_ISGITLINK(ntohl(ce->ce_mode)))
                        return 0;
        default:
                return TYPE_CHANGED;
@@ -142,7 +143,7 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
                    (has_symlinks || !S_ISREG(st->st_mode)))
                        changed |= TYPE_CHANGED;
                break;
-       case S_IFDIRLNK:
+       case S_IFGITLINK:
                if (!S_ISDIR(st->st_mode))
                        changed |= TYPE_CHANGED;
                else if (ce_compare_gitlink(ce))
@@ -166,7 +167,7 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
                changed |= MTIME_CHANGED;
        if (ce->ce_ctime.nsec != htonl(st->st_ctim.tv_nsec))
                changed |= CTIME_CHANGED;
-#endif 
+#endif
 
        if (ce->ce_uid != htonl(st->st_uid) ||
            ce->ce_gid != htonl(st->st_gid))
@@ -350,9 +351,37 @@ int remove_file_from_index(struct index_state *istate, const char *path)
        return 0;
 }
 
+static int compare_name(struct cache_entry *ce, const char *path, int namelen)
+{
+       return namelen != ce_namelen(ce) || memcmp(path, ce->name, namelen);
+}
+
+static int index_name_pos_also_unmerged(struct index_state *istate,
+       const char *path, int namelen)
+{
+       int pos = index_name_pos(istate, path, namelen);
+       struct cache_entry *ce;
+
+       if (pos >= 0)
+               return pos;
+
+       /* maybe unmerged? */
+       pos = -1 - pos;
+       if (pos >= istate->cache_nr ||
+                       compare_name((ce = istate->cache[pos]), path, namelen))
+               return -1;
+
+       /* order of preference: stage 2, 1, 3 */
+       if (ce_stage(ce) == 1 && pos + 1 < istate->cache_nr &&
+                       ce_stage((ce = istate->cache[pos + 1])) == 2 &&
+                       !compare_name(ce, path, namelen))
+               pos++;
+       return pos;
+}
+
 int add_file_to_index(struct index_state *istate, const char *path, int verbose)
 {
-       int size, namelen;
+       int size, namelen, pos;
        struct stat st;
        struct cache_entry *ce;
 
@@ -380,12 +409,21 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
                 * from it, otherwise assume unexecutable regular file.
                 */
                struct cache_entry *ent;
-               int pos = index_name_pos(istate, path, namelen);
+               int pos = index_name_pos_also_unmerged(istate, path, namelen);
 
                ent = (0 <= pos) ? istate->cache[pos] : NULL;
                ce->ce_mode = ce_mode_from_stat(ent, st.st_mode);
        }
 
+       pos = index_name_pos(istate, ce->name, namelen);
+       if (0 <= pos &&
+           !ce_stage(istate->cache[pos]) &&
+           !ie_modified(istate, istate->cache[pos], &st, 1)) {
+               /* Nothing changed, really */
+               free(ce);
+               return 0;
+       }
+
        if (index_path(ce->sha1, path, &st, 1))
                die("unable to index file %s", path);
        if (add_index_entry(istate, ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
@@ -597,7 +635,7 @@ static int has_dir_name(struct index_state *istate,
  * is being added, or we already have path and path/file is being
  * added.  Either one would result in a nonsense tree that has path
  * twice when git-write-tree tries to write it out.  Prevent it.
- * 
+ *
  * If ok-to-replace is specified, we remove the conflicting entries
  * from the cache so the caller should recompute the insert position.
  * When this happens, we return non-zero.
@@ -628,7 +666,7 @@ static int check_file_directory_conflict(struct index_state *istate,
        return retval + has_dir_name(istate, ce, pos, ok_to_replace);
 }
 
-int add_index_entry(struct index_state *istate, struct cache_entry *ce, int option)
+static int add_index_entry_with_check(struct index_state *istate, struct cache_entry *ce, int option)
 {
        int pos;
        int ok_to_add = option & ADD_CACHE_OK_TO_ADD;
@@ -670,6 +708,22 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
                pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
                pos = -pos-1;
        }
+       return pos + 1;
+}
+
+int add_index_entry(struct index_state *istate, struct cache_entry *ce, int option)
+{
+       int pos;
+
+       if (option & ADD_CACHE_JUST_APPEND)
+               pos = istate->cache_nr;
+       else {
+               int ret;
+               ret = add_index_entry_with_check(istate, ce, option);
+               if (ret <= 0)
+                       return ret;
+               pos = ret - 1;
+       }
 
        /* Make sure the array is big enough .. */
        if (istate->cache_nr == istate->cache_alloc) {
@@ -680,7 +734,7 @@ int add_index_entry(struct index_state *istate, struct cache_entry *ce, int opti
 
        /* Add it in.. */
        istate->cache_nr++;
-       if (istate->cache_nr > pos)
+       if (istate->cache_nr > pos + 1)
                memmove(istate->cache + pos + 1,
                        istate->cache + pos,
                        (istate->cache_nr - pos - 1) * sizeof(ce));
@@ -745,7 +799,7 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
        return updated;
 }
 
-int refresh_index(struct index_state *istate, unsigned int flags)
+int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec, char *seen)
 {
        int i;
        int has_errors = 0;
@@ -771,6 +825,9 @@ int refresh_index(struct index_state *istate, unsigned int flags)
                        continue;
                }
 
+               if (pathspec && !match_pathspec(pathspec, ce->name, strlen(ce->name), 0, seen))
+                       continue;
+
                new = refresh_cache_ent(istate, ce, really, &cache_errno);
                if (new == ce)
                        continue;
@@ -970,8 +1027,8 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len)
                write_buffer_len = buffered;
                len -= partial;
                data = (char *) data + partial;
-       }
-       return 0;
+       }
+       return 0;
 }
 
 static int write_index_ext_header(SHA_CTX *context, int fd,
@@ -1037,7 +1094,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
                 * size to zero here, then the object name recorded
                 * in index is the 6-byte file but the cached stat information
                 * becomes zero --- which would then match what we would
-                * obtain from the filesystem next time we stat("frotz"). 
+                * obtain from the filesystem next time we stat("frotz").
                 *
                 * However, the second update-index, before calling
                 * this function, notices that the cached size is 6
index 26aa26bcb5089adcf400d12b673519e328b49a23..d3c422be58c5ee1af7e75cf4186bc9bbfb19ec30 100644 (file)
@@ -209,7 +209,7 @@ static const char *update(struct command *cmd)
                return NULL; /* good */
        }
        else {
-               lock = lock_any_ref_for_update(name, old_sha1);
+               lock = lock_any_ref_for_update(name, old_sha1, 0);
                if (!lock) {
                        error("failed to lock %s", name);
                        return "failed to lock";
index c983858259f717b3ed9d0f00921aec92219c1ad3..ee1456b45a2e1bfe08564400c12015889e57cf12 100644 (file)
@@ -136,7 +136,7 @@ void init_reflog_walk(struct reflog_walk_info** info)
        *info = xcalloc(sizeof(struct reflog_walk_info), 1);
 }
 
-void add_reflog_for_walk(struct reflog_walk_info *info,
+int add_reflog_for_walk(struct reflog_walk_info *info,
                struct commit *commit, const char *name)
 {
        unsigned long timestamp = 0;
@@ -188,7 +188,7 @@ void add_reflog_for_walk(struct reflog_walk_info *info,
                        }
                }
                if (!reflogs || reflogs->nr == 0)
-                       die("No reflogs found for '%s'", branch);
+                       return -1;
                path_list_insert(branch, &info->complete_reflogs)->util
                        = reflogs;
        }
@@ -200,13 +200,14 @@ void add_reflog_for_walk(struct reflog_walk_info *info,
                if (commit_reflog->recno < 0) {
                        free(branch);
                        free(commit_reflog);
-                       return;
+                       return -1;
                }
        } else
                commit_reflog->recno = reflogs->nr - recno - 1;
        commit_reflog->reflogs = reflogs;
 
        add_commit_info(commit, commit_reflog, &info->reflogs);
+       return 0;
 }
 
 void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
index a4f7015d3e77b7f59ac5bf1e01713b8d277bc879..7ca1438f4d74b652f962c6bdfddd08fe0d75802d 100644 (file)
@@ -2,7 +2,7 @@
 #define REFLOG_WALK_H
 
 extern void init_reflog_walk(struct reflog_walk_info** info);
-extern void add_reflog_for_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);
diff --git a/refs.c b/refs.c
index 89876bff871d007a6675f5790ce8cb34fe21fb39..09a2c87fc23e4298bb3bcddc5c2cc649c3206a04 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -150,7 +150,7 @@ static struct ref_list *sort_ref_list(struct ref_list *list)
  * Future: need to be in "struct repository"
  * when doing a full libification.
  */
-struct cached_refs {
+static struct cached_refs {
        char did_loose;
        char did_packed;
        struct ref_list *loose;
@@ -603,15 +603,20 @@ int get_ref_sha1(const char *ref, unsigned char *sha1)
 
 static inline int bad_ref_char(int ch)
 {
-       return (((unsigned) ch) <= ' ' ||
-               ch == '~' || ch == '^' || ch == ':' ||
-               /* 2.13 Pattern Matching Notation */
-               ch == '?' || ch == '*' || ch == '[');
+       if (((unsigned) ch) <= ' ' ||
+           ch == '~' || ch == '^' || ch == ':')
+               return 1;
+       /* 2.13 Pattern Matching Notation */
+       if (ch == '?' || ch == '[') /* Unsupported */
+               return 1;
+       if (ch == '*') /* Supported at the end */
+               return 2;
+       return 0;
 }
 
 int check_ref_format(const char *ref)
 {
-       int ch, level;
+       int ch, level, bad_type;
        const char *cp = ref;
 
        level = 0;
@@ -622,13 +627,19 @@ int check_ref_format(const char *ref)
                        return -1; /* should not end with slashes */
 
                /* we are at the beginning of the path component */
-               if (ch == '.' || bad_ref_char(ch))
+               if (ch == '.')
                        return -1;
+               bad_type = bad_ref_char(ch);
+               if (bad_type) {
+                       return (bad_type == 2 && !*cp) ? -3 : -1;
+               }
 
                /* scan the rest of the path component */
                while ((ch = *cp++) != 0) {
-                       if (bad_ref_char(ch))
-                               return -1;
+                       bad_type = bad_ref_char(ch);
+                       if (bad_type) {
+                               return (bad_type == 2 && !*cp) ? -3 : -1;
+                       }
                        if (ch == '/')
                                break;
                        if (ch == '.' && *cp == '.')
@@ -736,19 +747,20 @@ static int is_refname_available(const char *ref, const char *oldref,
        return 1;
 }
 
-static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
+static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int flags, int *type_p)
 {
        char *ref_file;
        const char *orig_ref = ref;
        struct ref_lock *lock;
        struct stat st;
        int last_errno = 0;
+       int type;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
 
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
-       ref = resolve_ref(ref, lock->old_sha1, mustexist, flag);
+       ref = resolve_ref(ref, lock->old_sha1, mustexist, &type);
        if (!ref && errno == EISDIR) {
                /* we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
@@ -761,8 +773,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
                        error("there are still refs under '%s'", orig_ref);
                        goto error_return;
                }
-               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, flag);
+               ref = resolve_ref(orig_ref, lock->old_sha1, mustexist, &type);
        }
+       if (type_p)
+           *type_p = type;
        if (!ref) {
                last_errno = errno;
                error("unable to resolve reference %s: %s",
@@ -780,10 +794,15 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
 
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
+       if (flags & REF_NODEREF)
+               ref = orig_ref;
        lock->ref_name = xstrdup(ref);
        lock->orig_ref_name = xstrdup(orig_ref);
        ref_file = git_path("%s", ref);
-       lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
+       if (lstat(ref_file, &st) && errno == ENOENT)
+               lock->force_write = 1;
+       if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
+               lock->force_write = 1;
 
        if (safe_create_leading_directories(ref_file)) {
                last_errno = errno;
@@ -806,14 +825,14 @@ struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
        if (check_ref_format(ref))
                return NULL;
        strcpy(refpath, mkpath("refs/%s", ref));
-       return lock_ref_sha1_basic(refpath, old_sha1, NULL);
+       return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
 }
 
-struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
+struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags)
 {
        if (check_ref_format(ref) == -1)
                return NULL;
-       return lock_ref_sha1_basic(ref, old_sha1, NULL);
+       return lock_ref_sha1_basic(ref, old_sha1, flags, NULL);
 }
 
 static struct lock_file packlock;
@@ -850,6 +869,7 @@ static int repack_without_ref(const char *refname)
                        die("too long a refname '%s'", list->name);
                write_or_die(fd, line, len);
        }
+       close(fd);
        return commit_lock_file(&packlock);
 }
 
@@ -858,7 +878,7 @@ int delete_ref(const char *refname, const unsigned char *sha1)
        struct ref_lock *lock;
        int err, i, ret = 0, flag = 0;
 
-       lock = lock_ref_sha1_basic(refname, sha1, &flag);
+       lock = lock_ref_sha1_basic(refname, sha1, 0, &flag);
        if (!lock)
                return 1;
        if (!(flag & REF_ISPACKED)) {
@@ -909,7 +929,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
                return 1;
 
-       lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL);
+       lock = lock_ref_sha1_basic(renamed_ref, NULL, 0, NULL);
        if (!lock)
                return error("unable to lock %s", renamed_ref);
        lock->force_write = 1;
@@ -963,7 +983,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        }
        logmoved = log;
 
-       lock = lock_ref_sha1_basic(newref, NULL, NULL);
+       lock = lock_ref_sha1_basic(newref, NULL, 0, NULL);
        if (!lock) {
                error("unable to lock %s for update", newref);
                goto rollback;
@@ -979,7 +999,7 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
        return 0;
 
  rollback:
-       lock = lock_ref_sha1_basic(oldref, NULL, NULL);
+       lock = lock_ref_sha1_basic(oldref, NULL, 0, NULL);
        if (!lock) {
                error("unable to lock %s for rollback", oldref);
                goto rollbacklog;
@@ -1017,6 +1037,32 @@ void unlock_ref(struct ref_lock *lock)
        free(lock);
 }
 
+/*
+ * copy the reflog message msg to buf, which has been allocated sufficiently
+ * large, while cleaning up the whitespaces.  Especially, convert LF to space,
+ * because reflog file is one line per entry.
+ */
+static int copy_msg(char *buf, const char *msg)
+{
+       char *cp = buf;
+       char c;
+       int wasspace = 1;
+
+       *cp++ = '\t';
+       while ((c = *msg++)) {
+               if (wasspace && isspace(c))
+                       continue;
+               wasspace = isspace(c);
+               if (wasspace)
+                       c = ' ';
+               *cp++ = c;
+       }
+       while (buf < cp && isspace(cp[-1]))
+               cp--;
+       *cp++ = '\n';
+       return cp - buf;
+}
+
 static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
                         const unsigned char *new_sha1, const char *msg)
 {
@@ -1061,21 +1107,7 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
 
        adjust_shared_perm(log_file);
 
-       msglen = 0;
-       if (msg) {
-               /* clean up the message and make sure it is a single line */
-               for ( ; *msg; msg++)
-                       if (!isspace(*msg))
-                               break;
-               if (*msg) {
-                       const char *ep = strchr(msg, '\n');
-                       if (ep)
-                               msglen = ep - msg;
-                       else
-                               msglen = strlen(msg);
-               }
-       }
-
+       msglen = msg ? strlen(msg) : 0;
        committer = git_committer_info(-1);
        maxlen = strlen(committer) + msglen + 100;
        logrec = xmalloc(maxlen);
@@ -1084,11 +1116,10 @@ static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
                      sha1_to_hex(new_sha1),
                      committer);
        if (msglen)
-               len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
+               len += copy_msg(logrec + len - 1, msg) - 1;
        written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
        free(logrec);
-       close(logfd);
-       if (written != len)
+       if (close(logfd) != 0 || written != len)
                return error("Unable to append to %s", log_file);
        return 0;
 }
@@ -1185,8 +1216,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
                goto error_free_return;
        }
        written = write_in_full(fd, ref, len);
-       close(fd);
-       if (written != len) {
+       if (close(fd) != 0 || written != len) {
                error("Unable to write to %s", lockpath);
                goto error_unlink_return;
        }
@@ -1283,7 +1313,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (hashcmp(logged_sha1, sha1)) {
                                        fprintf(stderr,
                                                "warning: Log %s has gap after %s.\n",
-                                               logfile, show_rfc2822_date(date, tz));
+                                               logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }
                        else if (date == at_time) {
@@ -1296,7 +1326,7 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (hashcmp(logged_sha1, sha1)) {
                                        fprintf(stderr,
                                                "warning: Log %s unexpectedly ended on %s.\n",
-                                               logfile, show_rfc2822_date(date, tz));
+                                               logfile, show_date(date, tz, DATE_RFC2822));
                                }
                        }
                        munmap(log_mapped, mapsz);
diff --git a/refs.h b/refs.h
index f61f6d934e80b21432f93cd3b9f138770a9d2b86..f234eb76ba5d6aba03f484fe58d11ba81a90ff5a 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -33,7 +33,8 @@ extern int get_ref_sha1(const char *ref, unsigned char *sha1);
 extern struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1);
 
 /** Locks any ref (for 'HEAD' type refs). */
-extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1);
+#define REF_NODEREF    0x01
+extern struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1, int flags);
 
 /** Release any lock taken but not written. **/
 extern void unlock_ref(struct ref_lock *lock);
diff --git a/remote.c b/remote.c
new file mode 100644 (file)
index 0000000..bb774d0
--- /dev/null
+++ b/remote.c
@@ -0,0 +1,633 @@
+#include "cache.h"
+#include "remote.h"
+#include "refs.h"
+
+static struct remote **remotes;
+static int allocated_remotes;
+
+#define BUF_SIZE (2048)
+static char buffer[BUF_SIZE];
+
+static void add_push_refspec(struct remote *remote, const char *ref)
+{
+       int nr = remote->push_refspec_nr + 1;
+       remote->push_refspec =
+               xrealloc(remote->push_refspec, nr * sizeof(char *));
+       remote->push_refspec[nr-1] = ref;
+       remote->push_refspec_nr = nr;
+}
+
+static void add_fetch_refspec(struct remote *remote, const char *ref)
+{
+       int nr = remote->fetch_refspec_nr + 1;
+       remote->fetch_refspec =
+               xrealloc(remote->fetch_refspec, nr * sizeof(char *));
+       remote->fetch_refspec[nr-1] = ref;
+       remote->fetch_refspec_nr = nr;
+}
+
+static void add_uri(struct remote *remote, const char *uri)
+{
+       int nr = remote->uri_nr + 1;
+       remote->uri =
+               xrealloc(remote->uri, nr * sizeof(char *));
+       remote->uri[nr-1] = uri;
+       remote->uri_nr = nr;
+}
+
+static struct remote *make_remote(const char *name, int len)
+{
+       int i, empty = -1;
+
+       for (i = 0; i < allocated_remotes; i++) {
+               if (!remotes[i]) {
+                       if (empty < 0)
+                               empty = i;
+               } else {
+                       if (len ? (!strncmp(name, remotes[i]->name, len) &&
+                                  !remotes[i]->name[len]) :
+                           !strcmp(name, remotes[i]->name))
+                               return remotes[i];
+               }
+       }
+
+       if (empty < 0) {
+               empty = allocated_remotes;
+               allocated_remotes += allocated_remotes ? allocated_remotes : 1;
+               remotes = xrealloc(remotes,
+                                  sizeof(*remotes) * allocated_remotes);
+               memset(remotes + empty, 0,
+                      (allocated_remotes - empty) * sizeof(*remotes));
+       }
+       remotes[empty] = xcalloc(1, sizeof(struct remote));
+       if (len)
+               remotes[empty]->name = xstrndup(name, len);
+       else
+               remotes[empty]->name = xstrdup(name);
+       return remotes[empty];
+}
+
+static void read_remotes_file(struct remote *remote)
+{
+       FILE *f = fopen(git_path("remotes/%s", remote->name), "r");
+
+       if (!f)
+               return;
+       while (fgets(buffer, BUF_SIZE, f)) {
+               int value_list;
+               char *s, *p;
+
+               if (!prefixcmp(buffer, "URL:")) {
+                       value_list = 0;
+                       s = buffer + 4;
+               } else if (!prefixcmp(buffer, "Push:")) {
+                       value_list = 1;
+                       s = buffer + 5;
+               } else if (!prefixcmp(buffer, "Pull:")) {
+                       value_list = 2;
+                       s = buffer + 5;
+               } else
+                       continue;
+
+               while (isspace(*s))
+                       s++;
+               if (!*s)
+                       continue;
+
+               p = s + strlen(s);
+               while (isspace(p[-1]))
+                       *--p = 0;
+
+               switch (value_list) {
+               case 0:
+                       add_uri(remote, xstrdup(s));
+                       break;
+               case 1:
+                       add_push_refspec(remote, xstrdup(s));
+                       break;
+               case 2:
+                       add_fetch_refspec(remote, xstrdup(s));
+                       break;
+               }
+       }
+       fclose(f);
+}
+
+static void read_branches_file(struct remote *remote)
+{
+       const char *slash = strchr(remote->name, '/');
+       int n = slash ? slash - remote->name : 1000;
+       FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r");
+       char *s, *p;
+       int len;
+
+       if (!f)
+               return;
+       s = fgets(buffer, BUF_SIZE, f);
+       fclose(f);
+       if (!s)
+               return;
+       while (isspace(*s))
+               s++;
+       if (!*s)
+               return;
+       p = s + strlen(s);
+       while (isspace(p[-1]))
+               *--p = 0;
+       len = p - s;
+       if (slash)
+               len += strlen(slash);
+       p = xmalloc(len + 1);
+       strcpy(p, s);
+       if (slash)
+               strcat(p, slash);
+       add_uri(remote, p);
+}
+
+static char *default_remote_name = NULL;
+static const char *current_branch = NULL;
+static int current_branch_len = 0;
+
+static int handle_config(const char *key, const char *value)
+{
+       const char *name;
+       const char *subkey;
+       struct remote *remote;
+       if (!prefixcmp(key, "branch.") && current_branch &&
+           !strncmp(key + 7, current_branch, current_branch_len) &&
+           !strcmp(key + 7 + current_branch_len, ".remote")) {
+               free(default_remote_name);
+               default_remote_name = xstrdup(value);
+       }
+       if (prefixcmp(key,  "remote."))
+               return 0;
+       name = key + 7;
+       subkey = strrchr(name, '.');
+       if (!subkey)
+               return error("Config with no key for remote %s", name);
+       if (*subkey == '/') {
+               warning("Config remote shorthand cannot begin with '/': %s", name);
+               return 0;
+       }
+       remote = make_remote(name, subkey - name);
+       if (!value) {
+               /* if we ever have a boolean variable, e.g. "remote.*.disabled"
+                * [remote "frotz"]
+                *      disabled
+                * is a valid way to set it to true; we get NULL in value so
+                * we need to handle it here.
+                *
+                * if (!strcmp(subkey, ".disabled")) {
+                *      val = git_config_bool(key, value);
+                *      return 0;
+                * } else
+                *
+                */
+               return 0; /* ignore unknown booleans */
+       }
+       if (!strcmp(subkey, ".url")) {
+               add_uri(remote, xstrdup(value));
+       } else if (!strcmp(subkey, ".push")) {
+               add_push_refspec(remote, xstrdup(value));
+       } else if (!strcmp(subkey, ".fetch")) {
+               add_fetch_refspec(remote, xstrdup(value));
+       } else if (!strcmp(subkey, ".receivepack")) {
+               if (!remote->receivepack)
+                       remote->receivepack = xstrdup(value);
+               else
+                       error("more than one receivepack given, using the first");
+       }
+       return 0;
+}
+
+static void read_config(void)
+{
+       unsigned char sha1[20];
+       const char *head_ref;
+       int flag;
+       if (default_remote_name) // did this already
+               return;
+       default_remote_name = xstrdup("origin");
+       current_branch = NULL;
+       head_ref = resolve_ref("HEAD", sha1, 0, &flag);
+       if (head_ref && (flag & REF_ISSYMREF) &&
+           !prefixcmp(head_ref, "refs/heads/")) {
+               current_branch = head_ref + strlen("refs/heads/");
+               current_branch_len = strlen(current_branch);
+       }
+       git_config(handle_config);
+}
+
+static struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
+{
+       int i;
+       struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec);
+       for (i = 0; i < nr_refspec; i++) {
+               const char *sp, *ep, *gp;
+               sp = refspec[i];
+               if (*sp == '+') {
+                       rs[i].force = 1;
+                       sp++;
+               }
+               gp = strchr(sp, '*');
+               ep = strchr(sp, ':');
+               if (gp && ep && gp > ep)
+                       gp = NULL;
+               if (ep) {
+                       if (ep[1]) {
+                               const char *glob = strchr(ep + 1, '*');
+                               if (!glob)
+                                       gp = NULL;
+                               if (gp)
+                                       rs[i].dst = xstrndup(ep + 1,
+                                                            glob - ep - 1);
+                               else
+                                       rs[i].dst = xstrdup(ep + 1);
+                       }
+               } else {
+                       ep = sp + strlen(sp);
+               }
+               if (gp) {
+                       rs[i].pattern = 1;
+                       ep = gp;
+               }
+               rs[i].src = xstrndup(sp, ep - sp);
+       }
+       return rs;
+}
+
+struct remote *remote_get(const char *name)
+{
+       struct remote *ret;
+
+       read_config();
+       if (!name)
+               name = default_remote_name;
+       ret = make_remote(name, 0);
+       if (name[0] != '/') {
+               if (!ret->uri)
+                       read_remotes_file(ret);
+               if (!ret->uri)
+                       read_branches_file(ret);
+       }
+       if (!ret->uri)
+               add_uri(ret, name);
+       if (!ret->uri)
+               return NULL;
+       ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
+       ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec);
+       return ret;
+}
+
+int for_each_remote(each_remote_fn fn, void *priv)
+{
+       int i, result = 0;
+       read_config();
+       for (i = 0; i < allocated_remotes && !result; i++) {
+               struct remote *r = remotes[i];
+               if (!r)
+                       continue;
+               if (!r->fetch)
+                       r->fetch = parse_ref_spec(r->fetch_refspec_nr,
+                                       r->fetch_refspec);
+               if (!r->push)
+                       r->push = parse_ref_spec(r->push_refspec_nr,
+                                       r->push_refspec);
+               result = fn(r, priv);
+       }
+       return result;
+}
+
+int remote_has_uri(struct remote *remote, const char *uri)
+{
+       int i;
+       for (i = 0; i < remote->uri_nr; i++) {
+               if (!strcmp(remote->uri[i], uri))
+                       return 1;
+       }
+       return 0;
+}
+
+int remote_find_tracking(struct remote *remote, struct refspec *refspec)
+{
+       int find_src = refspec->src == NULL;
+       char *needle, **result;
+       int i;
+
+       if (find_src) {
+               if (refspec->dst == NULL)
+                       return error("find_tracking: need either src or dst");
+               needle = refspec->dst;
+               result = &refspec->src;
+       } else {
+               needle = refspec->src;
+               result = &refspec->dst;
+       }
+
+       for (i = 0; i < remote->fetch_refspec_nr; i++) {
+               struct refspec *fetch = &remote->fetch[i];
+               const char *key = find_src ? fetch->dst : fetch->src;
+               const char *value = find_src ? fetch->src : fetch->dst;
+               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));
+                               refspec->force = fetch->force;
+                               return 0;
+                       }
+               } else if (!strcmp(needle, key)) {
+                       *result = xstrdup(value);
+                       refspec->force = fetch->force;
+                       return 0;
+               }
+       }
+       return -1;
+}
+
+struct ref *alloc_ref(unsigned namelen)
+{
+       struct ref *ret = xmalloc(sizeof(struct ref) + namelen);
+       memset(ret, 0, sizeof(struct ref) + namelen);
+       return ret;
+}
+
+void free_refs(struct ref *ref)
+{
+       struct ref *next;
+       while (ref) {
+               next = ref->next;
+               if (ref->peer_ref)
+                       free(ref->peer_ref);
+               free(ref);
+               ref = next;
+       }
+}
+
+static int count_refspec_match(const char *pattern,
+                              struct ref *refs,
+                              struct ref **matched_ref)
+{
+       int patlen = strlen(pattern);
+       struct ref *matched_weak = NULL;
+       struct ref *matched = NULL;
+       int weak_match = 0;
+       int match = 0;
+
+       for (weak_match = match = 0; refs; refs = refs->next) {
+               char *name = refs->name;
+               int namelen = strlen(name);
+
+               if (namelen < patlen ||
+                   memcmp(name + namelen - patlen, pattern, patlen))
+                       continue;
+               if (namelen != patlen && name[namelen - patlen - 1] != '/')
+                       continue;
+
+               /* A match is "weak" if it is with refs outside
+                * heads or tags, and did not specify the pattern
+                * in full (e.g. "refs/remotes/origin/master") or at
+                * least from the toplevel (e.g. "remotes/origin/master");
+                * otherwise "git push $URL master" would result in
+                * ambiguity between remotes/origin/master and heads/master
+                * at the remote site.
+                */
+               if (namelen != patlen &&
+                   patlen != namelen - 5 &&
+                   prefixcmp(name, "refs/heads/") &&
+                   prefixcmp(name, "refs/tags/")) {
+                       /* We want to catch the case where only weak
+                        * matches are found and there are multiple
+                        * matches, and where more than one strong
+                        * matches are found, as ambiguous.  One
+                        * strong match with zero or more weak matches
+                        * are acceptable as a unique match.
+                        */
+                       matched_weak = refs;
+                       weak_match++;
+               }
+               else {
+                       matched = refs;
+                       match++;
+               }
+       }
+       if (!matched) {
+               *matched_ref = matched_weak;
+               return weak_match;
+       }
+       else {
+               *matched_ref = matched;
+               return match;
+       }
+}
+
+static void tail_link_ref(struct ref *ref, struct ref ***tail)
+{
+       **tail = ref;
+       while (ref->next)
+               ref = ref->next;
+       *tail = &ref->next;
+}
+
+static struct ref *try_explicit_object_name(const char *name)
+{
+       unsigned char sha1[20];
+       struct ref *ref;
+       int len;
+
+       if (!*name) {
+               ref = alloc_ref(20);
+               strcpy(ref->name, "(delete)");
+               hashclr(ref->new_sha1);
+               return ref;
+       }
+       if (get_sha1(name, sha1))
+               return NULL;
+       len = strlen(name) + 1;
+       ref = alloc_ref(len);
+       memcpy(ref->name, name, len);
+       hashcpy(ref->new_sha1, sha1);
+       return ref;
+}
+
+static struct ref *make_linked_ref(const char *name, struct ref ***tail)
+{
+       struct ref *ret;
+       size_t len;
+
+       len = strlen(name) + 1;
+       ret = alloc_ref(len);
+       memcpy(ret->name, name, len);
+       tail_link_ref(ret, tail);
+       return ret;
+}
+
+static int match_explicit(struct ref *src, struct ref *dst,
+                         struct ref ***dst_tail,
+                         struct refspec *rs,
+                         int errs)
+{
+       struct ref *matched_src, *matched_dst;
+
+       const char *dst_value = rs->dst;
+
+       if (rs->pattern)
+               return errs;
+
+       matched_src = matched_dst = NULL;
+       switch (count_refspec_match(rs->src, src, &matched_src)) {
+       case 1:
+               break;
+       case 0:
+               /* The source could be in the get_sha1() format
+                * not a reference name.  :refs/other is a
+                * way to delete 'other' ref at the remote end.
+                */
+               matched_src = try_explicit_object_name(rs->src);
+               if (matched_src)
+                       break;
+               error("src refspec %s does not match any.",
+                     rs->src);
+               break;
+       default:
+               matched_src = NULL;
+               error("src refspec %s matches more than one.",
+                     rs->src);
+               break;
+       }
+
+       if (!matched_src)
+               errs = 1;
+
+       if (dst_value == NULL)
+               dst_value = matched_src->name;
+
+       switch (count_refspec_match(dst_value, dst, &matched_dst)) {
+       case 1:
+               break;
+       case 0:
+               if (!memcmp(dst_value, "refs/", 5))
+                       matched_dst = make_linked_ref(dst_value, dst_tail);
+               else
+                       error("dst refspec %s does not match any "
+                             "existing ref on the remote and does "
+                             "not start with refs/.", dst_value);
+               break;
+       default:
+               matched_dst = NULL;
+               error("dst refspec %s matches more than one.",
+                     dst_value);
+               break;
+       }
+       if (errs || matched_dst == NULL)
+               return 1;
+       if (matched_dst->peer_ref) {
+               errs = 1;
+               error("dst ref %s receives from more than one src.",
+                     matched_dst->name);
+       }
+       else {
+               matched_dst->peer_ref = matched_src;
+               matched_dst->force = rs->force;
+       }
+       return errs;
+}
+
+static int match_explicit_refs(struct ref *src, struct ref *dst,
+                              struct ref ***dst_tail, struct refspec *rs,
+                              int rs_nr)
+{
+       int i, errs;
+       for (i = errs = 0; i < rs_nr; i++)
+               errs |= match_explicit(src, dst, dst_tail, &rs[i], errs);
+       return -errs;
+}
+
+static struct ref *find_ref_by_name(struct ref *list, const char *name)
+{
+       for ( ; list; list = list->next)
+               if (!strcmp(list->name, name))
+                       return list;
+       return NULL;
+}
+
+static const struct refspec *check_pattern_match(const struct refspec *rs,
+                                                int rs_nr,
+                                                const struct ref *src)
+{
+       int i;
+       for (i = 0; i < rs_nr; i++) {
+               if (rs[i].pattern && !prefixcmp(src->name, rs[i].src))
+                       return rs + i;
+       }
+       return NULL;
+}
+
+/*
+ * Note. This is used only by "push"; refspec matching rules for
+ * push and fetch are subtly different, so do not try to reuse it
+ * without thinking.
+ */
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+              int nr_refspec, char **refspec, int all)
+{
+       struct refspec *rs =
+               parse_ref_spec(nr_refspec, (const char **) refspec);
+
+       if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec))
+               return -1;
+
+       /* pick the remainder */
+       for ( ; src; src = src->next) {
+               struct ref *dst_peer;
+               const struct refspec *pat = NULL;
+               char *dst_name;
+               if (src->peer_ref)
+                       continue;
+               if (nr_refspec) {
+                       pat = check_pattern_match(rs, nr_refspec, src);
+                       if (!pat)
+                               continue;
+               }
+               else if (prefixcmp(src->name, "refs/heads/"))
+                       /*
+                        * "matching refs"; traditionally we pushed everything
+                        * including refs outside refs/heads/ hierarchy, but
+                        * that does not make much sense these days.
+                        */
+                       continue;
+
+               if (pat) {
+                       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));
+               } else
+                       dst_name = xstrdup(src->name);
+               dst_peer = find_ref_by_name(dst, dst_name);
+               if (dst_peer && dst_peer->peer_ref)
+                       /* We're already sending something to this ref. */
+                       goto free_name;
+               if (!dst_peer && !nr_refspec && !all)
+                       /* Remote doesn't have it, and we have no
+                        * explicit pattern, and we don't have
+                        * --all. */
+                       goto free_name;
+               if (!dst_peer) {
+                       /* Create a new one and link it */
+                       dst_peer = make_linked_ref(dst_name, dst_tail);
+                       hashcpy(dst_peer->new_sha1, src->new_sha1);
+               }
+               dst_peer->peer_ref = src;
+       free_name:
+               free(dst_name);
+       }
+       return 0;
+}
diff --git a/remote.h b/remote.h
new file mode 100644 (file)
index 0000000..17b8b5b
--- /dev/null
+++ b/remote.h
@@ -0,0 +1,51 @@
+#ifndef REMOTE_H
+#define REMOTE_H
+
+struct remote {
+       const char *name;
+
+       const char **uri;
+       int uri_nr;
+
+       const char **push_refspec;
+       struct refspec *push;
+       int push_refspec_nr;
+
+       const char **fetch_refspec;
+       struct refspec *fetch;
+       int fetch_refspec_nr;
+
+       const char *receivepack;
+};
+
+struct remote *remote_get(const char *name);
+
+typedef int each_remote_fn(struct remote *remote, void *priv);
+int for_each_remote(each_remote_fn fn, void *priv);
+
+int remote_has_uri(struct remote *remote, const char *uri);
+
+struct refspec {
+       unsigned force : 1;
+       unsigned pattern : 1;
+
+       char *src;
+       char *dst;
+};
+
+struct ref *alloc_ref(unsigned namelen);
+
+/*
+ * Frees the entire list and peers of elements.
+ */
+void free_refs(struct ref *ref);
+
+int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail,
+              int nr_refspec, char **refspec, int all);
+
+/*
+ * For the given remote, reads the refspec's src and sets the other fields.
+ */
+int remote_find_tracking(struct remote *remote, struct refspec *refspec);
+
+#endif
index e43c6489785a61707b838df782d2859ec6654651..7d32a89b0eca858113829e9219e80ff15e66e953 100644 (file)
@@ -114,19 +114,20 @@ void mark_parents_uninteresting(struct commit *commit)
        }
 }
 
-void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
-{
-       add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
-}
-
-void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
+static void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode)
 {
        if (revs->no_walk && (obj->flags & UNINTERESTING))
                die("object ranges do not make sense when not walking revisions");
+       if (revs->reflog_info && obj->type == OBJ_COMMIT &&
+                       add_reflog_for_walk(revs->reflog_info,
+                               (struct commit *)obj, name))
+               return;
        add_object_array_with_mode(obj, name, &revs->pending, mode);
-       if (revs->reflog_info && obj->type == OBJ_COMMIT)
-               add_reflog_for_walk(revs->reflog_info,
-                               (struct commit *)obj, name);
+}
+
+void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+{
+       add_pending_object_with_mode(revs, obj, name, S_IFINVALID);
 }
 
 static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
@@ -262,7 +263,7 @@ static void file_change(struct diff_options *options,
        options->has_changes = 1;
 }
 
-int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
+static int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
 {
        if (!t1)
                return REV_TREE_NEW;
@@ -276,7 +277,7 @@ int rev_compare_tree(struct rev_info *revs, struct tree *t1, struct tree *t2)
        return tree_difference;
 }
 
-int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
+static int rev_same_tree_as_empty(struct rev_info *revs, struct tree *t1)
 {
        int retval;
        void *tree;
@@ -437,7 +438,7 @@ static int add_parents_to_list(struct rev_info *revs, struct commit *commit, str
        return 0;
 }
 
-static void cherry_pick_list(struct commit_list *list)
+static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
 {
        struct commit_list *p;
        int left_count = 0, right_count = 0;
@@ -458,6 +459,11 @@ static void cherry_pick_list(struct commit_list *list)
 
        left_first = left_count < right_count;
        init_patch_ids(&ids);
+       if (revs->diffopt.nr_paths) {
+               ids.diffopts.nr_paths = revs->diffopt.nr_paths;
+               ids.diffopts.paths = revs->diffopt.paths;
+               ids.diffopts.pathlens = revs->diffopt.pathlens;
+       }
 
        /* Compute patch-ids for one side */
        for (p = list; p; p = p->next) {
@@ -546,7 +552,7 @@ static int limit_list(struct rev_info *revs)
                p = &commit_list_insert(commit, p)->next;
        }
        if (revs->cherry_pick)
-               cherry_pick_list(newlist);
+               cherry_pick_list(newlist, revs);
 
        revs->commits = newlist;
        return 0;
@@ -667,7 +673,6 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->min_age = -1;
        revs->skip_count = -1;
        revs->max_count = -1;
-       revs->subject_prefix = "PATCH";
 
        revs->prune_fn = NULL;
        revs->prune_data = NULL;
@@ -881,6 +886,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
        const char **unrecognized = argv + 1;
        int left = 1;
        int all_match = 0;
+       int regflags = 0;
 
        /* First, search for "--" */
        seen_dashdash = 0;
@@ -1128,6 +1134,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                        if (!strncmp(arg, "--date=", 7)) {
                                if (!strcmp(arg + 7, "relative"))
                                        revs->date_mode = DATE_RELATIVE;
+                               else if (!strcmp(arg + 7, "iso8601") ||
+                                        !strcmp(arg + 7, "iso"))
+                                       revs->date_mode = DATE_ISO8601;
+                               else if (!strcmp(arg + 7, "rfc2822") ||
+                                        !strcmp(arg + 7, "rfc"))
+                                       revs->date_mode = DATE_RFC2822;
+                               else if (!strcmp(arg + 7, "short"))
+                                       revs->date_mode = DATE_SHORT;
                                else if (!strcmp(arg + 7, "local"))
                                        revs->date_mode = DATE_LOCAL;
                                else if (!strcmp(arg + 7, "default"))
@@ -1136,6 +1150,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                        die("unknown date format %s", arg);
                                continue;
                        }
+                       if (!strcmp(arg, "--log-size")) {
+                               revs->show_log_size = 1;
+                               continue;
+                       }
 
                        /*
                         * Grepping the commit log
@@ -1152,6 +1170,16 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                add_message_grep(revs, arg+7);
                                continue;
                        }
+                       if (!strcmp(arg, "--extended-regexp") ||
+                           !strcmp(arg, "-E")) {
+                               regflags |= REG_EXTENDED;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--regexp-ignore-case") ||
+                           !strcmp(arg, "-i")) {
+                               regflags |= REG_ICASE;
+                               continue;
+                       }
                        if (!strcmp(arg, "--all-match")) {
                                all_match = 1;
                                continue;
@@ -1168,6 +1196,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->reverse ^= 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--no-walk")) {
+                               revs->no_walk = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--do-walk")) {
+                               revs->no_walk = 0;
+                               continue;
+                       }
 
                        opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
                        if (opts > 0) {
@@ -1201,6 +1237,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                }
        }
 
+       if (revs->grep_filter)
+               revs->grep_filter->regflags |= regflags;
+
        if (show_merge)
                prepare_show_merge(revs);
        if (def && !revs->pending.nr) {
@@ -1218,7 +1257,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
        if (revs->prune_data) {
                diff_tree_setup_paths(revs->prune_data, &revs->pruning);
-               revs->prune_fn = try_to_simplify_commit;
+               /* Can't prune commits with rename following: the paths change.. */
+               if (!revs->diffopt.follow_renames)
+                       revs->prune_fn = try_to_simplify_commit;
                if (!revs->full_diff)
                        diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
        }
@@ -1295,6 +1336,26 @@ static enum rewrite_result rewrite_one(struct rev_info *revs, struct commit **pp
        }
 }
 
+static void remove_duplicate_parents(struct commit *commit)
+{
+       struct commit_list **pp, *p;
+
+       /* Examine existing parents while marking ones we have seen... */
+       pp = &commit->parents;
+       while ((p = *pp) != NULL) {
+               struct commit *parent = p->item;
+               if (parent->object.flags & TMP_MARK) {
+                       *pp = p->next;
+                       continue;
+               }
+               parent->object.flags |= TMP_MARK;
+               pp = &p->next;
+       }
+       /* ... and clear the temporary mark */
+       for (p = commit->parents; p; p = p->next)
+               p->item->object.flags &= ~TMP_MARK;
+}
+
 static int rewrite_parents(struct rev_info *revs, struct commit *commit)
 {
        struct commit_list **pp = &commit->parents;
@@ -1311,6 +1372,7 @@ static int rewrite_parents(struct rev_info *revs, struct commit *commit)
                }
                pp = &parent->next;
        }
+       remove_duplicate_parents(commit);
        return 0;
 }
 
index 2845167746ce4632894fce88e840df60513933aa..98a0a8f3fa9db4b2302dd31a4867ae824831b25c 100644 (file)
@@ -81,6 +81,7 @@ struct rev_info {
        const char      *log_reencode;
        const char      *subject_prefix;
        int             no_inline;
+       int             show_log_size;
 
        /* Filter by commit log message */
        struct grep_opt *grep_filter;
@@ -106,8 +107,6 @@ struct rev_info {
 #define REV_TREE_DIFFERENT     2
 
 /* revision.c */
-extern int rev_same_tree_as_empty(struct rev_info *, struct tree *t1);
-extern int rev_compare_tree(struct rev_info *, struct tree *t1, struct tree *t2);
 
 extern void init_revisions(struct rev_info *revs, const char *prefix);
 extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
@@ -131,6 +130,5 @@ extern void add_object(struct object *obj,
                       const char *name);
 
 extern void add_pending_object(struct rev_info *revs, struct object *obj, const char *name);
-extern void add_pending_object_with_mode(struct rev_info *revs, struct object *obj, const char *name, unsigned mode);
 
 #endif
diff --git a/rsh.h b/rsh.h
index 3b4194239dc419c38910012b129c4c92b72ef8ad..ee2f49929131c684bcffa3e95a1ee29521e8818b 100644 (file)
--- a/rsh.h
+++ b/rsh.h
@@ -1,7 +1,7 @@
 #ifndef RSH_H
 #define RSH_H
 
-int setup_connection(int *fd_in, int *fd_out, const char *remote_prog, 
+int setup_connection(int *fd_in, int *fd_out, const char *remote_prog,
                     char *url, int rmt_argc, char **rmt_argv);
 
 #endif
index eff523e191b35385895f6b077fc76c7c21819012..7e779d33ee9ea5f7d2e6aedc8c3a0a0476e87135 100644 (file)
@@ -73,6 +73,17 @@ int start_command(struct child_process *cmd)
                        close(cmd->out);
                }
 
+               if (cmd->dir && chdir(cmd->dir))
+                       die("exec %s: cd to %s failed (%s)", cmd->argv[0],
+                           cmd->dir, strerror(errno));
+               if (cmd->env) {
+                       for (; *cmd->env; cmd->env++) {
+                               if (strchr(*cmd->env, '='))
+                                       putenv((char*)*cmd->env);
+                               else
+                                       unsetenv(*cmd->env);
+                       }
+               }
                if (cmd->git_cmd) {
                        execv_git_cmd(cmd->argv);
                } else {
@@ -133,13 +144,37 @@ int run_command(struct child_process *cmd)
        return finish_command(cmd);
 }
 
+static void prepare_run_command_v_opt(struct child_process *cmd,
+                                     const char **argv,
+                                     int opt)
+{
+       memset(cmd, 0, sizeof(*cmd));
+       cmd->argv = argv;
+       cmd->no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
+       cmd->git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
+       cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
+}
+
 int run_command_v_opt(const char **argv, int opt)
 {
        struct child_process cmd;
-       memset(&cmd, 0, sizeof(cmd));
-       cmd.argv = argv;
-       cmd.no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
-       cmd.git_cmd = opt & RUN_GIT_CMD ? 1 : 0;
-       cmd.stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
+       prepare_run_command_v_opt(&cmd, argv, opt);
+       return run_command(&cmd);
+}
+
+int run_command_v_opt_cd(const char **argv, int opt, const char *dir)
+{
+       struct child_process cmd;
+       prepare_run_command_v_opt(&cmd, argv, opt);
+       cmd.dir = dir;
+       return run_command(&cmd);
+}
+
+int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env)
+{
+       struct child_process cmd;
+       prepare_run_command_v_opt(&cmd, argv, opt);
+       cmd.dir = dir;
+       cmd.env = env;
        return run_command(&cmd);
 }
index 3680ef9d452490c67788b0ab027839a8383ed855..7958eb1e0b7a927019460e06d7a01622eddf81df 100644 (file)
@@ -16,6 +16,8 @@ struct child_process {
        pid_t pid;
        int in;
        int out;
+       const char *dir;
+       const char *const *env;
        unsigned close_in:1;
        unsigned close_out:1;
        unsigned no_stdin:1;
@@ -32,5 +34,12 @@ int run_command(struct child_process *);
 #define RUN_GIT_CMD         2  /*If this is to be git sub-command */
 #define RUN_COMMAND_STDOUT_TO_STDERR 4
 int run_command_v_opt(const char **argv, int opt);
+int run_command_v_opt_cd(const char **argv, int opt, const char *dir);
+
+/*
+ * env (the environment) is to be formatted like environ: "VAR=VALUE".
+ * To unset an environment variable use just "VAR".
+ */
+int run_command_v_opt_cd_env(const char **argv, int opt, const char *dir, const char *const *env);
 
 #endif
index d5b51628dfbcc44b22b5069df19eeac67dda1e57..9fc8a812f4c526d61e3acb8183b8b8da0e36895c 100644 (file)
@@ -4,6 +4,7 @@
 #include "refs.h"
 #include "pkt-line.h"
 #include "run-command.h"
+#include "remote.h"
 
 static const char send_pack_usage[] =
 "git-send-pack [--all] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
@@ -176,7 +177,7 @@ static int receive_status(int in)
        return ret;
 }
 
-static int send_pack(int in, int out, int nr_refspec, char **refspec)
+static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec)
 {
        struct ref *ref;
        int new_refs;
@@ -213,18 +214,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        new_refs = 0;
        for (ref = remote_refs; ref; ref = ref->next) {
                char old_hex[60], *new_hex;
-               int delete_ref;
+               int will_delete_ref;
 
                if (!ref->peer_ref)
                        continue;
 
-               delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
-               if (delete_ref && !allow_deleting_refs) {
+
+               will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
+               if (will_delete_ref && !allow_deleting_refs) {
                        error("remote does not support deleting refs");
                        ret = -2;
                        continue;
                }
-               if (!delete_ref &&
+               if (!will_delete_ref &&
                    !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
                        if (verbose)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
@@ -251,7 +253,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                 */
 
                if (!force_update &&
-                   !delete_ref &&
+                   !will_delete_ref &&
                    !is_null_sha1(ref->old_sha1) &&
                    !ref->force) {
                        if (!has_sha1_file(ref->old_sha1) ||
@@ -275,7 +277,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                        }
                }
                hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               if (!delete_ref)
+               if (!will_delete_ref)
                        new_refs++;
                strcpy(old_hex, sha1_to_hex(ref->old_sha1));
                new_hex = sha1_to_hex(ref->new_sha1);
@@ -290,7 +292,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                else
                        packet_write(out, "%s %s %s",
                                     old_hex, new_hex, ref->name);
-               if (delete_ref)
+               if (will_delete_ref)
                        fprintf(stderr, "deleting '%s'\n", ref->name);
                else {
                        fprintf(stderr, "updating '%s'", ref->name);
@@ -300,6 +302,28 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                        fprintf(stderr, "\n  from %s\n  to   %s\n",
                                old_hex, new_hex);
                }
+               if (remote) {
+                       struct refspec rs;
+                       rs.src = ref->name;
+                       rs.dst = NULL;
+                       if (!remote_find_tracking(remote, &rs)) {
+                               struct ref_lock *lock;
+                               fprintf(stderr, " Also local %s\n", rs.dst);
+                               if (will_delete_ref) {
+                                       if (delete_ref(rs.dst, NULL)) {
+                                               error("Failed to delete");
+                                       }
+                               } else {
+                                       lock = lock_any_ref_for_update(rs.dst, NULL, 0);
+                                       if (!lock)
+                                               error("Failed to lock");
+                                       else
+                                               write_ref_sha1(lock, ref->new_sha1,
+                                                              "update by push");
+                               }
+                               free(rs.dst);
+                       }
+               }
        }
 
        packet_flush(out);
@@ -330,6 +354,7 @@ static void verify_remote_names(int nr_heads, char **heads)
                case -2: /* ok but a single level -- that is fine for
                          * a match pattern.
                          */
+               case -3: /* ok but ends with a pattern-match character */
                        continue;
                }
                die("remote part of refspec is not a valid name in %s",
@@ -344,6 +369,8 @@ int main(int argc, char **argv)
        char **heads = NULL;
        int fd[2], ret;
        pid_t pid;
+       char *remote_name = NULL;
+       struct remote *remote = NULL;
 
        setup_git_directory();
        git_config(git_default_config);
@@ -361,6 +388,10 @@ int main(int argc, char **argv)
                                receivepack = arg + 7;
                                continue;
                        }
+                       if (!prefixcmp(arg, "--remote=")) {
+                               remote_name = arg + 9;
+                               continue;
+                       }
                        if (!strcmp(arg, "--all")) {
                                send_all = 1;
                                continue;
@@ -393,10 +424,18 @@ int main(int argc, char **argv)
                usage(send_pack_usage);
        verify_remote_names(nr_heads, heads);
 
-       pid = git_connect(fd, dest, receivepack);
+       if (remote_name) {
+               remote = remote_get(remote_name);
+               if (!remote_has_uri(remote, dest)) {
+                       die("Destination %s is not a uri for %s",
+                           dest, remote_name);
+               }
+       }
+
+       pid = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0);
        if (pid < 0)
                return 1;
-       ret = send_pack(fd[0], fd[1], nr_heads, heads);
+       ret = send_pack(fd[0], fd[1], remote, nr_heads, heads);
        close(fd[0]);
        close(fd[1]);
        ret |= finish_connect(pid);
index f9be5a7f60c1cc5208e7c91367ecb28014106f18..0d1312ca56d52daa3fc692984d4d3abaf3425791 100644 (file)
@@ -38,6 +38,7 @@ static int update_info_refs(int force)
                return error("unable to update %s", path0);
        for_each_ref(add_info_ref, NULL);
        fclose(info_ref_fp);
+       adjust_shared_perm(path1);
        rename(path1, path0);
        free(path0);
        free(path1);
@@ -227,6 +228,7 @@ static int update_info_packs(int force)
                return error("cannot open %s", name);
        write_pack_info_file(fp);
        fclose(fp);
+       adjust_shared_perm(name);
        rename(name, infofile);
        return 0;
 }
diff --git a/setup.c b/setup.c
index 2b8e8c0e5e1e957615e986efaab289d77c8fc51c..06004f15874fbcbab50eafd272c5898a34fcdcfe 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -1,4 +1,8 @@
 #include "cache.h"
+#include "dir.h"
+
+static int inside_git_dir = -1;
+static int inside_work_tree = -1;
 
 const char *prefix_path(const char *prefix, int len, const char *path)
 {
@@ -39,7 +43,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
        if (len) {
                int speclen = strlen(path);
                char *n = xmalloc(speclen + len + 1);
-       
+
                memcpy(n, prefix, len);
                memcpy(n + len, path, speclen+1);
                path = n;
@@ -47,7 +51,7 @@ const char *prefix_path(const char *prefix, int len, const char *path)
        return path;
 }
 
-/* 
+/*
  * Unlike prefix_path, this should be used if the named file does
  * not have to interact with index entry; i.e. name of a random file
  * on the filesystem.
@@ -95,7 +99,7 @@ void verify_non_filename(const char *prefix, const char *arg)
        const char *name;
        struct stat st;
 
-       if (is_inside_git_dir())
+       if (!is_inside_work_tree() || is_inside_git_dir())
                return;
        if (*arg == '-')
                return; /* flag */
@@ -170,30 +174,45 @@ static int is_git_directory(const char *suspect)
        return 1;
 }
 
-static int inside_git_dir = -1;
-
 int is_inside_git_dir(void)
 {
-       if (inside_git_dir < 0) {
-               char buffer[1024];
-
-               if (is_bare_repository())
-                       return (inside_git_dir = 1);
-               if (getcwd(buffer, sizeof(buffer))) {
-                       const char *git_dir = get_git_dir(), *cwd = buffer;
-                       while (*git_dir && *git_dir == *cwd) {
-                               git_dir++;
-                               cwd++;
-                       }
-                       inside_git_dir = !*git_dir;
-               } else
-                       inside_git_dir = 0;
-       }
+       if (inside_git_dir < 0)
+               inside_git_dir = is_inside_dir(get_git_dir());
        return inside_git_dir;
 }
 
+int is_inside_work_tree(void)
+{
+       if (inside_work_tree < 0)
+               inside_work_tree = is_inside_dir(get_git_work_tree());
+       return inside_work_tree;
+}
+
+/*
+ * set_work_tree() is only ever called if you set GIT_DIR explicitely.
+ * The old behaviour (which we retain here) is to set the work tree root
+ * to the cwd, unless overridden by the config, the command line, or
+ * GIT_WORK_TREE.
+ */
+static const char *set_work_tree(const char *dir)
+{
+       char buffer[PATH_MAX + 1];
+
+       if (!getcwd(buffer, sizeof(buffer)))
+               die ("Could not get the current working directory");
+       git_work_tree_cfg = xstrdup(buffer);
+       inside_work_tree = 1;
+
+       return NULL;
+}
+
+/*
+ * We cannot decide in this function whether we are in the work tree or
+ * not, since the config can only be read _after_ this function was called.
+ */
 const char *setup_git_directory_gently(int *nongit_ok)
 {
+       const char *work_tree_env = getenv(GIT_WORK_TREE_ENVIRONMENT);
        static char cwd[PATH_MAX+1];
        const char *gitdirenv;
        int len, offset;
@@ -207,8 +226,11 @@ const char *setup_git_directory_gently(int *nongit_ok)
        if (gitdirenv) {
                if (PATH_MAX - 40 < strlen(gitdirenv))
                        die("'$%s' too big", GIT_DIR_ENVIRONMENT);
-               if (is_git_directory(gitdirenv))
+               if (is_git_directory(gitdirenv)) {
+                       if (!work_tree_env)
+                               return set_work_tree(gitdirenv);
                        return NULL;
+               }
                if (nongit_ok) {
                        *nongit_ok = 1;
                        return NULL;
@@ -216,23 +238,32 @@ const char *setup_git_directory_gently(int *nongit_ok)
                die("Not a git repository: '%s'", gitdirenv);
        }
 
-       if (!getcwd(cwd, sizeof(cwd)-1) || cwd[0] != '/')
+       if (!getcwd(cwd, sizeof(cwd)-1))
                die("Unable to read current working directory");
 
+       /*
+        * Test in the following order (relative to the cwd):
+        * - .git/
+        * - ./ (bare)
+        * - ../.git/
+        * - ../ (bare)
+        * - ../../.git/
+        *   etc.
+        */
        offset = len = strlen(cwd);
        for (;;) {
-               if (is_git_directory(".git"))
+               if (is_git_directory(DEFAULT_GIT_DIR_ENVIRONMENT))
                        break;
+               if (is_git_directory(".")) {
+                       inside_git_dir = 1;
+                       if (!work_tree_env)
+                               inside_work_tree = 0;
+                       setenv(GIT_DIR_ENVIRONMENT, ".", 1);
+                       return NULL;
+               }
                chdir("..");
                do {
                        if (!offset) {
-                               if (is_git_directory(cwd)) {
-                                       if (chdir(cwd))
-                                               die("Cannot come back to cwd");
-                                       setenv(GIT_DIR_ENVIRONMENT, cwd, 1);
-                                       inside_git_dir = 1;
-                                       return NULL;
-                               }
                                if (nongit_ok) {
                                        if (chdir(cwd))
                                                die("Cannot come back to cwd");
@@ -244,6 +275,10 @@ const char *setup_git_directory_gently(int *nongit_ok)
                } while (cwd[--offset] != '/');
        }
 
+       inside_git_dir = 0;
+       if (!work_tree_env)
+               inside_work_tree = 1;
+       git_work_tree_cfg = xstrndup(cwd, offset);
        if (offset == len)
                return NULL;
 
@@ -251,13 +286,13 @@ const char *setup_git_directory_gently(int *nongit_ok)
        offset++;
        cwd[len++] = '/';
        cwd[len] = 0;
-       inside_git_dir = !prefixcmp(cwd + offset, ".git/");
        return cwd + offset;
 }
 
 int git_config_perm(const char *var, const char *value)
 {
        if (value) {
+               int i;
                if (!strcmp(value, "umask"))
                        return PERM_UMASK;
                if (!strcmp(value, "group"))
@@ -266,17 +301,30 @@ int git_config_perm(const char *var, const char *value)
                    !strcmp(value, "world") ||
                    !strcmp(value, "everybody"))
                        return PERM_EVERYBODY;
+               i = atoi(value);
+               if (i > 1)
+                       return i;
        }
        return git_config_bool(var, value);
 }
 
 int check_repository_format_version(const char *var, const char *value)
 {
-       if (strcmp(var, "core.repositoryformatversion") == 0)
-               repository_format_version = git_config_int(var, value);
+       if (strcmp(var, "core.repositoryformatversion") == 0)
+               repository_format_version = git_config_int(var, value);
        else if (strcmp(var, "core.sharedrepository") == 0)
                shared_repository = git_config_perm(var, value);
-       return 0;
+       else if (strcmp(var, "core.bare") == 0) {
+               is_bare_repository_cfg = git_config_bool(var, value);
+               if (is_bare_repository_cfg == 1)
+                       inside_work_tree = -1;
+       } else if (strcmp(var, "core.worktree") == 0) {
+               if (git_work_tree_cfg)
+                       free(git_work_tree_cfg);
+               git_work_tree_cfg = xstrdup(value);
+               inside_work_tree = -1;
+       }
+       return 0;
 }
 
 int check_repository_format(void)
@@ -292,5 +340,16 @@ const char *setup_git_directory(void)
 {
        const char *retval = setup_git_directory_gently(NULL);
        check_repository_format();
+
+       /* If the work tree is not the default one, recompute prefix */
+       if (inside_work_tree < 0) {
+               static char buffer[PATH_MAX + 1];
+               char *rel;
+               if (retval && chdir(retval))
+                       die ("Could not jump back into original cwd");
+               rel = get_relative_cwd(buffer, PATH_MAX, get_git_work_tree());
+               return rel && *rel ? strcat(rel, "/") : NULL;
+       }
+
        return retval;
 }
index 1d328c8d61d97314c4fd7f83888b0d30b22ac301..b219d4d5f246eef13c3fe22cd39497b621d7cc4b 100644 (file)
@@ -193,7 +193,7 @@ char *sha1_pack_name(const unsigned char *sha1)
                *buf++ = hex[val >> 4];
                *buf++ = hex[val & 0xf];
        }
-       
+
        return base;
 }
 
@@ -218,7 +218,7 @@ char *sha1_pack_index_name(const unsigned char *sha1)
                *buf++ = hex[val >> 4];
                *buf++ = hex[val & 0xf];
        }
-       
+
        return base;
 }
 
@@ -380,11 +380,12 @@ void prepare_alt_odb(void)
 {
        const char *alt;
 
+       if (alt_odb_tail)
+               return;
+
        alt = getenv(ALTERNATE_DB_ENVIRONMENT);
        if (!alt) alt = "";
 
-       if (alt_odb_tail)
-               return;
        alt_odb_tail = &alt_odb_list;
        link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL, 0);
 
@@ -416,7 +417,7 @@ static size_t peak_pack_mapped;
 static size_t pack_mapped;
 struct packed_git *packed_git;
 
-void pack_report()
+void pack_report(void)
 {
        fprintf(stderr,
                "pack_report: getpagesize()            = %10" SZ_FMT "\n"
@@ -496,7 +497,7 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
                 */
                if (idx_size != 4*256 + nr * 24 + 20 + 20) {
                        munmap(idx_map, idx_size);
-                       return error("wrong index file size in %s", path);
+                       return error("wrong index v1 file size in %s", path);
                }
        } else if (version == 2) {
                /*
@@ -518,7 +519,7 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
                        max_size += (nr - 1)*8;
                if (idx_size < min_size || idx_size > max_size) {
                        munmap(idx_map, idx_size);
-                       return error("wrong index file size in %s", path);
+                       return error("wrong index v2 file size in %s", path);
                }
                if (idx_size != min_size) {
                        /* make sure we can deal with large pack offsets */
@@ -537,6 +538,21 @@ static int check_packed_git_idx(const char *path,  struct packed_git *p)
        return 0;
 }
 
+int open_pack_index(struct packed_git *p)
+{
+       char *idx_name;
+       int ret;
+
+       if (p->index_data)
+               return 0;
+
+       idx_name = xstrdup(p->pack_name);
+       strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx");
+       ret = check_packed_git_idx(idx_name, p);
+       free(idx_name);
+       return ret;
+}
+
 static void scan_windows(struct packed_git *p,
        struct packed_git **lru_p,
        struct pack_window **lru_w,
@@ -612,6 +628,9 @@ static int open_packed_git_1(struct packed_git *p)
        unsigned char *idx_sha1;
        long fd_flag;
 
+       if (!p->index_data && open_pack_index(p))
+               return error("packfile %s index unavailable", p->pack_name);
+
        p->pack_fd = open(p->pack_name, O_RDONLY);
        if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
                return -1;
@@ -764,8 +783,7 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local)
                return NULL;
        memcpy(p->pack_name, path, path_len);
        strcpy(p->pack_name + path_len, ".pack");
-       if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode) ||
-           check_packed_git_idx(path, p)) {
+       if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) {
                free(p);
                return NULL;
        }
@@ -773,6 +791,10 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local)
        /* ok, it looks sane as far as we can check without
         * actually mapping the pack file.
         */
+       p->index_version = 0;
+       p->index_data = NULL;
+       p->index_size = 0;
+       p->num_objects = 0;
        p->pack_size = st.st_size;
        p->next = NULL;
        p->windows = NULL;
@@ -950,7 +972,7 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz
        return hashcmp(sha1, real_sha1) ? -1 : 0;
 }
 
-void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
+static void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
 {
        struct stat st;
        void *map;
@@ -985,7 +1007,7 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
        return map;
 }
 
-int legacy_loose_object(unsigned char *map)
+static int legacy_loose_object(unsigned char *map)
 {
        unsigned int word;
 
@@ -1047,6 +1069,14 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
                return inflate(stream, 0);
        }
 
+
+       /*
+        * There used to be a second loose object header format which
+        * was meant to mimic the in-pack format, allowing for direct
+        * copy of the object data.  This format turned up not to be
+        * really worth it and we don't write it any longer.  But we
+        * can still read it.
+        */
        used = unpack_object_header_gently(map, mapsize, &type, &size);
        if (!used || !valid_loose_object_type[type])
                return -1;
@@ -1122,7 +1152,7 @@ static int parse_sha1_header(const char *hdr, unsigned long *sizep)
        unsigned long size;
 
        /*
-        * The type can be at most ten bytes (including the 
+        * The type can be at most ten bytes (including the
         * terminating '\0' that we add), and is followed by
         * a space.
         */
@@ -1577,10 +1607,15 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset,
        return data;
 }
 
-const unsigned char *nth_packed_object_sha1(const struct packed_git *p,
+const unsigned char *nth_packed_object_sha1(struct packed_git *p,
                                            uint32_t n)
 {
        const unsigned char *index = p->index_data;
+       if (!index) {
+               if (open_pack_index(p))
+                       return NULL;
+               index = p->index_data;
+       }
        if (n >= p->num_objects)
                return NULL;
        index += 4 * 256;
@@ -1617,6 +1652,12 @@ off_t find_pack_entry_one(const unsigned char *sha1,
        const unsigned char *index = p->index_data;
        unsigned hi, lo;
 
+       if (!index) {
+               if (open_pack_index(p))
+                       return 0;
+               level1_ofs = p->index_data;
+               index = p->index_data;
+       }
        if (p->index_version > 1) {
                level1_ofs += 2;
                index += 8;
@@ -1659,20 +1700,25 @@ static int matches_pack_name(struct packed_git *p, const char *ig)
 
 static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
 {
+       static struct packed_git *last_found = (void *)1;
        struct packed_git *p;
        off_t offset;
 
        prepare_packed_git();
+       if (!packed_git)
+               return 0;
+       p = (last_found == (void *)1) ? packed_git : last_found;
 
-       for (p = packed_git; p; p = p->next) {
+       do {
                if (ignore_packed) {
                        const char **ig;
                        for (ig = ignore_packed; *ig; ig++)
                                if (!matches_pack_name(p, *ig))
                                        break;
                        if (*ig)
-                               continue;
+                               goto next;
                }
+
                offset = find_pack_entry_one(sha1, p);
                if (offset) {
                        /*
@@ -1685,18 +1731,27 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons
                         */
                        if (p->pack_fd == -1 && open_packed_git(p)) {
                                error("packfile %s cannot be accessed", p->pack_name);
-                               continue;
+                               goto next;
                        }
                        e->offset = offset;
                        e->p = p;
                        hashcpy(e->sha1, sha1);
+                       last_found = p;
                        return 1;
                }
-       }
+
+               next:
+               if (p == last_found)
+                       p = packed_git;
+               else
+                       p = p->next;
+               if (p == last_found)
+                       p = p->next;
+       } while (p);
        return 0;
 }
 
-struct packed_git *find_sha1_pack(const unsigned char *sha1, 
+struct packed_git *find_sha1_pack(const unsigned char *sha1,
                                  struct packed_git *packs)
 {
        struct packed_git *p;
@@ -1975,40 +2030,6 @@ static int write_buffer(int fd, const void *buf, size_t len)
        return 0;
 }
 
-static int write_binary_header(unsigned char *hdr, enum object_type type, unsigned long len)
-{
-       int hdr_len;
-       unsigned char c;
-
-       c = (type << 4) | (len & 15);
-       len >>= 4;
-       hdr_len = 1;
-       while (len) {
-               *hdr++ = c | 0x80;
-               hdr_len++;
-               c = (len & 0x7f);
-               len >>= 7;
-       }
-       *hdr = c;
-       return hdr_len;
-}
-
-static void setup_object_header(z_stream *stream, const char *type, unsigned long len)
-{
-       int obj_type, hdrlen;
-
-       if (use_legacy_headers) {
-               while (deflate(stream, 0) == Z_OK)
-                       /* nothing */;
-               return;
-       }
-       obj_type = type_from_string(type);
-       hdrlen = write_binary_header(stream->next_out, obj_type, len);
-       stream->total_out = hdrlen;
-       stream->next_out += hdrlen;
-       stream->avail_out -= hdrlen;
-}
-
 int hash_sha1_file(const void *buf, unsigned long len, const char *type,
                    unsigned char *sha1)
 {
@@ -2075,7 +2096,8 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        /* First header.. */
        stream.next_in = (unsigned char *)hdr;
        stream.avail_in = hdrlen;
-       setup_object_header(&stream, type, len);
+       while (deflate(&stream, 0) == Z_OK)
+               /* nothing */;
 
        /* Then the data itself.. */
        stream.next_in = buf;
@@ -2282,27 +2304,36 @@ int has_sha1_file(const unsigned char *sha1)
  *
  * returns 0 if anything went fine and -1 otherwise
  *
+ * The buffer is always NUL-terminated, not including it in returned size.
+ *
  * NOTE: both buf and size may change, but even when -1 is returned
  * you still have to free() it yourself.
  */
-int read_pipe(int fd, char** return_buf, unsigned long* return_size)
+int read_fd(int fd, char **return_buf, unsigned long *return_size)
 {
-       charbuf = *return_buf;
+       char *buf = *return_buf;
        unsigned long size = *return_size;
        ssize_t iret;
        unsigned long off = 0;
 
+       if (!buf || size <= 1) {
+               size = 1024;
+               buf = xrealloc(buf, size);
+       }
+
        do {
-               iret = xread(fd, buf + off, size - off);
+               iret = xread(fd, buf + off, (size - 1) - off);
                if (iret > 0) {
                        off += iret;
-                       if (off == size) {
-                               size *= 2;
+                       if (off == size - 1) {
+                               size = alloc_nr(size);
                                buf = xrealloc(buf, size);
                        }
                }
        } while (iret > 0);
 
+       buf[off] = '\0';
+
        *return_buf = buf;
        *return_size = off;
 
@@ -2317,7 +2348,7 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
        char *buf = xmalloc(size);
        int ret;
 
-       if (read_pipe(fd, &buf, &size)) {
+       if (read_fd(fd, &buf, &size)) {
                free(buf);
                return -1;
        }
index 55f25a2d3b49b1d565a9840b7dcfb2e22687cdd3..2d727d54dc43c23cb2fe19369d58e57e2a020ca9 100644 (file)
@@ -76,8 +76,11 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne
 
        prepare_packed_git();
        for (p = packed_git; p && found < 2; p = p->next) {
-               uint32_t num = p->num_objects;
-               uint32_t first = 0, last = num;
+               uint32_t num, last;
+               uint32_t first = 0;
+               open_pack_index(p);
+               num = p->num_objects;
+               last = num;
                while (first < last) {
                        uint32_t mid = (first + last) / 2;
                        const unsigned char *now;
@@ -133,6 +136,7 @@ static int find_unique_short_object(int len, char *canonical,
        int has_unpacked, has_packed;
        unsigned char unpacked_sha1[20], packed_sha1[20];
 
+       prepare_alt_odb();
        has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1);
        has_packed = find_short_packed_object(len, res, packed_sha1);
        if (!has_unpacked && !has_packed)
@@ -366,7 +370,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                                fprintf(stderr,
                                        "warning: Log for '%.*s' only goes "
                                        "back to %s.\n", len, str,
-                                       show_rfc2822_date(co_time, co_tz));
+                                       show_date(co_time, co_tz, DATE_RFC2822));
                        else
                                fprintf(stderr,
                                        "warning: Log for '%.*s' only has "
@@ -654,7 +658,6 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
        const char *cp;
 
        *mode = S_IFINVALID;
-       prepare_alt_odb();
        ret = get_sha1_1(name, namelen, sha1);
        if (!ret)
                return ret;
@@ -679,8 +682,6 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode)
                namelen = namelen - (cp - name);
                if (!active_cache)
                        read_cache();
-               if (active_nr < 0)
-                       return -1;
                pos = cache_name_pos(cp, namelen);
                if (pos < 0)
                        pos = -pos - 1;
index d17868929ceadbd2565549a60cb738a95c90a4bb..dbd9f5ad0ac21e70fc3a095d8e2938f245c238d3 100644 (file)
--- a/shallow.c
+++ b/shallow.c
@@ -101,4 +101,3 @@ struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
 
        return result;
 }
-
index 498d41e19b5756c82ef8e0ead779f40812f84227..20c35f03dd1fb468c7cecc71f91951657f229f5e 100644 (file)
@@ -29,24 +29,24 @@ static int serve_object(int fd_in, int fd_out) {
        }
        if (!size)
                return -1;
-       
+
        if (verbose)
                fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
 
        remote = 0;
-       
+
        if (!has_sha1_file(sha1)) {
                fprintf(stderr, "git-ssh-upload: could not find %s\n",
                        sha1_to_hex(sha1));
                remote = -1;
        }
-       
+
        if (write_in_full(fd_out, &remote, 1) != 1)
                return 0;
-       
+
        if (remote < 0)
                return 0;
-       
+
        return write_sha1_to_fd(fd_out, sha1);
 }
 
index 7f14b0fb59bd7e14979b002f441ae84ff5b0e9a2..e33d06b87c978ad7484c9d6bf972681c3d6cdeb0 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -39,4 +39,3 @@ void read_line(struct strbuf *sb, FILE *fp, int term) {
                sb->eof = 1;
        strbuf_end(sb);
 }
-
index 19e38508a70708b78de6467df71ed36373999d6a..72d7884232fbb4ce04082a778f9f952395764bf6 100644 (file)
@@ -6,6 +6,7 @@
 #GIT_TEST_OPTS=--verbose --debug
 SHELL_PATH ?= $(SHELL)
 TAR ?= $(TAR)
+RM ?= rm -f
 
 # Shell quote;
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
@@ -19,7 +20,7 @@ $(T):
        @echo "*** $@ ***"; GIT_CONFIG=.git/config '$(SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS)
 
 clean:
-       rm -fr trash
+       $(RM) -r trash
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
@@ -28,4 +29,3 @@ full-svn-test:
 
 .PHONY: $(T) clean
 .NOTPARALLEL:
-
index f6fe78cd278bd25f47b8c17e14f5f419d639fb7a..8d4a44721380213369668ea74dc4dbe3cf360485 100644 (file)
@@ -48,3 +48,37 @@ svnrepo="file://$svnrepo"
 poke() {
        test-chmtime +1 "$1"
 }
+
+SVN_HTTPD_MODULE_PATH=${SVN_HTTPD_MODULE_PATH-'/usr/lib/apache2/modules'}
+SVN_HTTPD_PATH=${SVN_HTTPD_PATH-'/usr/sbin/apache2'}
+
+start_httpd () {
+       if test -z "$SVN_HTTPD_PORT"
+       then
+               echo >&2 'SVN_HTTPD_PORT is not defined!'
+               return
+       fi
+
+       mkdir "$GIT_DIR"/logs
+
+       cat > "$GIT_DIR/httpd.conf" <<EOF
+ServerName "git-svn test"
+ServerRoot "$GIT_DIR"
+DocumentRoot "$GIT_DIR"
+PidFile "$GIT_DIR/httpd.pid"
+Listen 127.0.0.1:$SVN_HTTPD_PORT
+LoadModule dav_module $SVN_HTTPD_MODULE_PATH/mod_dav.so
+LoadModule dav_svn_module $SVN_HTTPD_MODULE_PATH/mod_dav_svn.so
+<Location /svn>
+       DAV svn
+       SVNPath $rawsvnrepo
+</Location>
+EOF
+       "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k start
+       svnrepo=http://127.0.0.1:$SVN_HTTPD_PORT/svn
+}
+
+stop_httpd () {
+       test -z "$SVN_HTTPD_PORT" && return
+       "$SVN_HTTPD_PATH" -f "$GIT_DIR"/httpd.conf -k stop
+}
index d195603dfa9dc0328f1fcfd6c79c6c6cbe424f0c..168329adbc4edeedc98501575ccc9b9c81f0c061 100644 (file)
@@ -10,14 +10,14 @@ do
        echo This is Z/$p from the original tree. >Z/$p
        test_expect_success \
            "adding test file $p and Z/$p" \
-           'git-update-index --add $p &&
-           git-update-index --add Z/$p'
+           'git update-index --add $p &&
+           git update-index --add Z/$p'
     done
 done
 echo This is SS from the original tree. >SS
 test_expect_success \
     'adding test file SS' \
-    'git-update-index --add SS'
+    'git update-index --add SS'
 cat >TT <<\EOF
 This is a trivial merge sample text.
 Branch A is expected to upcase this word, here.
@@ -32,10 +32,10 @@ This concludes the trivial merge sample file.
 EOF
 test_expect_success \
     'adding test file TT' \
-    'git-update-index --add TT'
+    'git update-index --add TT'
 test_expect_success \
     'prepare initial tree' \
-    'tree_O=$(git-write-tree)'
+    'tree_O=$(git write-tree)'
 
 ################################################################
 # Branch A and B makes the changes according to the above matrix.
@@ -47,14 +47,14 @@ to_remove=$(echo D? Z/D?)
 rm -f $to_remove
 test_expect_success \
     'change in branch A (removal)' \
-    'git-update-index --remove $to_remove'
+    'git update-index --remove $to_remove'
 
 for p in M? Z/M?
 do
     echo This is modified $p in the branch A. >$p
     test_expect_success \
        'change in branch A (modification)' \
-        "git-update-index $p"
+        "git update-index $p"
 done
 
 for p in AN AA Z/AN Z/AA
@@ -62,32 +62,32 @@ do
     echo This is added $p in the branch A. >$p
     test_expect_success \
        'change in branch A (addition)' \
-       "git-update-index --add $p"
+       "git update-index --add $p"
 done
 
 echo This is SS from the modified tree. >SS
 echo This is LL from the modified tree. >LL
 test_expect_success \
     'change in branch A (addition)' \
-    'git-update-index --add LL &&
-     git-update-index SS'
+    'git update-index --add LL &&
+     git update-index SS'
 mv TT TT-
 sed -e '/Branch A/s/word/WORD/g' <TT- >TT
 rm -f TT-
 test_expect_success \
     'change in branch A (edit)' \
-    'git-update-index TT'
+    'git update-index TT'
 
 mkdir DF
 echo Branch A makes a file at DF/DF, creating a directory DF. >DF/DF
 test_expect_success \
     'change in branch A (change file to directory)' \
-    'git-update-index --add DF/DF'
+    'git update-index --add DF/DF'
 
 test_expect_success \
     'recording branch A tree' \
-    'tree_A=$(git-write-tree)'
-          
+    'tree_A=$(git write-tree)'
+
 ################################################################
 # Branch B
 # Start from O
@@ -96,21 +96,21 @@ rm -rf [NDMASLT][NDMASLT] Z DF
 mkdir Z
 test_expect_success \
     'reading original tree and checking out' \
-    'git-read-tree $tree_O &&
-     git-checkout-index -a'
+    'git read-tree $tree_O &&
+     git checkout-index -a'
 
 to_remove=$(echo ?D Z/?D)
 rm -f $to_remove
 test_expect_success \
     'change in branch B (removal)' \
-    "git-update-index --remove $to_remove"
+    "git update-index --remove $to_remove"
 
 for p in ?M Z/?M
 do
     echo This is modified $p in the branch B. >$p
     test_expect_success \
        'change in branch B (modification)' \
-       "git-update-index $p"
+       "git update-index $p"
 done
 
 for p in NA AA Z/NA Z/AA
@@ -118,41 +118,41 @@ do
     echo This is added $p in the branch B. >$p
     test_expect_success \
        'change in branch B (addition)' \
-       "git-update-index --add $p"
+       "git update-index --add $p"
 done
 echo This is SS from the modified tree. >SS
 echo This is LL from the modified tree. >LL
 test_expect_success \
     'change in branch B (addition and modification)' \
-    'git-update-index --add LL &&
-     git-update-index SS'
+    'git update-index --add LL &&
+     git update-index SS'
 mv TT TT-
 sed -e '/Branch B/s/word/WORD/g' <TT- >TT
 rm -f TT-
 test_expect_success \
     'change in branch B (modification)' \
-    'git-update-index TT'
+    'git update-index TT'
 
 echo Branch B makes a file at DF. >DF
 test_expect_success \
     'change in branch B (addition of a file to conflict with directory)' \
-    'git-update-index --add DF'
+    'git update-index --add DF'
 
 test_expect_success \
     'recording branch B tree' \
-    'tree_B=$(git-write-tree)'
+    'tree_B=$(git write-tree)'
 
 test_expect_success \
     'keep contents of 3 trees for easy access' \
     'rm -f .git/index &&
-     git-read-tree $tree_O &&
+     git read-tree $tree_O &&
      mkdir .orig-O &&
-     git-checkout-index --prefix=.orig-O/ -f -q -a &&
+     git checkout-index --prefix=.orig-O/ -f -q -a &&
      rm -f .git/index &&
-     git-read-tree $tree_A &&
+     git read-tree $tree_A &&
      mkdir .orig-A &&
-     git-checkout-index --prefix=.orig-A/ -f -q -a &&
+     git checkout-index --prefix=.orig-A/ -f -q -a &&
      rm -f .git/index &&
-     git-read-tree $tree_B &&
+     git read-tree $tree_B &&
      mkdir .orig-B &&
-     git-checkout-index --prefix=.orig-B/ -f -q -a'
+     git checkout-index --prefix=.orig-B/ -f -q -a'
index 186de7024336ca98bd5dcea6f675e1fc221d0ce0..4e49d590651363631a1f3a795d3c1679a70fb05f 100755 (executable)
@@ -31,13 +31,13 @@ fi
 . ./test-lib.sh
 
 ################################################################
-# git-init has been done in an empty repository.
+# git init has been done in an empty repository.
 # make sure it is empty.
 
 find .git/objects -type f -print >should-be-empty
 test_expect_success \
-    '.git/objects should be empty after git-init in an empty repo.' \
-    'cmp -s /dev/null should-be-empty' 
+    '.git/objects should be empty after git init in an empty repo.' \
+    'cmp -s /dev/null should-be-empty'
 
 # also it should have 2 subdirectories; no fan-out anymore, pack, and info.
 # 3 is counting "objects" itself
@@ -51,17 +51,17 @@ test_expect_success \
 
 # updating a new file without --add should fail.
 test_expect_failure \
-    'git-update-index without --add should fail adding.' \
-    'git-update-index should-be-empty'
+    'git update-index without --add should fail adding.' \
+    'git update-index should-be-empty'
 
 # and with --add it should succeed, even if it is empty (it used to fail).
 test_expect_success \
-    'git-update-index with --add should succeed.' \
-    'git-update-index --add should-be-empty'
+    'git update-index with --add should succeed.' \
+    'git update-index --add should-be-empty'
 
 test_expect_success \
-    'writing tree out with git-write-tree' \
-    'tree=$(git-write-tree)'
+    'writing tree out with git write-tree' \
+    'tree=$(git write-tree)'
 
 # we know the shape and contents of the tree and know the object ID for it.
 test_expect_success \
@@ -71,17 +71,17 @@ test_expect_success \
 # Removing paths.
 rm -f should-be-empty full-of-directories
 test_expect_failure \
-    'git-update-index without --remove should fail removing.' \
-    'git-update-index should-be-empty'
+    'git update-index without --remove should fail removing.' \
+    'git update-index should-be-empty'
 
 test_expect_success \
-    'git-update-index with --remove should be able to remove.' \
-    'git-update-index --remove should-be-empty'
+    'git update-index with --remove should be able to remove.' \
+    'git update-index --remove should-be-empty'
 
 # Empty tree can be written with recent write-tree.
 test_expect_success \
-    'git-write-tree should be able to write an empty tree.' \
-    'tree=$(git-write-tree)'
+    'git write-tree should be able to write an empty tree.' \
+    'tree=$(git write-tree)'
 
 test_expect_success \
     'validate object ID of a known tree.' \
@@ -95,13 +95,13 @@ do
     ln -s "hello $p" ${p}sym
 done
 test_expect_success \
-    'adding various types of objects with git-update-index --add.' \
-    'find path* ! -type d -print | xargs git-update-index --add'
+    'adding various types of objects with git update-index --add.' \
+    'find path* ! -type d -print | xargs git update-index --add'
 
 # Show them and see that matches what we expect.
 test_expect_success \
-    'showing stage with git-ls-files --stage' \
-    'git-ls-files --stage >current'
+    'showing stage with git ls-files --stage' \
+    'git ls-files --stage >current'
 
 cat >expected <<\EOF
 100644 f87290f8eb2cbbea7857214459a0739927eab154 0      path0
@@ -114,19 +114,19 @@ cat >expected <<\EOF
 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0      path3/subp3/file3sym
 EOF
 test_expect_success \
-    'validate git-ls-files output for a known tree.' \
+    'validate git ls-files output for a known tree.' \
     'diff current expected'
 
 test_expect_success \
-    'writing tree out with git-write-tree.' \
-    'tree=$(git-write-tree)'
+    '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_expect_success \
-    'showing tree with git-ls-tree' \
-    'git-ls-tree $tree >current'
+    'showing tree with git ls-tree' \
+    'git ls-tree $tree >current'
 cat >expected <<\EOF
 100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
@@ -134,14 +134,14 @@ cat >expected <<\EOF
 040000 tree 21ae8269cacbe57ae09138dcc3a2887f904d02b3   path3
 EOF
 test_expect_success \
-    'git-ls-tree output for a known tree.' \
+    'git ls-tree output for a known tree.' \
     'diff current expected'
 
 # 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'
+    'showing tree with git ls-tree -r' \
+    'git ls-tree -r $tree >current'
 cat >expected <<\EOF
 100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
@@ -153,13 +153,13 @@ cat >expected <<\EOF
 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
 EOF
 test_expect_success \
-    'git-ls-tree -r output for a known tree.' \
+    'git ls-tree -r output for a known tree.' \
     'diff current expected'
 
 # But with -r -t we can have both.
 test_expect_success \
-    'showing tree with git-ls-tree -r -t' \
-    'git-ls-tree -r -t $tree >current'
+    'showing tree with git ls-tree -r -t' \
+    'git ls-tree -r -t $tree >current'
 cat >expected <<\EOF
 100644 blob f87290f8eb2cbbea7857214459a0739927eab154   path0
 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01   path0sym
@@ -174,19 +174,19 @@ cat >expected <<\EOF
 120000 blob 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c   path3/subp3/file3sym
 EOF
 test_expect_success \
-    'git-ls-tree -r output for a known tree.' \
+    'git ls-tree -r output for a known tree.' \
     'diff current expected'
 
 test_expect_success \
-    'writing partial tree out with git-write-tree --prefix.' \
-    'ptree=$(git-write-tree --prefix=path3)'
+    '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_expect_success \
-    'writing partial tree out with git-write-tree --prefix.' \
-    'ptree=$(git-write-tree --prefix=path3/subp3)'
+    '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'
@@ -202,24 +202,24 @@ EOF
 rm .git/index
 test_expect_success \
     'put invalid objects into the index.' \
-    'git-update-index --index-info < badobjects'
+    'git update-index --index-info < badobjects'
 
 test_expect_failure \
     'writing this tree without --missing-ok.' \
-    'git-write-tree'
+    'git write-tree'
 
 test_expect_success \
     'writing this tree with --missing-ok.' \
-    'git-write-tree --missing-ok'
+    'git write-tree --missing-ok'
 
 
 ################################################################
 rm .git/index
 test_expect_success \
-    'git-read-tree followed by write-tree should be idempotent.' \
-    'git-read-tree $tree &&
+    'git read-tree followed by write-tree should be idempotent.' \
+    'git read-tree $tree &&
      test -f .git/index &&
-     newtree=$(git-write-tree) &&
+     newtree=$(git write-tree) &&
      test "$newtree" = "$tree"'
 
 cat >expected <<\EOF
@@ -233,36 +233,36 @@ cat >expected <<\EOF
 :120000 120000 6649a1ebe9e9f1c553b66f5a6e74136a07ccc57c 0000000000000000000000000000000000000000 M     path3/subp3/file3sym
 EOF
 test_expect_success \
-    'validate git-diff-files output for a know cache/work tree state.' \
-    'git-diff-files >current && diff >/dev/null -b current expected'
+    'validate git diff-files output for a know cache/work tree state.' \
+    'git diff-files >current && diff >/dev/null -b current expected'
 
 test_expect_success \
-    'git-update-index --refresh should succeed.' \
-    'git-update-index --refresh'
+    'git update-index --refresh should succeed.' \
+    'git update-index --refresh'
 
 test_expect_success \
-    'no diff after checkout and git-update-index --refresh.' \
-    'git-diff-files >current && cmp -s current /dev/null'
+    'no diff after checkout and git update-index --refresh.' \
+    'git diff-files >current && cmp -s current /dev/null'
 
 ################################################################
 P=087704a96baf1c2d1c869a8b084481e121c88b5b
 test_expect_success \
-    'git-commit-tree records the correct tree in a commit.' \
-    'commit0=$(echo NO | git-commit-tree $P) &&
+    'git commit-tree records the correct tree in a commit.' \
+    'commit0=$(echo NO | git commit-tree $P) &&
      tree=$(git show --pretty=raw $commit0 |
         sed -n -e "s/^tree //p" -e "/^author /q") &&
      test "z$tree" = "z$P"'
 
 test_expect_success \
-    'git-commit-tree records the correct parent in a commit.' \
-    'commit1=$(echo NO | git-commit-tree $P -p $commit0) &&
+    'git commit-tree records the correct parent in a commit.' \
+    'commit1=$(echo NO | git commit-tree $P -p $commit0) &&
      parent=$(git show --pretty=raw $commit1 |
         sed -n -e "s/^parent //p" -e "/^author /q") &&
      test "z$commit0" = "z$parent"'
 
 test_expect_success \
-    'git-commit-tree omits duplicated parent in a commit.' \
-    'commit2=$(echo NO | git-commit-tree $P -p $commit0 -p $commit0) &&
+    'git commit-tree omits duplicated parent in a commit.' \
+    'commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) &&
      parent=$(git show --pretty=raw $commit2 |
         sed -n -e "s/^parent //p" -e "/^author /q" |
         sort -u) &&
@@ -281,4 +281,20 @@ test_expect_success 'update-index D/F conflict' '
        test $numpath0 = 1
 '
 
+test_expect_success 'absolute path works as expected' '
+       mkdir first &&
+       ln -s ../.git first/.git &&
+       mkdir second &&
+       ln -s ../first second/other &&
+       mkdir third &&
+       dir="$(cd .git; pwd -P)" &&
+       dir2=third/../second/other/.git &&
+       test "$dir" = "$(test-absolute-path $dir2)" &&
+       file="$dir"/index &&
+       test "$file" = "$(test-absolute-path $dir2/index)" &&
+       ln -s ../first/file .git/syml &&
+       sym="$(cd first; pwd -P)"/file &&
+       test "$sym" = "$(test-absolute-path $dir2/syml)"
+'
+
 test_done
index fe1dfd08a02e7a8c9c26542e934ccd6fc4f16f5c..0807d9f01a178567e433c9c057064d026109ade2 100755 (executable)
@@ -290,4 +290,85 @@ test_expect_success '.gitattributes says two and three are text' '
        fi
 '
 
+test_expect_success 'in-tree .gitattributes (1)' '
+
+       echo "one -crlf" >>.gitattributes &&
+       git add .gitattributes &&
+       git commit -m "Add .gitattributes" &&
+
+       rm -rf tmp one dir .gitattributes patch.file three &&
+       git read-tree --reset -u HEAD &&
+
+       if remove_cr one >/dev/null
+       then
+               echo "Eh? one should not have CRLF"
+               false
+       else
+               : happy
+       fi &&
+       remove_cr three >/dev/null || {
+               echo "Eh? three should still have CRLF"
+               false
+       }
+'
+
+test_expect_success 'in-tree .gitattributes (2)' '
+
+       rm -rf tmp one dir .gitattributes patch.file three &&
+       git read-tree --reset HEAD &&
+       git checkout-index -f -q -u -a &&
+
+       if remove_cr one >/dev/null
+       then
+               echo "Eh? one should not have CRLF"
+               false
+       else
+               : happy
+       fi &&
+       remove_cr three >/dev/null || {
+               echo "Eh? three should still have CRLF"
+               false
+       }
+'
+
+test_expect_success 'in-tree .gitattributes (3)' '
+
+       rm -rf tmp one dir .gitattributes patch.file three &&
+       git read-tree --reset HEAD &&
+       git checkout-index -u .gitattributes &&
+       git checkout-index -u one dir/two three &&
+
+       if remove_cr one >/dev/null
+       then
+               echo "Eh? one should not have CRLF"
+               false
+       else
+               : happy
+       fi &&
+       remove_cr three >/dev/null || {
+               echo "Eh? three should still have CRLF"
+               false
+       }
+'
+
+test_expect_success 'in-tree .gitattributes (4)' '
+
+       rm -rf tmp one dir .gitattributes patch.file three &&
+       git read-tree --reset HEAD &&
+       git checkout-index -u one dir/two three &&
+       git checkout-index -u .gitattributes &&
+
+       if remove_cr one >/dev/null
+       then
+               echo "Eh? one should not have CRLF"
+               false
+       else
+               : happy
+       fi &&
+       remove_cr three >/dev/null || {
+               echo "Eh? three should still have CRLF"
+               false
+       }
+'
+
 test_done
diff --git a/t/t0022-crlf-rename.sh b/t/t0022-crlf-rename.sh
new file mode 100755 (executable)
index 0000000..430a1d1
--- /dev/null
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+test_description='ignore CR in CRLF sequence while computing similiarity'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       cat ../t0022-crlf-rename.sh >sample &&
+       git add sample &&
+
+       test_tick &&
+       git commit -m Initial &&
+
+       sed -e "s/\$/\r/" ../t0022-crlf-rename.sh >elpmas &&
+       git add elpmas &&
+       rm -f sample &&
+
+       test_tick &&
+       git commit -a -m Second
+
+'
+
+test_expect_success 'diff -M' '
+
+       git diff-tree -M -r --name-status HEAD^ HEAD |
+       sed -e "s/R[0-9]*/RNUM/" >actual &&
+       echo "RNUM      sample  elpmas" >expect &&
+       diff -u expect actual
+
+'
+
+test_done
diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh
new file mode 100755 (executable)
index 0000000..cad95f3
--- /dev/null
@@ -0,0 +1,400 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Carlos Rica
+#
+
+test_description='git stripspace'
+
+. ./test-lib.sh
+
+t40='A quick brown fox jumps over the lazy do'
+s40='                                        '
+sss="$s40$s40$s40$s40$s40$s40$s40$s40$s40$s40" # 400
+ttt="$t40$t40$t40$t40$t40$t40$t40$t40$t40$t40" # 400
+
+test_expect_success \
+    'long lines without spaces should be unchanged' '
+    echo "$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual &&
+
+    echo "$ttt$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual &&
+
+    echo "$ttt$ttt$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual &&
+
+    echo "$ttt$ttt$ttt$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'lines with spaces at the beginning should be unchanged' '
+    echo "$sss$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual &&
+
+    echo "$sss$sss$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual &&
+
+    echo "$sss$sss$sss$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'lines with intermediate spaces should be unchanged' '
+    echo "$ttt$sss$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual &&
+
+    echo "$ttt$sss$sss$ttt" >expect &&
+    git stripspace <expect >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'consecutive blank lines should be unified' '
+    printf "$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt$ttt$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt\n\n\n\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$ttt$ttt\n" > expect &&
+    printf "$ttt\n\n\n\n\n$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$ttt$ttt$ttt\n" > expect &&
+    printf "$ttt\n\n\n\n\n$ttt$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt\n\t\n \n\n  \t\t\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt$ttt\n\t\n \n\n  \t\t\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt$ttt$ttt\n\t\n \n\n  \t\t\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$ttt\n" > expect &&
+    printf "$ttt\n\t\n \n\n  \t\t\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$ttt$ttt\n" > expect &&
+    printf "$ttt\n\t\n \n\n  \t\t\n$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$ttt$ttt$ttt\n" > expect &&
+    printf "$ttt\n\t\n \n\n  \t\t\n$ttt$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'only consecutive blank lines should be completely removed' '
+    > expect &&
+
+    printf "\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "\n\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss\n$sss\n$sss\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss$sss\n$sss\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "\n$sss\n$sss$sss\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss$sss$sss$sss\n\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "\n$sss$sss$sss$sss\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "\n\n$sss$sss$sss$sss\n" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'consecutive blank lines at the beginning should be removed' '
+    printf "$ttt\n" > expect &&
+    printf "\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n" > expect &&
+    printf "\n\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt\n" > expect &&
+    printf "\n\n\n$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt$ttt\n" > expect &&
+    printf "\n\n\n$ttt$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt$ttt$ttt\n" > expect &&
+    printf "\n\n\n$ttt$ttt$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n" > expect &&
+
+    printf "$sss\n$sss\n$sss\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "\n$sss\n$sss$sss\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss$sss\n$sss\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss$sss$sss\n\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "\n$sss$sss$sss\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "\n\n$sss$sss$sss\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'consecutive blank lines at the end should be removed' '
+    printf "$ttt\n" > expect &&
+    printf "$ttt\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n" > expect &&
+    printf "$ttt\n\n\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt\n" > expect &&
+    printf "$ttt$ttt\n\n\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt$ttt\n" > expect &&
+    printf "$ttt$ttt$ttt\n\n\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt$ttt$ttt\n" > expect &&
+    printf "$ttt$ttt$ttt$ttt\n\n\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n" > expect &&
+
+    printf "$ttt\n$sss\n$sss\n$sss\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$sss\n$sss$sss\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n$sss$sss\n$sss\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n$sss$sss$sss\n\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n$sss$sss$sss\n\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n\n\n$sss$sss$sss\n" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'text without newline at end should end with newline' '
+    test `printf "$ttt" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$ttt" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$ttt$ttt$ttt" | git stripspace | wc -l` -gt 0
+'
+
+# text plus spaces at the end:
+
+test_expect_success \
+    'text plus spaces without newline at end should end with newline' '
+    test `printf "$ttt$sss" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$ttt$ttt$sss" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$sss$sss" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$ttt$sss$sss" | git stripspace | wc -l` -gt 0 &&
+    test `printf "$ttt$sss$sss$sss" | git stripspace | wc -l` -gt 0
+'
+
+test_expect_failure \
+    'text plus spaces without newline at end should not show spaces' '
+    printf "$ttt$sss" | git stripspace | grep -q "  " ||
+    printf "$ttt$ttt$sss" | git stripspace | grep -q "  " ||
+    printf "$ttt$ttt$ttt$sss" | git stripspace | grep -q "  " ||
+    printf "$ttt$sss$sss" | git stripspace | grep -q "  " ||
+    printf "$ttt$ttt$sss$sss" | git stripspace | grep -q "  " ||
+    printf "$ttt$sss$sss$sss" | git stripspace | grep -q "  "
+'
+
+test_expect_success \
+    'text plus spaces without newline should show the correct lines' '
+    printf "$ttt\n" >expect &&
+    printf "$ttt$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n" >expect &&
+    printf "$ttt$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n" >expect &&
+    printf "$ttt$sss$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt\n" >expect &&
+    printf "$ttt$ttt$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt\n" >expect &&
+    printf "$ttt$ttt$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt$ttt\n" >expect &&
+    printf "$ttt$ttt$ttt$sss" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_failure \
+    'text plus spaces at end should not show spaces' '
+    echo "$ttt$sss" | git stripspace | grep -q "  " ||
+    echo "$ttt$ttt$sss" | git stripspace | grep -q "  " ||
+    echo "$ttt$ttt$ttt$sss" | git stripspace | grep -q "  " ||
+    echo "$ttt$sss$sss" | git stripspace | grep -q "  " ||
+    echo "$ttt$ttt$sss$sss" | git stripspace | grep -q "  " ||
+    echo "$ttt$sss$sss$sss" | git stripspace | grep -q "  "
+'
+
+test_expect_success \
+    'text plus spaces at end should be cleaned and newline must remain' '
+    echo "$ttt" >expect &&
+    echo "$ttt$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$ttt" >expect &&
+    echo "$ttt$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$ttt" >expect &&
+    echo "$ttt$sss$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$ttt$ttt" >expect &&
+    echo "$ttt$ttt$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$ttt$ttt" >expect &&
+    echo "$ttt$ttt$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$ttt$ttt$ttt" >expect &&
+    echo "$ttt$ttt$ttt$sss" | git stripspace >actual &&
+    git diff expect actual
+'
+
+# spaces only:
+
+test_expect_success \
+    'spaces with newline at end should be replaced with empty string' '
+    printf "" >expect &&
+
+    echo | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$sss$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    echo "$sss$sss$sss$sss" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_failure \
+    'spaces without newline at end should not show spaces' '
+    printf "" | git stripspace | grep -q " " ||
+    printf "$sss" | git stripspace | grep -q " " ||
+    printf "$sss$sss" | git stripspace | grep -q " " ||
+    printf "$sss$sss$sss" | git stripspace | grep -q " " ||
+    printf "$sss$sss$sss$sss" | git stripspace | grep -q " "
+'
+
+test_expect_success \
+    'spaces without newline at end should be replaced with empty string' '
+    printf "" >expect &&
+
+    printf "" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss$sss$sss" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$sss$sss$sss$sss" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_success \
+    'consecutive text lines should be unchanged' '
+    printf "$ttt$ttt\n$ttt\n" >expect &&
+    printf "$ttt$ttt\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n$ttt$ttt\n$ttt\n" >expect &&
+    printf "$ttt\n$ttt$ttt\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n$ttt\n$ttt\n$ttt$ttt\n" >expect &&
+    printf "$ttt\n$ttt\n$ttt\n$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n$ttt\n\n$ttt$ttt\n$ttt\n" >expect &&
+    printf "$ttt\n$ttt\n\n$ttt$ttt\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt$ttt\n\n$ttt\n$ttt$ttt\n" >expect &&
+    printf "$ttt$ttt\n\n$ttt\n$ttt$ttt\n" | git stripspace >actual &&
+    git diff expect actual &&
+
+    printf "$ttt\n$ttt$ttt\n\n$ttt\n" >expect &&
+    printf "$ttt\n$ttt$ttt\n\n$ttt\n" | git stripspace >actual &&
+    git diff expect actual
+'
+
+test_expect_success 'strip comments, too' '
+       test ! -z "$(echo "# comment" | git stripspace)" &&
+       test -z "$(echo "# comment" | git stripspace -s)"
+'
+
+test_done
index de4e5eb61f5cc197a51005c6cfbd3bf2b9428480..37add1b50472e23ccb6b938ac6cdadba0c097fb8 100755 (executable)
@@ -130,28 +130,28 @@ _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"
 
 check_result () {
-    git-ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
+    git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
     git diff expected current
 }
 
 # This is done on an empty work directory, which is the normal
 # merge person behaviour.
 test_expect_success \
-    '3-way merge with git-read-tree -m, empty cache' \
+    '3-way merge with git read-tree -m, empty cache' \
     "rm -fr [NDMALTS][NDMALTSF] Z &&
      rm .git/index &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 # This starts out with the first head, which is the normal
 # patch submitter behaviour.
 test_expect_success \
-    '3-way merge with git-read-tree -m, match H' \
+    '3-way merge with git read-tree -m, match H' \
     "rm -fr [NDMALTS][NDMALTSF] Z &&
      rm .git/index &&
-     git-read-tree $tree_A &&
-     git-checkout-index -f -u -a &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git read-tree $tree_A &&
+     git checkout-index -f -u -a &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 : <<\END_OF_CASE_TABLE
@@ -160,7 +160,7 @@ We have so far tested only empty index and clean-and-matching-A index
 case which are trivial.  Make sure index requirements are also
 checked.
 
-"git-read-tree -m O A B"
+"git read-tree -m O A B"
 
      O       A       B         result      index requirements
 -------------------------------------------------------------------
@@ -214,87 +214,87 @@ test_expect_failure \
     '1 - must not have an entry not in A.' \
     "rm -f .git/index XX &&
      echo XX >XX &&
-     git-update-index --add XX &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add XX &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '2 - must match B in !O && !A && B case.' \
     "rm -f .git/index NA &&
      cp .orig-B/NA NA &&
-     git-update-index --add NA &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add NA &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '2 - matching B alone is OK in !O && !A && B case.' \
     "rm -f .git/index NA &&
      cp .orig-B/NA NA &&
-     git-update-index --add NA &&
+     git update-index --add NA &&
      echo extra >>NA &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '3 - must match A in !O && A && !B case.' \
     "rm -f .git/index AN &&
      cp .orig-A/AN AN &&
-     git-update-index --add AN &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add AN &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
     '3 - matching A alone is OK in !O && A && !B case.' \
     "rm -f .git/index AN &&
      cp .orig-A/AN AN &&
-     git-update-index --add AN &&
+     git update-index --add AN &&
      echo extra >>AN &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '3 (fail) - must match A in !O && A && !B case.' \
     "rm -f .git/index AN &&
      cp .orig-A/AN AN &&
      echo extra >>AN &&
-     git-update-index --add AN &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add AN &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '4 - must match and be up-to-date in !O && A && B && A!=B case.' \
     "rm -f .git/index AA &&
      cp .orig-A/AA AA &&
-     git-update-index --add AA &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add AA &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
     '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
     "rm -f .git/index AA &&
      cp .orig-A/AA AA &&
-     git-update-index --add AA &&
+     git update-index --add AA &&
      echo extra >>AA &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \
     "rm -f .git/index AA &&
      cp .orig-A/AA AA &&
      echo extra >>AA &&
-     git-update-index --add AA &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add AA &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '5 - must match in !O && A && B && A==B case.' \
     "rm -f .git/index LL &&
      cp .orig-A/LL LL &&
-     git-update-index --add LL &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add LL &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
     '5 - must match in !O && A && B && A==B case.' \
     "rm -f .git/index LL &&
      cp .orig-A/LL LL &&
-     git-update-index --add LL &&
+     git update-index --add LL &&
      echo extra >>LL &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
@@ -302,117 +302,117 @@ test_expect_failure \
     "rm -f .git/index LL &&
      cp .orig-A/LL LL &&
      echo extra >>LL &&
-     git-update-index --add LL &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add LL &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '6 - must not exist in O && !A && !B case' \
     "rm -f .git/index DD &&
      echo DD >DD
-     git-update-index --add DD &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add DD &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '7 - must not exist in O && !A && B && O!=B case' \
     "rm -f .git/index DM &&
      cp .orig-B/DM DM &&
-     git-update-index --add DM &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add DM &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '8 - must not exist in O && !A && B && O==B case' \
     "rm -f .git/index DN &&
      cp .orig-B/DN DN &&
-     git-update-index --add DN &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add DN &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '9 - must match and be up-to-date in O && A && !B && O!=A case' \
     "rm -f .git/index MD &&
      cp .orig-A/MD MD &&
-     git-update-index --add MD &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add MD &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
     '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
     "rm -f .git/index MD &&
      cp .orig-A/MD MD &&
-     git-update-index --add MD &&
+     git update-index --add MD &&
      echo extra >>MD &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \
     "rm -f .git/index MD &&
      cp .orig-A/MD MD &&
      echo extra >>MD &&
-     git-update-index --add MD &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add MD &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '10 - must match and be up-to-date in O && A && !B && O==A case' \
     "rm -f .git/index ND &&
      cp .orig-A/ND ND &&
-     git-update-index --add ND &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add ND &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
     '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
     "rm -f .git/index ND &&
      cp .orig-A/ND ND &&
-     git-update-index --add ND &&
+     git update-index --add ND &&
      echo extra >>ND &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \
     "rm -f .git/index ND &&
      cp .orig-A/ND ND &&
      echo extra >>ND &&
-     git-update-index --add ND &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add ND &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
     "rm -f .git/index MM &&
      cp .orig-A/MM MM &&
-     git-update-index --add MM &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add MM &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
     '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
     "rm -f .git/index MM &&
      cp .orig-A/MM MM &&
-     git-update-index --add MM &&
+     git update-index --add MM &&
      echo extra >>MM &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \
     "rm -f .git/index MM &&
      cp .orig-A/MM MM &&
      echo extra >>MM &&
-     git-update-index --add MM &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add MM &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '12 - must match A in O && A && B && O!=A && A==B case' \
     "rm -f .git/index SS &&
      cp .orig-A/SS SS &&
-     git-update-index --add SS &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add SS &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
     '12 - must match A in O && A && B && O!=A && A==B case' \
     "rm -f .git/index SS &&
      cp .orig-A/SS SS &&
-     git-update-index --add SS &&
+     git update-index --add SS &&
      echo extra >>SS &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
@@ -420,74 +420,74 @@ test_expect_failure \
     "rm -f .git/index SS &&
      cp .orig-A/SS SS &&
      echo extra >>SS &&
-     git-update-index --add SS &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add SS &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '13 - must match A in O && A && B && O!=A && O==B case' \
     "rm -f .git/index MN &&
      cp .orig-A/MN MN &&
-     git-update-index --add MN &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add MN &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
     '13 - must match A in O && A && B && O!=A && O==B case' \
     "rm -f .git/index MN &&
      cp .orig-A/MN MN &&
-     git-update-index --add MN &&
+     git update-index --add MN &&
      echo extra >>MN &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
     '14 - must match and be up-to-date in O && A && B && O==A && O!=B case' \
     "rm -f .git/index NM &&
      cp .orig-A/NM NM &&
-     git-update-index --add NM &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add NM &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
     '14 - may match B in O && A && B && O==A && O!=B case' \
     "rm -f .git/index NM &&
      cp .orig-B/NM NM &&
-     git-update-index --add NM &&
+     git update-index --add NM &&
      echo extra >>NM &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
     '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
     "rm -f .git/index NM &&
      cp .orig-A/NM NM &&
-     git-update-index --add NM &&
+     git update-index --add NM &&
      echo extra >>NM &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_failure \
     '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \
     "rm -f .git/index NM &&
      cp .orig-A/NM NM &&
      echo extra >>NM &&
-     git-update-index --add NM &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add NM &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 test_expect_success \
     '15 - must match A in O && A && B && O==A && O==B case' \
     "rm -f .git/index NN &&
      cp .orig-A/NN NN &&
-     git-update-index --add NN &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git update-index --add NN &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_success \
     '15 - must match A in O && A && B && O==A && O==B case' \
     "rm -f .git/index NN &&
      cp .orig-A/NN NN &&
-     git-update-index --add NN &&
+     git update-index --add NN &&
      echo extra >>NN &&
-     git-read-tree -m $tree_O $tree_A $tree_B &&
+     git read-tree -m $tree_O $tree_A $tree_B &&
      check_result"
 
 test_expect_failure \
@@ -495,20 +495,20 @@ test_expect_failure \
     "rm -f .git/index NN &&
      cp .orig-A/NN NN &&
      echo extra >>NN &&
-     git-update-index --add NN &&
-     git-read-tree -m $tree_O $tree_A $tree_B"
+     git update-index --add NN &&
+     git read-tree -m $tree_O $tree_A $tree_B"
 
 # #16
 test_expect_success \
     '16 - A matches in one and B matches in another.' \
     'rm -f .git/index F16 &&
     echo F16 >F16 &&
-    git-update-index --add F16 &&
-    tree0=`git-write-tree` &&
+    git update-index --add F16 &&
+    tree0=`git write-tree` &&
     echo E16 >F16 &&
-    git-update-index F16 &&
-    tree1=`git-write-tree` &&
-    git-read-tree -m $tree0 $tree1 $tree1 $tree0 &&
-    git-ls-files --stage'
+    git update-index F16 &&
+    tree1=`git write-tree` &&
+    git read-tree -m $tree0 $tree1 $tree1 $tree0 &&
+    git ls-files --stage'
 
 test_done
index 030226bbfbd764a21aa59577b365868237d4f40b..b01b0037a0c4923549b5b2261ed931b3718a7671 100755 (executable)
@@ -11,7 +11,7 @@ There is the head (called H) and another commit (called M), which is
 simply ahead of H.  The index and the work tree contains a state that
 is derived from H, but may also have local changes.  This test checks
 all the combinations described in the two-tree merge "carry forward"
-rules, found in <Documentation/git-read-tree.txt>.
+rules, found in <Documentation/git read-tree.txt>.
 
 In the test, these paths are used:
         bozbar  - in H, stays in M, modified from bozbar to gnusto
@@ -23,7 +23,7 @@ In the test, these paths are used:
 . ./test-lib.sh
 
 read_tree_twoway () {
-    git-read-tree -m "$1" "$2" && git-ls-files --stage
+    git read-tree -m "$1" "$2" && git ls-files --stage
 }
 
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
@@ -37,7 +37,7 @@ compare_change () {
 }
 
 check_cache_at () {
-       clean_if_empty=`git-diff-files -- "$1"`
+       clean_if_empty=`git diff-files -- "$1"`
        case "$clean_if_empty" in
        '')  echo "$1: clean" ;;
        ?*)  echo "$1: dirty" ;;
@@ -68,24 +68,24 @@ test_expect_success \
      cat bozbar-old >bozbar &&
      echo rezrov >rezrov &&
      echo yomin >yomin &&
-     git-update-index --add nitfol bozbar rezrov &&
-     treeH=`git-write-tree` &&
+     git update-index --add nitfol bozbar rezrov &&
+     treeH=`git write-tree` &&
      echo treeH $treeH &&
-     git-ls-tree $treeH &&
+     git ls-tree $treeH &&
 
      cat bozbar-new >bozbar &&
-     git-update-index --add frotz bozbar --force-remove rezrov &&
-     git-ls-files --stage >M.out &&
-     treeM=`git-write-tree` &&
+     git update-index --add frotz bozbar --force-remove rezrov &&
+     git ls-files --stage >M.out &&
+     treeM=`git write-tree` &&
      echo treeM $treeM &&
-     git-ls-tree $treeM &&
-     git-diff-tree $treeH $treeM'
+     git ls-tree $treeM &&
+     git diff-tree $treeH $treeM'
 
 test_expect_success \
     '1, 2, 3 - no carry forward' \
     'rm -f .git/index &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >1-3.out &&
+     git ls-files --stage >1-3.out &&
      git diff M.out 1-3.out &&
      check_cache_at bozbar dirty &&
      check_cache_at frotz dirty &&
@@ -96,11 +96,11 @@ echo '+100644 X 0   yomin' >expected
 test_expect_success \
     '4 - carry forward local addition.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
-     git-update-index --add yomin &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
+     git update-index --add yomin &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >4.out || return 1
+     git ls-files --stage >4.out || return 1
      git diff M.out 4.out >4diff.out
      compare_change 4diff.out expected &&
      check_cache_at yomin clean'
@@ -108,13 +108,13 @@ test_expect_success \
 test_expect_success \
     '5 - carry forward local addition.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo yomin >yomin &&
-     git-update-index --add yomin &&
+     git update-index --add yomin &&
      echo yomin yomin >yomin &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >5.out || return 1
+     git ls-files --stage >5.out || return 1
      git diff M.out 5.out >5diff.out
      compare_change 5diff.out expected &&
      check_cache_at yomin dirty'
@@ -122,83 +122,83 @@ test_expect_success \
 test_expect_success \
     '6 - local addition already has the same.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
-     git-update-index --add frotz &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
+     git update-index --add frotz &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >6.out &&
+     git ls-files --stage >6.out &&
      git diff M.out 6.out &&
      check_cache_at frotz clean'
 
 test_expect_success \
     '7 - local addition already has the same.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo frotz >frotz &&
-     git-update-index --add frotz &&
+     git update-index --add frotz &&
      echo frotz frotz >frotz &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >7.out &&
+     git ls-files --stage >7.out &&
      git diff M.out 7.out &&
      check_cache_at frotz dirty'
 
 test_expect_success \
     '8 - conflicting addition.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo frotz frotz >frotz &&
-     git-update-index --add frotz &&
+     git update-index --add frotz &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '9 - conflicting addition.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo frotz frotz >frotz &&
-     git-update-index --add frotz &&
+     git update-index --add frotz &&
      echo frotz >frotz &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '10 - path removed.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo rezrov >rezrov &&
-     git-update-index --add rezrov &&
+     git update-index --add rezrov &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >10.out &&
+     git ls-files --stage >10.out &&
      git diff M.out 10.out'
 
 test_expect_success \
     '11 - dirty path removed.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo rezrov >rezrov &&
-     git-update-index --add rezrov &&
+     git update-index --add rezrov &&
      echo rezrov rezrov >rezrov &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '12 - unmatching local changes being removed.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo rezrov rezrov >rezrov &&
-     git-update-index --add rezrov &&
+     git update-index --add rezrov &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '13 - unmatching local changes being removed.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo rezrov rezrov >rezrov &&
-     git-update-index --add rezrov &&
+     git update-index --add rezrov &&
      echo rezrov >rezrov &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
@@ -210,12 +210,12 @@ EOF
 test_expect_success \
     '14 - unchanged in two heads.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo nitfol nitfol >nitfol &&
-     git-update-index --add nitfol &&
+     git update-index --add nitfol &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >14.out || return 1
+     git ls-files --stage >14.out || return 1
      git diff M.out 14.out >14diff.out
      compare_change 14diff.out expected &&
      check_cache_at nitfol clean'
@@ -223,13 +223,13 @@ test_expect_success \
 test_expect_success \
     '15 - unchanged in two heads.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo nitfol nitfol >nitfol &&
-     git-update-index --add nitfol &&
+     git update-index --add nitfol &&
      echo nitfol nitfol nitfol >nitfol &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >15.out || return 1
+     git ls-files --stage >15.out || return 1
      git diff M.out 15.out >15diff.out
      compare_change 15diff.out expected &&
      check_cache_at nitfol dirty'
@@ -237,66 +237,66 @@ test_expect_success \
 test_expect_success \
     '16 - conflicting local change.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo bozbar bozbar >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '17 - conflicting local change.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      echo bozbar bozbar >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      echo bozbar bozbar bozbar >bozbar &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '18 - local change already having a good result.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      cat bozbar-new >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >18.out &&
+     git ls-files --stage >18.out &&
      git diff M.out 18.out &&
      check_cache_at bozbar clean'
 
 test_expect_success \
     '19 - local change already having a good result, further modified.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      cat bozbar-new >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >19.out &&
+     git ls-files --stage >19.out &&
      git diff M.out 19.out &&
      check_cache_at bozbar dirty'
 
 test_expect_success \
     '20 - no local change, use new tree.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      cat bozbar-old >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      read_tree_twoway $treeH $treeM &&
-     git-ls-files --stage >20.out &&
+     git ls-files --stage >20.out &&
      git diff M.out 20.out &&
      check_cache_at bozbar dirty'
 
 test_expect_success \
     '21 - no local change, dirty cache.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      cat bozbar-old >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
@@ -304,10 +304,10 @@ test_expect_success \
 test_expect_success \
     '22 - local change cache updated.' \
     'rm -f .git/index &&
-     git-read-tree $treeH &&
-     git-checkout-index -u -f -q -a &&
+     git read-tree $treeH &&
+     git checkout-index -u -f -q -a &&
      sed -e "s/such as/SUCH AS/" bozbar-old >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      if read_tree_twoway $treeH $treeM; then false; else :; fi'
 
 # Also make sure we did not break DF vs DF/DF case.
@@ -315,28 +315,28 @@ test_expect_success \
     'DF vs DF/DF case setup.' \
     'rm -f .git/index &&
      echo DF >DF &&
-     git-update-index --add DF &&
-     treeDF=`git-write-tree` &&
+     git update-index --add DF &&
+     treeDF=`git write-tree` &&
      echo treeDF $treeDF &&
-     git-ls-tree $treeDF &&
+     git ls-tree $treeDF &&
 
      rm -f DF &&
      mkdir DF &&
      echo DF/DF >DF/DF &&
-     git-update-index --add --remove DF DF/DF &&
-     treeDFDF=`git-write-tree` &&
+     git update-index --add --remove DF DF/DF &&
+     treeDFDF=`git write-tree` &&
      echo treeDFDF $treeDFDF &&
-     git-ls-tree $treeDFDF &&
-     git-ls-files --stage >DFDF.out'
+     git ls-tree $treeDFDF &&
+     git ls-files --stage >DFDF.out'
 
 test_expect_success \
     'DF vs DF/DF case test.' \
     'rm -f .git/index &&
      rm -fr DF &&
      echo DF >DF &&
-     git-update-index --add DF &&
+     git update-index --add DF &&
      read_tree_twoway $treeDF $treeDFDF &&
-     git-ls-files --stage >DFDFcheck.out &&
+     git ls-files --stage >DFDFcheck.out &&
      git diff DFDF.out DFDFcheck.out &&
      check_cache_at DF/DF dirty &&
      :'
index 87fe993f59e2b3843ea2abcd92a2bab77a392997..42e5cf81813a7703ff2173dcc29e35ffdc68f552 100755 (executable)
@@ -20,7 +20,7 @@ compare_change () {
 }
 
 check_cache_at () {
-       clean_if_empty=`git-diff-files -- "$1"`
+       clean_if_empty=`git diff-files -- "$1"`
        case "$clean_if_empty" in
        '')  echo "$1: clean" ;;
        ?*)  echo "$1: dirty" ;;
@@ -39,26 +39,26 @@ test_expect_success \
      echo nitfol >nitfol &&
      echo bozbar >bozbar &&
      echo rezrov >rezrov &&
-     git-update-index --add nitfol bozbar rezrov &&
-     treeH=`git-write-tree` &&
+     git update-index --add nitfol bozbar rezrov &&
+     treeH=`git write-tree` &&
      echo treeH $treeH &&
-     git-ls-tree $treeH &&
+     git ls-tree $treeH &&
 
      echo gnusto >bozbar &&
-     git-update-index --add frotz bozbar --force-remove rezrov &&
-     git-ls-files --stage >M.out &&
-     treeM=`git-write-tree` &&
+     git update-index --add frotz bozbar --force-remove rezrov &&
+     git ls-files --stage >M.out &&
+     treeM=`git write-tree` &&
      echo treeM $treeM &&
-     git-ls-tree $treeM &&
+     git ls-tree $treeM &&
      sum bozbar frotz nitfol >M.sum &&
-     git-diff-tree $treeH $treeM'
+     git diff-tree $treeH $treeM'
 
 test_expect_success \
     '1, 2, 3 - no carry forward' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >1-3.out &&
+     git read-tree --reset -u $treeH &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >1-3.out &&
      cmp M.out 1-3.out &&
      sum bozbar frotz nitfol >actual3.sum &&
      cmp M.sum actual3.sum &&
@@ -69,12 +69,12 @@ test_expect_success \
 test_expect_success \
     '4 - carry forward local addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo "+100644 X 0 yomin" >expected &&
      echo yomin >yomin &&
-     git-update-index --add yomin &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >4.out || return 1
+     git update-index --add yomin &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >4.out || return 1
      diff -U0 M.out 4.out >4diff.out
      compare_change 4diff.out expected &&
      check_cache_at yomin clean &&
@@ -87,13 +87,13 @@ test_expect_success \
 test_expect_success \
     '5 - carry forward local addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
-     git-read-tree -m -u $treeH &&
+     git read-tree --reset -u $treeH &&
+     git read-tree -m -u $treeH &&
      echo yomin >yomin &&
-     git-update-index --add yomin &&
+     git update-index --add yomin &&
      echo yomin yomin >yomin &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >5.out || return 1
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >5.out || return 1
      diff -U0 M.out 5.out >5diff.out
      compare_change 5diff.out expected &&
      check_cache_at yomin dirty &&
@@ -107,11 +107,11 @@ test_expect_success \
 test_expect_success \
     '6 - local addition already has the same.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo frotz >frotz &&
-     git-update-index --add frotz &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >6.out &&
+     git update-index --add frotz &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >6.out &&
      diff -U0 M.out 6.out &&
      check_cache_at frotz clean &&
      sum bozbar frotz nitfol >actual3.sum &&
@@ -123,12 +123,12 @@ test_expect_success \
 test_expect_success \
     '7 - local addition already has the same.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo frotz >frotz &&
-     git-update-index --add frotz &&
+     git update-index --add frotz &&
      echo frotz frotz >frotz &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >7.out &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >7.out &&
      diff -U0 M.out 7.out &&
      check_cache_at frotz dirty &&
      sum bozbar frotz nitfol >actual7.sum &&
@@ -141,28 +141,28 @@ test_expect_success \
 test_expect_success \
     '8 - conflicting addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo frotz frotz >frotz &&
-     git-update-index --add frotz &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     git update-index --add frotz &&
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '9 - conflicting addition.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo frotz frotz >frotz &&
-     git-update-index --add frotz &&
+     git update-index --add frotz &&
      echo frotz >frotz &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '10 - path removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo rezrov >rezrov &&
-     git-update-index --add rezrov &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >10.out &&
+     git update-index --add rezrov &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >10.out &&
      cmp M.out 10.out &&
      sum bozbar frotz nitfol >actual10.sum &&
      cmp M.sum actual10.sum'
@@ -170,28 +170,28 @@ test_expect_success \
 test_expect_success \
     '11 - dirty path removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo rezrov >rezrov &&
-     git-update-index --add rezrov &&
+     git update-index --add rezrov &&
      echo rezrov rezrov >rezrov &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '12 - unmatching local changes being removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo rezrov rezrov >rezrov &&
-     git-update-index --add rezrov &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     git update-index --add rezrov &&
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '13 - unmatching local changes being removed.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo rezrov rezrov >rezrov &&
-     git-update-index --add rezrov &&
+     git update-index --add rezrov &&
      echo rezrov >rezrov &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 cat >expected <<EOF
 -100644 X 0    nitfol
@@ -201,11 +201,11 @@ EOF
 test_expect_success \
     '14 - unchanged in two heads.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo nitfol nitfol >nitfol &&
-     git-update-index --add nitfol &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >14.out || return 1
+     git update-index --add nitfol &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >14.out || return 1
      diff -U0 M.out 14.out >14diff.out
      compare_change 14diff.out expected &&
      sum bozbar frotz >actual14.sum &&
@@ -221,12 +221,12 @@ test_expect_success \
 test_expect_success \
     '15 - unchanged in two heads.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo nitfol nitfol >nitfol &&
-     git-update-index --add nitfol &&
+     git update-index --add nitfol &&
      echo nitfol nitfol nitfol >nitfol &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >15.out || return 1
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >15.out || return 1
      diff -U0 M.out 15.out >15diff.out
      compare_change 15diff.out expected &&
      check_cache_at nitfol dirty &&
@@ -242,28 +242,28 @@ test_expect_success \
 test_expect_success \
     '16 - conflicting local change.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo bozbar bozbar >bozbar &&
-     git-update-index --add bozbar &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     git update-index --add bozbar &&
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '17 - conflicting local change.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo bozbar bozbar >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      echo bozbar bozbar bozbar >bozbar &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 test_expect_success \
     '18 - local change already having a good result.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo gnusto >bozbar &&
-     git-update-index --add bozbar &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >18.out &&
+     git update-index --add bozbar &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >18.out &&
      diff -U0 M.out 18.out &&
      check_cache_at bozbar clean &&
      sum bozbar frotz nitfol >actual18.sum &&
@@ -272,12 +272,12 @@ test_expect_success \
 test_expect_success \
     '19 - local change already having a good result, further modified.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo gnusto >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >19.out &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >19.out &&
      diff -U0 M.out 19.out &&
      check_cache_at bozbar dirty &&
      sum frotz nitfol >actual19.sum &&
@@ -292,11 +292,11 @@ test_expect_success \
 test_expect_success \
     '20 - no local change, use new tree.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo bozbar >bozbar &&
-     git-update-index --add bozbar &&
-     git-read-tree -m -u $treeH $treeM &&
-     git-ls-files --stage >20.out &&
+     git update-index --add bozbar &&
+     git read-tree -m -u $treeH $treeM &&
+     git ls-files --stage >20.out &&
      diff -U0 M.out 20.out &&
      check_cache_at bozbar clean &&
      sum bozbar frotz nitfol >actual20.sum &&
@@ -305,39 +305,39 @@ test_expect_success \
 test_expect_success \
     '21 - no local change, dirty cache.' \
     'rm -f .git/index nitfol bozbar rezrov frotz &&
-     git-read-tree --reset -u $treeH &&
+     git read-tree --reset -u $treeH &&
      echo bozbar >bozbar &&
-     git-update-index --add bozbar &&
+     git update-index --add bozbar &&
      echo gnusto gnusto >bozbar &&
-     if git-read-tree -m -u $treeH $treeM; then false; else :; fi'
+     if git read-tree -m -u $treeH $treeM; then false; else :; fi'
 
 # Also make sure we did not break DF vs DF/DF case.
 test_expect_success \
     'DF vs DF/DF case setup.' \
     'rm -f .git/index
      echo DF >DF &&
-     git-update-index --add DF &&
-     treeDF=`git-write-tree` &&
+     git update-index --add DF &&
+     treeDF=`git write-tree` &&
      echo treeDF $treeDF &&
-     git-ls-tree $treeDF &&
+     git ls-tree $treeDF &&
 
      rm -f DF &&
      mkdir DF &&
      echo DF/DF >DF/DF &&
-     git-update-index --add --remove DF DF/DF &&
-     treeDFDF=`git-write-tree` &&
+     git update-index --add --remove DF DF/DF &&
+     treeDFDF=`git write-tree` &&
      echo treeDFDF $treeDFDF &&
-     git-ls-tree $treeDFDF &&
-     git-ls-files --stage >DFDF.out'
+     git ls-tree $treeDFDF &&
+     git ls-files --stage >DFDF.out'
 
 test_expect_success \
     'DF vs DF/DF case test.' \
     'rm -f .git/index &&
      rm -fr DF &&
      echo DF >DF &&
-     git-update-index --add DF &&
-     git-read-tree -m -u $treeDF $treeDFDF &&
-     git-ls-files --stage >DFDFcheck.out &&
+     git update-index --add DF &&
+     git read-tree -m -u $treeDF $treeDFDF &&
+     git ls-files --stage >DFDFcheck.out &&
      diff -U0 DFDF.out DFDFcheck.out &&
      check_cache_at DF/DF clean'
 
index 48ab117d755ca606a09d848af9747e24ea13a26f..8c6d67edda1468ba0f1c7afc6d13ea3a7bbe0a90 100755 (executable)
@@ -3,15 +3,15 @@
 # Copyright (c) 2006 Junio C Hamano
 #
 
-test_description='git-read-tree --prefix test.
+test_description='git read-tree --prefix test.
 '
 
 . ./test-lib.sh
 
 test_expect_success setup '
        echo hello >one &&
-       git-update-index --add one &&
-       tree=`git-write-tree` &&
+       git update-index --add one &&
+       tree=`git write-tree` &&
        echo tree is $tree
 '
 
@@ -19,8 +19,8 @@ echo 'one
 two/one' >expect
 
 test_expect_success 'read-tree --prefix' '
-       git-read-tree --prefix=two/ $tree &&
-       git-ls-files >actual &&
+       git read-tree --prefix=two/ $tree &&
+       git ls-files >actual &&
        cmp expect actual
 '
 
index c11420a8b6ce9104f1c2ca3b4b2a23aef4f265ff..d609a551ae3b8c75714f6cf4e9cefe4f6af61c5b 100755 (executable)
@@ -84,7 +84,7 @@ test_expect_success 'three-way not complaining on an untracked path in both' '
        echo >file2 file two is untracked on the master side &&
        echo >subdir/file2 file two is untracked on the master side &&
 
-       git-read-tree -m -u branch-point master side
+       git read-tree -m -u branch-point master side
 '
 
 test_expect_success 'three-way not clobbering a working tree file' '
index 1e8f9e59dfe7e63177dea0a2e4b5c018a4756994..b9cef3422c3ba1392b32ec66f72dc4b7c34f4a43 100755 (executable)
@@ -22,19 +22,19 @@ LF='
 
 test_expect_success 'update-index and ls-files' '
        cd $HERE &&
-       git-update-index --add one &&
-       case "`git-ls-files`" in
+       git update-index --add one &&
+       case "`git ls-files`" in
        one) echo ok one ;;
        *) echo bad one; exit 1 ;;
        esac &&
        cd dir &&
-       git-update-index --add two &&
-       case "`git-ls-files`" in
+       git update-index --add two &&
+       case "`git ls-files`" in
        two) echo ok two ;;
        *) echo bad two; exit 1 ;;
        esac &&
        cd .. &&
-       case "`git-ls-files`" in
+       case "`git ls-files`" in
        dir/two"$LF"one) echo ok both ;;
        *) echo bad; exit 1 ;;
        esac
@@ -42,13 +42,13 @@ test_expect_success 'update-index and ls-files' '
 
 test_expect_success 'cat-file' '
        cd $HERE &&
-       two=`git-ls-files -s dir/two` &&
+       two=`git ls-files -s dir/two` &&
        two=`expr "$two" : "[0-7]* \\([0-9a-f]*\\)"` &&
        echo "$two" &&
-       git-cat-file -p "$two" >actual &&
+       git cat-file -p "$two" >actual &&
        cmp dir/two actual &&
        cd dir &&
-       git-cat-file -p "$two" >actual &&
+       git cat-file -p "$two" >actual &&
        cmp two actual
 '
 rm -f actual dir/actual
@@ -57,17 +57,17 @@ test_expect_success 'diff-files' '
        cd $HERE &&
        echo a >>one &&
        echo d >>dir/two &&
-       case "`git-diff-files --name-only`" in
+       case "`git diff-files --name-only`" in
        dir/two"$LF"one) echo ok top ;;
        *) echo bad top; exit 1 ;;
        esac &&
        # diff should not omit leading paths
        cd dir &&
-       case "`git-diff-files --name-only`" in
+       case "`git diff-files --name-only`" in
        dir/two"$LF"one) echo ok subdir ;;
        *) echo bad subdir; exit 1 ;;
        esac &&
-       case "`git-diff-files --name-only .`" in
+       case "`git diff-files --name-only .`" in
        dir/two) echo ok subdir limited ;;
        *) echo bad subdir limited; exit 1 ;;
        esac
@@ -75,33 +75,33 @@ test_expect_success 'diff-files' '
 
 test_expect_success 'write-tree' '
        cd $HERE &&
-       top=`git-write-tree` &&
+       top=`git write-tree` &&
        echo $top &&
        cd dir &&
-       sub=`git-write-tree` &&
+       sub=`git write-tree` &&
        echo $sub &&
        test "z$top" = "z$sub"
 '
 
 test_expect_success 'checkout-index' '
        cd $HERE &&
-       git-checkout-index -f -u one &&
+       git checkout-index -f -u one &&
        cmp one original.one &&
        cd dir &&
-       git-checkout-index -f -u two &&
+       git checkout-index -f -u two &&
        cmp two ../original.two
 '
 
 test_expect_success 'read-tree' '
        cd $HERE &&
        rm -f one dir/two &&
-       tree=`git-write-tree` &&
-       git-read-tree --reset -u "$tree" &&
+       tree=`git write-tree` &&
+       git read-tree --reset -u "$tree" &&
        cmp one original.one &&
        cmp dir/two original.two &&
        cd dir &&
        rm -f two &&
-       git-read-tree --reset -u "$tree" &&
+       git read-tree --reset -u "$tree" &&
        cmp two ../original.two &&
        cmp ../one ../original.one
 '
index 19a0ed4d20409a79c5615004ceb63a969f0414de..7f7fc36734d96de96801c59af41024db97dc614d 100755 (executable)
@@ -3,9 +3,9 @@
 # Copyright (C) 2005 Rene Scharfe
 #
 
-test_description='git-commit-tree options test
+test_description='git commit-tree options test
 
-This test checks that git-commit-tree can create a specific commit
+This test checks that git commit-tree can create a specific commit
 object by defining all environment variables that it understands.
 '
 
@@ -21,7 +21,7 @@ EOF
 
 test_expect_success \
     'test preparation: write empty tree' \
-    'git-write-tree >treeid'
+    'git write-tree >treeid'
 
 test_expect_success \
     'construct commit' \
@@ -32,11 +32,11 @@ test_expect_success \
      GIT_COMMITTER_NAME="Committer Name" \
      GIT_COMMITTER_EMAIL="committer@email" \
      GIT_COMMITTER_DATE="2005-05-26 23:30" \
-     TZ=GMT git-commit-tree `cat treeid` >commitid 2>/dev/null'
+     TZ=GMT git commit-tree `cat treeid` >commitid 2>/dev/null'
 
 test_expect_success \
     'read commit' \
-    'git-cat-file commit `cat commitid` >commit'
+    'git cat-file commit `cat commitid` >commit'
 
 test_expect_success \
     'compare commit' \
index ca2c30f7aff5beda97ea598e272750c23e34296e..991d3c5e9c5c8dc9e59b0105010f1b77d4bf3a3f 100755 (executable)
@@ -10,11 +10,11 @@ test_description='A simple turial in the form of a test case'
 echo "Hello World" > hello
 echo "Silly example" > example
 
-git-update-index --add hello example
+git update-index --add hello example
 
-test_expect_success 'blob' "test blob = \"$(git-cat-file -t 557db03)\""
+test_expect_success 'blob' "test blob = \"$(git cat-file -t 557db03)\""
 
-test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git-cat-file blob 557db03)\""
+test_expect_success 'blob 557db03' "test \"Hello World\" = \"$(git cat-file blob 557db03)\""
 
 echo "It's a new day for git" >>hello
 cat > diff.expect << EOF
@@ -26,25 +26,25 @@ index 557db03..263414f 100644
  Hello World
 +It's a new day for git
 EOF
-git-diff-files -p > diff.output
-test_expect_success 'git-diff-files -p' 'cmp diff.expect diff.output'
+git diff-files -p > diff.output
+test_expect_success 'git diff-files -p' 'cmp diff.expect diff.output'
 git diff > diff.output
 test_expect_success 'git diff' 'cmp diff.expect diff.output'
 
-tree=$(git-write-tree 2>/dev/null)
+tree=$(git write-tree 2>/dev/null)
 
 test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tree"
 
-output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
+output="$(echo "Initial commit" | git commit-tree $(git write-tree) 2>&1 > .git/refs/heads/master)"
 
-git-diff-index -p HEAD > diff.output
-test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
+git diff-index -p HEAD > diff.output
+test_expect_success 'git diff-index -p HEAD' 'cmp diff.expect diff.output'
 
 git diff HEAD > diff.output
 test_expect_success 'git diff HEAD' 'cmp diff.expect diff.output'
 
 #rm hello
-#test_expect_success 'git-read-tree --reset HEAD' "git-read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git-update-index --refresh)\""
+#test_expect_success 'git read-tree --reset HEAD' "git read-tree --reset HEAD ; test \"hello: needs update\" = \"$(git update-index --refresh)\""
 
 cat > whatchanged.expect << EOF
 commit VARIABLE
@@ -69,11 +69,11 @@ index 0000000..557db03
 +Hello World
 EOF
 
-git-whatchanged -p --root | \
+git whatchanged -p --root | \
        sed -e "1s/^\(.\{7\}\).\{40\}/\1VARIABLE/" \
                -e "2,3s/^\(.\{8\}\).*$/\1VARIABLE/" \
 > whatchanged.output
-test_expect_success 'git-whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
+test_expect_success 'git whatchanged -p --root' 'cmp whatchanged.expect whatchanged.output'
 
 git tag my-first-tag
 test_expect_success 'git tag my-first-tag' 'cmp .git/refs/heads/master .git/refs/tags/my-first-tag'
@@ -159,4 +159,3 @@ test_expect_success 'git prune-packed' 'git prune-packed'
 test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'
 
 test_done
-
index f1a78b19ac6e426f4475b66cfe23b89e41c7fafa..1d2bf2c06087546503b7272a9547ab8232b017d4 100755 (executable)
@@ -3,13 +3,13 @@
 # Copyright (c) 2005 Johannes Schindelin
 #
 
-test_description='Test git-config in different settings'
+test_description='Test git config in different settings'
 
 . ./test-lib.sh
 
 test -f .git/config && rm .git/config
 
-git-config core.penguin "little blue"
+git config core.penguin "little blue"
 
 cat > expect << EOF
 [core]
@@ -18,7 +18,7 @@ EOF
 
 test_expect_success 'initial' 'cmp .git/config expect'
 
-git-config Core.Movie BadPhysics
+git config Core.Movie BadPhysics
 
 cat > expect << EOF
 [core]
@@ -28,7 +28,7 @@ EOF
 
 test_expect_success 'mixed case' 'cmp .git/config expect'
 
-git-config Cores.WhatEver Second
+git config Cores.WhatEver Second
 
 cat > expect << EOF
 [core]
@@ -40,7 +40,7 @@ EOF
 
 test_expect_success 'similar section' 'cmp .git/config expect'
 
-git-config CORE.UPPERCASE true
+git config CORE.UPPERCASE true
 
 cat > expect << EOF
 [core]
@@ -54,10 +54,10 @@ EOF
 test_expect_success 'similar section' 'cmp .git/config expect'
 
 test_expect_success 'replace with non-match' \
-       'git-config core.penguin kingpin !blue'
+       'git config core.penguin kingpin !blue'
 
 test_expect_success 'replace with non-match (actually matching)' \
-       'git-config core.penguin "very blue" !kingpin'
+       'git config core.penguin "very blue" !kingpin'
 
 cat > expect << EOF
 [core]
@@ -86,7 +86,7 @@ EOF
 cp .git/config .git/config2
 
 test_expect_success 'multiple unset' \
-       'git-config --unset-all beta.haha'
+       'git config --unset-all beta.haha'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -102,7 +102,7 @@ test_expect_success 'multiple unset is correct' 'cmp .git/config expect'
 mv .git/config2 .git/config
 
 test_expect_success '--replace-all' \
-       'git-config --replace-all beta.haha gamma'
+       'git config --replace-all beta.haha gamma'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -116,7 +116,7 @@ EOF
 
 test_expect_success 'all replaced' 'cmp .git/config expect'
 
-git-config beta.haha alpha
+git config beta.haha alpha
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -130,7 +130,7 @@ EOF
 
 test_expect_success 'really mean test' 'cmp .git/config expect'
 
-git-config nextsection.nonewline wow
+git config nextsection.nonewline wow
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -145,8 +145,8 @@ EOF
 
 test_expect_success 'really really mean test' 'cmp .git/config expect'
 
-test_expect_success 'get value' 'test alpha = $(git-config beta.haha)'
-git-config --unset beta.haha
+test_expect_success 'get value' 'test alpha = $(git config beta.haha)'
+git config --unset beta.haha
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -160,7 +160,7 @@ EOF
 
 test_expect_success 'unset' 'cmp .git/config expect'
 
-git-config nextsection.NoNewLine "wow2 for me" "for me$"
+git config nextsection.NoNewLine "wow2 for me" "for me$"
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -176,18 +176,18 @@ EOF
 test_expect_success 'multivar' 'cmp .git/config expect'
 
 test_expect_success 'non-match' \
-       'git-config --get nextsection.nonewline !for'
+       'git config --get nextsection.nonewline !for'
 
 test_expect_success 'non-match value' \
-       'test wow = $(git-config --get nextsection.nonewline !for)'
+       'test wow = $(git config --get nextsection.nonewline !for)'
 
 test_expect_failure 'ambiguous get' \
-       'git-config --get nextsection.nonewline'
+       'git config --get nextsection.nonewline'
 
 test_expect_success 'get multivar' \
-       'git-config --get-all nextsection.nonewline'
+       'git config --get-all nextsection.nonewline'
 
-git-config nextsection.nonewline "wow3" "wow$"
+git config nextsection.nonewline "wow3" "wow$"
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -202,15 +202,15 @@ EOF
 
 test_expect_success 'multivar replace' 'cmp .git/config expect'
 
-test_expect_failure 'ambiguous value' 'git-config nextsection.nonewline'
+test_expect_failure 'ambiguous value' 'git config nextsection.nonewline'
 
 test_expect_failure 'ambiguous unset' \
-       'git-config --unset nextsection.nonewline'
+       'git config --unset nextsection.nonewline'
 
 test_expect_failure 'invalid unset' \
-       'git-config --unset somesection.nonewline'
+       'git config --unset somesection.nonewline'
 
-git-config --unset nextsection.nonewline "wow3$"
+git config --unset nextsection.nonewline "wow3$"
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -224,12 +224,12 @@ EOF
 
 test_expect_success 'multivar unset' 'cmp .git/config expect'
 
-test_expect_failure 'invalid key' 'git-config inval.2key blabla'
+test_expect_failure 'invalid key' 'git config inval.2key blabla'
 
-test_expect_success 'correct key' 'git-config 123456.a123 987'
+test_expect_success 'correct key' 'git config 123456.a123 987'
 
 test_expect_success 'hierarchical section' \
-       'git-config Version.1.2.3eX.Alpha beta'
+       'git config Version.1.2.3eX.Alpha beta'
 
 cat > expect << EOF
 [beta] ; silly comment # another comment
@@ -255,7 +255,7 @@ version.1.2.3eX.alpha=beta
 EOF
 
 test_expect_success 'working --list' \
-       'git-config --list > output && cmp output expect'
+       'git config --list > output && cmp output expect'
 
 cat > expect << EOF
 beta.noindent sillyValue
@@ -263,9 +263,9 @@ nextsection.nonewline wow2 for me
 EOF
 
 test_expect_success '--get-regexp' \
-       'git-config --get-regexp in > output && cmp output expect'
+       'git config --get-regexp in > output && cmp output expect'
 
-git-config --add nextsection.nonewline "wow4 for you"
+git config --add nextsection.nonewline "wow4 for you"
 
 cat > expect << EOF
 wow2 for me
@@ -273,7 +273,7 @@ wow4 for you
 EOF
 
 test_expect_success '--add' \
-       'git-config --get-all nextsection.nonewline > output && cmp output expect'
+       'git config --get-all nextsection.nonewline > output && cmp output expect'
 
 cat > .git/config << EOF
 [novalue]
@@ -281,15 +281,15 @@ cat > .git/config << EOF
 EOF
 
 test_expect_success 'get variable with no value' \
-       'git-config --get novalue.variable ^$'
+       'git config --get novalue.variable ^$'
 
 echo novalue.variable > expect
 
 test_expect_success 'get-regexp variable with no value' \
-       'git-config --get-regexp novalue > output &&
+       'git config --get-regexp novalue > output &&
         cmp output expect'
 
-git-config > output 2>&1
+git config > output 2>&1
 
 test_expect_success 'no arguments, but no crash' \
        "test $? = 129 && grep usage output"
@@ -299,7 +299,7 @@ cat > .git/config << EOF
        c = d
 EOF
 
-git-config a.x y
+git config a.x y
 
 cat > expect << EOF
 [a.b]
@@ -310,8 +310,8 @@ EOF
 
 test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
 
-git-config b.x y
-git-config a.b c
+git config b.x y
+git config a.b c
 
 cat > expect << EOF
 [a.b]
@@ -325,6 +325,9 @@ 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'
+
 cat > other-config << EOF
 [ein]
        bahn = strasse
@@ -334,11 +337,14 @@ cat > expect << EOF
 ein.bahn=strasse
 EOF
 
-GIT_CONFIG=other-config git-config -l > output
+GIT_CONFIG=other-config git config -l > output
 
 test_expect_success 'alternative GIT_CONFIG' 'cmp output expect'
 
-GIT_CONFIG=other-config git-config anwohner.park ausweis
+test_expect_success 'alternative GIT_CONFIG (--file)' \
+       'git config --file other-config -l > output && cmp output expect'
+
+GIT_CONFIG=other-config git config anwohner.park ausweis
 
 cat > expect << EOF
 [ein]
@@ -361,7 +367,7 @@ weird
 EOF
 
 test_expect_success "rename section" \
-       "git-config --rename-section branch.eins branch.zwei"
+       "git config --rename-section branch.eins branch.zwei"
 
 cat > expect << EOF
 # Hallo
@@ -377,12 +383,12 @@ EOF
 test_expect_success "rename succeeded" "git diff expect .git/config"
 
 test_expect_failure "rename non-existing section" \
-       'git-config --rename-section branch."world domination" branch.drei'
+       'git config --rename-section branch."world domination" branch.drei'
 
 test_expect_success "rename succeeded" "git diff expect .git/config"
 
 test_expect_success "rename another section" \
-       'git-config --rename-section branch."1 234 blabl/a" branch.drei'
+       'git config --rename-section branch."1 234 blabl/a" branch.drei'
 
 cat > expect << EOF
 # Hallo
@@ -425,20 +431,20 @@ EOF
 
 test_expect_success 'section ending' '
 
-       git-config gitcvs.enabled true &&
-       git-config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
-       git-config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
+       git config gitcvs.enabled true &&
+       git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
+       git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
        cmp .git/config expect
 
 '
 
 test_expect_success numbers '
 
-       git-config kilo.gram 1k &&
-       git-config mega.ton 1m &&
-       k=$(git-config --int --get kilo.gram) &&
+       git config kilo.gram 1k &&
+       git config mega.ton 1m &&
+       k=$(git config --int --get kilo.gram) &&
        test z1024 = "z$k" &&
-       m=$(git-config --int --get mega.ton) &&
+       m=$(git config --int --get mega.ton) &&
        test z1048576 = "z$m"
 '
 
@@ -455,33 +461,79 @@ EOF
 
 test_expect_success bool '
 
-       git-config bool.true1 01 &&
-       git-config bool.true2 -1 &&
-       git-config bool.true3 YeS &&
-       git-config bool.true4 true &&
-       git-config bool.false1 000 &&
-       git-config bool.false2 "" &&
-       git-config bool.false3 nO &&
-       git-config bool.false4 FALSE &&
+       git config bool.true1 01 &&
+       git config bool.true2 -1 &&
+       git config bool.true3 YeS &&
+       git config bool.true4 true &&
+       git config bool.false1 000 &&
+       git config bool.false2 "" &&
+       git config bool.false3 nO &&
+       git config bool.false4 FALSE &&
        rm -f result &&
        for i in 1 2 3 4
        do
-           git-config --bool --get bool.true$i >>result
-           git-config --bool --get bool.false$i >>result
+           git config --bool --get bool.true$i >>result
+           git config --bool --get bool.false$i >>result
         done &&
        cmp expect result'
 
-test_expect_failure 'invalid bool' '
+test_expect_failure 'invalid bool (--get)' '
+
+       git config bool.nobool foobar &&
+       git config --bool --get bool.nobool'
+
+test_expect_failure 'invalid bool (set)' '
+
+       git config --bool bool.nobool foobar'
+
+rm .git/config
+
+cat > expect <<\EOF
+[bool]
+       true1 = true
+       true2 = true
+       true3 = true
+       true4 = true
+       false1 = false
+       false2 = false
+       false3 = false
+       false4 = false
+EOF
+
+test_expect_success 'set --bool' '
+
+       git config --bool bool.true1 01 &&
+       git config --bool bool.true2 -1 &&
+       git config --bool bool.true3 YeS &&
+       git config --bool bool.true4 true &&
+       git config --bool bool.false1 000 &&
+       git config --bool bool.false2 "" &&
+       git config --bool bool.false3 nO &&
+       git config --bool bool.false4 FALSE &&
+       cmp expect .git/config'
+
+rm .git/config
+
+cat > expect <<\EOF
+[int]
+       val1 = 1
+       val2 = -1
+       val3 = 5242880
+EOF
 
-       git-config bool.nobool foobar &&
-       git-config --bool --get bool.nobool'
+test_expect_success 'set --int' '
+
+       git config --int int.val1 01 &&
+       git config --int int.val2 -1 &&
+       git config --int int.val3 5m &&
+       cmp expect .git/config'
 
 rm .git/config
 
-git-config quote.leading " test"
-git-config quote.ending "test "
-git-config quote.semicolon "test;test"
-git-config quote.hash "test#test"
+git config quote.leading " test"
+git config quote.ending "test "
+git config quote.semicolon "test;test"
+git config quote.hash "test#test"
 
 cat > expect << EOF
 [quote]
@@ -519,5 +571,49 @@ git config --list > result
 
 test_expect_success 'value continued on next line' 'cmp result expect'
 
-test_done
+cat > .git/config <<\EOF
+[section "sub=section"]
+       val1 = foo=bar
+       val2 = foo\nbar
+       val3 = \n\n
+       val4 =
+       val5
+EOF
+
+cat > expect <<\EOF
+section.sub=section.val1
+foo=barQsection.sub=section.val2
+foo
+barQsection.sub=section.val3
+
+
+Qsection.sub=section.val4
+Qsection.sub=section.val5Q
+EOF
+
+git config --null --list | tr '[\000]' 'Q' > result
+echo >>result
 
+test_expect_success '--null --list' 'cmp result expect'
+
+git config --null --get-regexp 'val[0-9]' | tr '[\000]' 'Q' > result
+echo >>result
+
+test_expect_success '--null --get-regexp' 'cmp result expect'
+
+test_expect_success 'symlinked configuration' '
+
+       ln -s notyet myconfig &&
+       GIT_CONFIG=myconfig git config test.frotz nitfol &&
+       test -h myconfig &&
+       test -f notyet &&
+       test "z$(GIT_CONFIG=notyet git config test.frotz)" = znitfol &&
+       GIT_CONFIG=myconfig git config test.xyzzy rezrov &&
+       test -h myconfig &&
+       test -f notyet &&
+       test "z$(GIT_CONFIG=notyet git config test.frotz)" = znitfol &&
+       test "z$(GIT_CONFIG=notyet git config test.xyzzy)" = zrezrov
+
+'
+
+test_done
diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh
new file mode 100755 (executable)
index 0000000..bb5f302
--- /dev/null
@@ -0,0 +1,27 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes Schindelin
+#
+
+test_description='Test shared repository initialization'
+
+. ./test-lib.sh
+
+test_expect_success 'shared=all' '
+       mkdir sub &&
+       cd sub &&
+       git init --shared=all &&
+       test 2 = $(git config core.sharedrepository)
+'
+
+test_expect_success 'update-server-info honors core.sharedRepository' '
+       : > a1 &&
+       git add a1 &&
+       test_tick &&
+       git commit -m a1 &&
+       umask 0277 &&
+       git update-server-info &&
+       test 444 = $(stat -c %a .git/info/refs)
+'
+
+test_done
index d0aba2c2ae1fbd5f5282de1a0ee7ed8b87b80faa..c4c0dfaab1adf7866ca82d998b6d7a040a011935 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Shawn Pearce
 #
 
-test_description='Test git-update-ref and basic ref logging'
+test_description='Test git update-ref and basic ref logging'
 . ./test-lib.sh
 
 Z=0000000000000000000000000000000000000000
@@ -19,34 +19,34 @@ n=$n_dir/fixes
 
 test_expect_success \
        "create $m" \
-       "git-update-ref $m $A &&
+       "git update-ref $m $A &&
         test $A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "create $m" \
-       "git-update-ref $m $B $A &&
+       "git update-ref $m $B $A &&
         test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
 test_expect_success \
        "fail to create $n" \
        "touch .git/$n_dir
-        git-update-ref $n $A >out 2>err"'
+        git update-ref $n $A >out 2>err"'
         test $? != 0'
 rm -f .git/$n_dir out err
 
 test_expect_success \
        "create $m (by HEAD)" \
-       "git-update-ref HEAD $A &&
+       "git update-ref HEAD $A &&
         test $A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "create $m (by HEAD)" \
-       "git-update-ref HEAD $B $A &&
+       "git update-ref HEAD $B $A &&
         test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
 test_expect_failure \
        '(not) create HEAD with old sha1' \
-       "git-update-ref HEAD $A $B"
+       "git update-ref HEAD $A $B"
 test_expect_failure \
        "(not) prior created .git/$m" \
        "test -f .git/$m"
@@ -54,10 +54,10 @@ rm -f .git/$m
 
 test_expect_success \
        "create HEAD" \
-       "git-update-ref HEAD $A"
+       "git update-ref HEAD $A"
 test_expect_failure \
        '(not) change HEAD with wrong SHA1' \
-       "git-update-ref HEAD $B $Z"
+       "git update-ref HEAD $B $Z"
 test_expect_failure \
        "(not) changed .git/$m" \
        "test $B"' = $(cat .git/'"$m"')'
@@ -68,17 +68,17 @@ rm -f .git/$m
 test_expect_success \
        "create $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
-        git-update-ref HEAD '"$A"' -m "Initial Creation" &&
+        git update-ref HEAD '"$A"' -m "Initial Creation" &&
         test '"$A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "update $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:31" \
-        git-update-ref HEAD'" $B $A "'-m "Switch" &&
+        git update-ref HEAD'" $B $A "'-m "Switch" &&
         test '"$B"' = $(cat .git/'"$m"')'
 test_expect_success \
        "set $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:41" \
-        git-update-ref HEAD'" $A &&
+        git update-ref HEAD'" $A &&
         test $A"' = $(cat .git/'"$m"')'
 
 cat >expect <<EOF
@@ -93,23 +93,23 @@ rm -rf .git/$m .git/logs expect
 
 test_expect_success \
        'enable core.logAllRefUpdates' \
-       'git-config core.logAllRefUpdates true &&
-        test true = $(git-config --bool --get core.logAllRefUpdates)'
+       'git config core.logAllRefUpdates true &&
+        test true = $(git config --bool --get core.logAllRefUpdates)'
 
 test_expect_success \
        "create $m (logged by config)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:32" \
-        git-update-ref HEAD'" $A "'-m "Initial Creation" &&
+        git update-ref HEAD'" $A "'-m "Initial Creation" &&
         test '"$A"' = $(cat .git/'"$m"')'
 test_expect_success \
        "update $m (logged by config)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:33" \
-        git-update-ref HEAD'" $B $A "'-m "Switch" &&
+        git update-ref HEAD'" $B $A "'-m "Switch" &&
         test '"$B"' = $(cat .git/'"$m"')'
 test_expect_success \
        "set $m (logged by config)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:43" \
-        git-update-ref HEAD '"$A &&
+        git update-ref HEAD '"$A &&
         test $A"' = $(cat .git/'"$m"')'
 
 cat >expect <<EOF
@@ -122,7 +122,7 @@ test_expect_success \
        'diff expect .git/logs/$m'
 rm -f .git/$m .git/logs/$m expect
 
-git-update-ref $m $D
+git update-ref $m $D
 cat >.git/logs/$m <<EOF
 $C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500
 $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500
@@ -136,49 +136,49 @@ ld="Thu, 26 May 2005 18:43:00 -0500"
 test_expect_success \
        'Query "master@{May 25 2005}" (before history)' \
        'rm -f o e
-        git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
+        git rev-parse --verify "master@{May 25 2005}" >o 2>e &&
         test '"$C"' = $(cat o) &&
         test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
 test_expect_success \
        "Query master@{2005-05-25} (before history)" \
        'rm -f o e
-        git-rev-parse --verify master@{2005-05-25} >o 2>e &&
+        git rev-parse --verify master@{2005-05-25} >o 2>e &&
         test '"$C"' = $(cat o) &&
         echo test "warning: Log for '\'master\'' only goes back to $ed." = "$(cat e)"'
 test_expect_success \
        'Query "master@{May 26 2005 23:31:59}" (1 second before history)' \
        'rm -f o e
-        git-rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
+        git rev-parse --verify "master@{May 26 2005 23:31:59}" >o 2>e &&
         test '"$C"' = $(cat o) &&
         test "warning: Log for '\''master'\'' only goes back to $ed." = "$(cat e)"'
 test_expect_success \
        'Query "master@{May 26 2005 23:32:00}" (exactly history start)' \
        'rm -f o e
-        git-rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
+        git rev-parse --verify "master@{May 26 2005 23:32:00}" >o 2>e &&
         test '"$A"' = $(cat o) &&
         test "" = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-26 23:33:01}" (middle of history with gap)' \
        'rm -f o e
-        git-rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
+        git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e &&
         test '"$B"' = $(cat o) &&
         test "warning: Log .git/logs/'"$m has gap after $gd"'." = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-26 23:38:00}" (middle of history)' \
        'rm -f o e
-        git-rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
+        git rev-parse --verify "master@{2005-05-26 23:38:00}" >o 2>e &&
         test '"$Z"' = $(cat o) &&
         test "" = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-26 23:43:00}" (exact end of history)' \
        'rm -f o e
-        git-rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
+        git rev-parse --verify "master@{2005-05-26 23:43:00}" >o 2>e &&
         test '"$E"' = $(cat o) &&
         test "" = "$(cat e)"'
 test_expect_success \
        'Query "master@{2005-05-28}" (past end of history)' \
        'rm -f o e
-        git-rev-parse --verify "master@{2005-05-28}" >o 2>e &&
+        git rev-parse --verify "master@{2005-05-28}" >o 2>e &&
         test '"$D"' = $(cat o) &&
         test "warning: Log .git/logs/'"$m unexpectedly ended on $ld"'." = "$(cat e)"'
 
@@ -188,26 +188,26 @@ rm -f .git/$m .git/logs/$m expect
 test_expect_success \
     'creating initial files' \
     'echo TEST >F &&
-     git-add F &&
+     git add F &&
         GIT_AUTHOR_DATE="2005-05-26 23:30" \
         GIT_COMMITTER_DATE="2005-05-26 23:30" git-commit -m add -a &&
-        h_TEST=$(git-rev-parse --verify HEAD)
+        h_TEST=$(git rev-parse --verify HEAD)
         echo The other day this did not work. >M &&
         echo And then Bob told me how to fix it. >>M &&
         echo OTHER >F &&
         GIT_AUTHOR_DATE="2005-05-26 23:41" \
         GIT_COMMITTER_DATE="2005-05-26 23:41" git-commit -F M -a &&
-        h_OTHER=$(git-rev-parse --verify HEAD) &&
+        h_OTHER=$(git rev-parse --verify HEAD) &&
         echo FIXED >F &&
         GIT_AUTHOR_DATE="2005-05-26 23:44" \
         GIT_COMMITTER_DATE="2005-05-26 23:44" git-commit --amend &&
-        h_FIXED=$(git-rev-parse --verify HEAD) &&
+        h_FIXED=$(git rev-parse --verify HEAD) &&
         echo TEST+FIXED >F &&
         echo Merged initial commit and a later commit. >M &&
         echo $h_TEST >.git/MERGE_HEAD &&
         GIT_AUTHOR_DATE="2005-05-26 23:45" \
         GIT_COMMITTER_DATE="2005-05-26 23:45" git-commit -F M &&
-        h_MERGED=$(git-rev-parse --verify HEAD)
+        h_MERGED=$(git rev-parse --verify HEAD)
         rm -f M'
 
 cat >expect <<EOF
@@ -222,13 +222,13 @@ test_expect_success \
 unset h_TEST h_OTHER h_FIXED h_MERGED
 
 test_expect_success \
-       'git-cat-file blob master:F (expect OTHER)' \
-       'test OTHER = $(git-cat-file blob master:F)'
+       'git cat-file blob master:F (expect OTHER)' \
+       'test OTHER = $(git cat-file blob master:F)'
 test_expect_success \
-       'git-cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
-       'test TEST = $(git-cat-file blob "master@{2005-05-26 23:30}:F")'
+       'git cat-file blob master@{2005-05-26 23:30}:F (expect TEST)' \
+       'test TEST = $(git cat-file blob "master@{2005-05-26 23:30}:F")'
 test_expect_success \
-       'git-cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
-       'test OTHER = $(git-cat-file blob "master@{2005-05-26 23:42}:F")'
+       'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER)' \
+       'test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F")'
 
 test_done
diff --git a/t/t1420-lost-found.sh b/t/t1420-lost-found.sh
new file mode 100755 (executable)
index 0000000..dc9e402
--- /dev/null
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test fsck --lost-found'
+. ./test-lib.sh
+
+test_expect_success setup '
+       git config core.logAllRefUpdates 0 &&
+       : > file1 &&
+       git add file1 &&
+       test_tick &&
+       git commit -m initial &&
+       echo 1 > file1 &&
+       echo 2 > file2 &&
+       git add file1 file2 &&
+       test_tick &&
+       git commit -m second &&
+       echo 3 > file3 &&
+       git add file3
+'
+
+test_expect_success 'lost and found something' '
+       git rev-parse HEAD > lost-commit &&
+       git rev-parse :file3 > lost-other &&
+       test_tick &&
+       git reset --hard HEAD^ &&
+       git fsck --lost-found &&
+       test 2 = $(ls .git/lost-found/*/* | wc -l) &&
+       test -f .git/lost-found/commit/$(cat lost-commit) &&
+       test -f .git/lost-found/other/$(cat lost-other)
+'
+
+test_done
diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh
new file mode 100755 (executable)
index 0000000..e474b3f
--- /dev/null
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+test_description='test git rev-parse'
+. ./test-lib.sh
+
+test_rev_parse() {
+       name=$1
+       shift
+
+       test_expect_success "$name: is-bare-repository" \
+       "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+       shift
+       [ $# -eq 0 ] && return
+
+       test_expect_success "$name: is-inside-git-dir" \
+       "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+       shift
+       [ $# -eq 0 ] && return
+
+       test_expect_success "$name: is-inside-work-tree" \
+       "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+       shift
+       [ $# -eq 0 ] && return
+
+       test_expect_success "$name: prefix" \
+       "test '$1' = \"\$(git rev-parse --show-prefix)\""
+       shift
+       [ $# -eq 0 ] && return
+}
+
+# label is-bare is-inside-git is-inside-work prefix
+
+test_rev_parse toplevel false false true ''
+
+cd .git || exit 1
+test_rev_parse .git/ true true false ''
+cd objects || exit 1
+test_rev_parse .git/objects/ true true false ''
+cd ../.. || exit 1
+
+mkdir -p sub/dir || exit 1
+cd sub/dir || exit 1
+test_rev_parse subdirectory false false true sub/dir/
+cd ../.. || exit 1
+
+git config core.bare true
+test_rev_parse 'core.bare = true' true false false
+
+git config --unset core.bare
+test_rev_parse 'core.bare undefined' false false true
+
+mkdir work || exit 1
+cd work || exit 1
+export GIT_DIR=../.git
+export GIT_CONFIG="$(pwd)"/../.git/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../.git, core.bare = false' false false true ''
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../.git, core.bare = true' true false false ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../.git, core.bare undefined' false false true ''
+
+mv ../.git ../repo.git || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG="$(pwd)"/../repo.git/config
+
+git config core.bare false
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = false' false false true ''
+
+git config core.bare true
+test_rev_parse 'GIT_DIR=../repo.git, core.bare = true' true false false ''
+
+git config --unset core.bare
+test_rev_parse 'GIT_DIR=../repo.git, core.bare undefined' false false true ''
+
+test_done
diff --git a/t/t1501-worktree.sh b/t/t1501-worktree.sh
new file mode 100755 (executable)
index 0000000..7322161
--- /dev/null
@@ -0,0 +1,106 @@
+#!/bin/sh
+
+test_description='test separate work tree'
+. ./test-lib.sh
+
+test_rev_parse() {
+       name=$1
+       shift
+
+       test_expect_success "$name: is-bare-repository" \
+       "test '$1' = \"\$(git rev-parse --is-bare-repository)\""
+       shift
+       [ $# -eq 0 ] && return
+
+       test_expect_success "$name: is-inside-git-dir" \
+       "test '$1' = \"\$(git rev-parse --is-inside-git-dir)\""
+       shift
+       [ $# -eq 0 ] && return
+
+       test_expect_success "$name: is-inside-work-tree" \
+       "test '$1' = \"\$(git rev-parse --is-inside-work-tree)\""
+       shift
+       [ $# -eq 0 ] && return
+
+       test_expect_success "$name: prefix" \
+       "test '$1' = \"\$(git rev-parse --show-prefix)\""
+       shift
+       [ $# -eq 0 ] && return
+}
+
+mkdir -p work/sub/dir || exit 1
+mv .git repo.git || exit 1
+
+say "core.worktree = relative path"
+export GIT_DIR=repo.git
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
+unset GIT_WORK_TREE
+git config core.worktree ../work
+test_rev_parse 'outside'      false false false
+cd work || exit 1
+export GIT_DIR=../repo.git
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
+test_rev_parse 'inside'       false false true ''
+cd sub/dir || exit 1
+export GIT_DIR=../../../repo.git
+export GIT_CONFIG="$(pwd)"/$GIT_DIR/config
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "core.worktree = absolute path"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree "$(pwd)/work"
+test_rev_parse 'outside'      false false false
+cd work || exit 1
+test_rev_parse 'inside'       false false true ''
+cd sub/dir || exit 1
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+say "GIT_WORK_TREE=relative path (override core.worktree)"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+git config core.worktree non-existent
+export GIT_WORK_TREE=work
+test_rev_parse 'outside'      false false false
+cd work || exit 1
+export GIT_WORK_TREE=.
+test_rev_parse 'inside'       false false true ''
+cd sub/dir || exit 1
+export GIT_WORK_TREE=../..
+test_rev_parse 'subdirectory' false false true sub/dir/
+cd ../../.. || exit 1
+
+mv work repo.git/work
+
+say "GIT_WORK_TREE=absolute path, work tree below git dir"
+export GIT_DIR=$(pwd)/repo.git
+export GIT_CONFIG=$GIT_DIR/config
+export GIT_WORK_TREE=$(pwd)/repo.git/work
+test_rev_parse 'outside'              false false false
+cd repo.git || exit 1
+test_rev_parse 'in repo.git'              false true  false
+cd objects || exit 1
+test_rev_parse 'in repo.git/objects'      false true  false
+cd ../work || exit 1
+test_rev_parse 'in repo.git/work'         false true true ''
+cd sub/dir || exit 1
+test_rev_parse 'in repo.git/sub/dir' false true true sub/dir/
+cd ../../../.. || exit 1
+
+test_expect_success 'repo finds its work tree' '
+       (cd repo.git &&
+        : > work/sub/dir/untracked &&
+        test sub/dir/untracked = "$(git ls-files --others)")
+'
+
+test_expect_success 'repo finds its work tree from work tree, too' '
+       (cd repo.git/work/sub/dir &&
+        : > tracked &&
+        git --git-dir=../../.. add tracked &&
+        cd ../../.. &&
+        test sub/dir/tracked = "$(git ls-files)")
+'
+
+test_done
index 03ea4dece4b7fd3849269a0af3e960572ab5618d..ac84335b0a47fe1d26794e4c92f00d0ed051e540 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-checkout-index test.
+test_description='git checkout-index test.
 
 This test registers the following filesystem structure in the
 cache:
@@ -16,7 +16,7 @@ And then tries to checkout in a work tree that has the following:
     path0/file0 - a file in a directory
     path1       - a file
 
-The git-checkout-index command should fail when attempting to checkout
+The git checkout-index command should fail when attempting to checkout
 path0, finding it is occupied by a directory, and path1/file1, finding
 path1 is occupied by a non-directory.  With "-f" flag, it should remove
 the conflicting paths and succeed.
@@ -28,8 +28,8 @@ mkdir path1
 date >path1/file1
 
 test_expect_success \
-    'git-update-index --add various paths.' \
-    'git-update-index --add path0 path1/file1'
+    'git update-index --add various paths.' \
+    'git update-index --add path0 path1/file1'
 
 rm -fr path0 path1
 mkdir path0
@@ -37,17 +37,15 @@ date >path0/file0
 date >path1
 
 test_expect_failure \
-    'git-checkout-index without -f should fail on conflicting work tree.' \
-    'git-checkout-index -a'
+    'git checkout-index without -f should fail on conflicting work tree.' \
+    'git checkout-index -a'
 
 test_expect_success \
-    'git-checkout-index with -f should succeed.' \
-    'git-checkout-index -f -a'
+    'git checkout-index with -f should succeed.' \
+    'git checkout-index -f -a'
 
 test_expect_success \
-    'git-checkout-index conflicting paths.' \
+    'git checkout-index conflicting paths.' \
     'test -f path0 && test -d path1 && test -f path1/file1'
 
 test_done
-
-
index 0dcab8f1dea0d3d5e4bcebbc0b571fe46c2265e0..ef007532b15108d72445f7c95a2906a3039fbbbb 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-checkout-index test.
+test_description='git checkout-index test.
 
 This test registers the following filesystem structure in the cache:
 
@@ -26,46 +26,46 @@ show_files() {
        find path? -ls |
        sed -e 's/^[0-9]* * [0-9]* * \([-bcdl]\)[^ ]* *[0-9]* *[^ ]* *[^ ]* *[0-9]* [A-Z][a-z][a-z] [0-9][0-9] [^ ]* /fs: \1 /'
        # what's in the cache, just mode and name
-       git-ls-files --stage |
+       git ls-files --stage |
        sed -e 's/^\([0-9]*\) [0-9a-f]* [0-3] /ca: \1 /'
        # what's in the tree, just mode and name.
-       git-ls-tree -r "$1" |
+       git ls-tree -r "$1" |
        sed -e 's/^\([0-9]*\)   [^ ]*   [0-9a-f]*       /tr: \1 /'
 }
 
 mkdir path0
 date >path0/file0
 test_expect_success \
-    'git-update-index --add path0/file0' \
-    'git-update-index --add path0/file0'
+    'git update-index --add path0/file0' \
+    'git update-index --add path0/file0'
 test_expect_success \
-    'writing tree out with git-write-tree' \
-    'tree1=$(git-write-tree)'
+    'writing tree out with git write-tree' \
+    'tree1=$(git write-tree)'
 test_debug 'show_files $tree1'
 
 mkdir path1
 date >path1/file1
 test_expect_success \
-    'git-update-index --add path1/file1' \
-    'git-update-index --add path1/file1'
+    'git update-index --add path1/file1' \
+    'git update-index --add path1/file1'
 test_expect_success \
-    'writing tree out with git-write-tree' \
-    'tree2=$(git-write-tree)'
+    'writing tree out with git write-tree' \
+    'tree2=$(git write-tree)'
 test_debug 'show_files $tree2'
 
 rm -fr path1
 test_expect_success \
     'read previously written tree and checkout.' \
-    'git-read-tree -m $tree1 && git-checkout-index -f -a'
+    'git read-tree -m $tree1 && git checkout-index -f -a'
 test_debug 'show_files $tree1'
 
 ln -s path0 path1
 test_expect_success \
-    'git-update-index --add a symlink.' \
-    'git-update-index --add path1'
+    'git update-index --add a symlink.' \
+    'git update-index --add path1'
 test_expect_success \
-    'writing tree out with git-write-tree' \
-    'tree3=$(git-write-tree)'
+    'writing tree out with git write-tree' \
+    'tree3=$(git write-tree)'
 test_debug 'show_files $tree3'
 
 # Morten says "Got that?" here.
@@ -73,7 +73,7 @@ test_debug 'show_files $tree3'
 
 test_expect_success \
     'read previously written tree and checkout.' \
-    'git-read-tree $tree2 && git-checkout-index -f -a'
+    'git read-tree $tree2 && git checkout-index -f -a'
 test_debug 'show_files $tree2'
 
 test_expect_success \
@@ -84,4 +84,3 @@ test_expect_success \
      test ! -h path1/file1 && test -f path1/file1'
 
 test_done
-
index 4352ddb1cb78968099acacfd7cebc47febd58e5b..f7a00559209872fab5c79896ed5bc71ba64c884e 100755 (executable)
@@ -3,31 +3,31 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-checkout-index -u test.
+test_description='git checkout-index -u test.
 
-With -u flag, git-checkout-index internally runs the equivalent of
-git-update-index --refresh on the checked out entry.'
+With -u flag, git checkout-index internally runs the equivalent of
+git update-index --refresh on the checked out entry.'
 
 . ./test-lib.sh
 
 test_expect_success \
 'preparation' '
 echo frotz >path0 &&
-git-update-index --add path0 &&
-t=$(git-write-tree)'
+git update-index --add path0 &&
+t=$(git write-tree)'
 
 test_expect_failure \
-'without -u, git-checkout-index smudges stat information.' '
+'without -u, git checkout-index smudges stat information.' '
 rm -f path0 &&
-git-read-tree $t &&
-git-checkout-index -f -a &&
-git-diff-files | diff - /dev/null'
+git read-tree $t &&
+git checkout-index -f -a &&
+git diff-files | diff - /dev/null'
 
 test_expect_success \
-'with -u, git-checkout-index picks up stat information from new files.' '
+'with -u, git checkout-index picks up stat information from new files.' '
 rm -f path0 &&
-git-read-tree $t &&
-git-checkout-index -u -f -a &&
-git-diff-files | diff - /dev/null'
+git read-tree $t &&
+git checkout-index -u -f -a &&
+git diff-files | diff - /dev/null'
 
 test_done
index f9bc90aee44a799d62bb07ad70165a5d8bd34491..71894b37439bd1b9c72194cbbabe37680d2f9743 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-checkout-index --prefix test.
+test_description='git checkout-index --prefix test.
 
 This test makes sure that --prefix option works as advertised, and
 also verifies that such leading path may contain symlinks, unlike
@@ -17,14 +17,14 @@ test_expect_success \
     'mkdir path1 &&
     echo frotz >path0 &&
     echo rezrov >path1/file1 &&
-    git-update-index --add path0 path1/file1'
+    git update-index --add path0 path1/file1'
 
 test_expect_success \
     'have symlink in place where dir is expected.' \
     'rm -fr path0 path1 &&
      mkdir path2 &&
      ln -s path2 path1 &&
-     git-checkout-index -f -a &&
+     git checkout-index -f -a &&
      test ! -h path1 && test -d path1 &&
      test -f path1/file1 && test ! -f path2/file1'
 
@@ -32,7 +32,7 @@ test_expect_success \
     'use --prefix=path2/' \
     'rm -fr path0 path1 path2 &&
      mkdir path2 &&
-     git-checkout-index --prefix=path2/ -f -a &&
+     git checkout-index --prefix=path2/ -f -a &&
      test -f path2/path0 &&
      test -f path2/path1/file1 &&
      test ! -f path0 &&
@@ -41,7 +41,7 @@ test_expect_success \
 test_expect_success \
     'use --prefix=tmp-' \
     'rm -fr path0 path1 path2 tmp* &&
-     git-checkout-index --prefix=tmp- -f -a &&
+     git checkout-index --prefix=tmp- -f -a &&
      test -f tmp-path0 &&
      test -f tmp-path1/file1 &&
      test ! -f path0 &&
@@ -52,7 +52,7 @@ test_expect_success \
     'rm -fr path0 path1 path2 tmp* &&
      echo nitfol >tmp-path1 &&
      mkdir tmp-path0 &&
-     git-checkout-index --prefix=tmp- -f -a &&
+     git checkout-index --prefix=tmp- -f -a &&
      test -f tmp-path0 &&
      test -f tmp-path1/file1 &&
      test ! -f path0 &&
@@ -64,7 +64,7 @@ test_expect_success \
     'rm -fr path0 path1 path2 tmp* &&
      mkdir tmp1 tmp1/orary &&
      ln -s tmp1 tmp &&
-     git-checkout-index --prefix=tmp/orary/ -f -a &&
+     git checkout-index --prefix=tmp/orary/ -f -a &&
      test -d tmp1/orary &&
      test -f tmp1/orary/path0 &&
      test -f tmp1/orary/path1/file1 &&
@@ -76,7 +76,7 @@ test_expect_success \
     'rm -fr path0 path1 path2 tmp* &&
      mkdir tmp1 &&
      ln -s tmp1 tmp &&
-     git-checkout-index --prefix=tmp/orary- -f -a &&
+     git checkout-index --prefix=tmp/orary- -f -a &&
      test -f tmp1/orary-path0 &&
      test -f tmp1/orary-path1/file1 &&
      test -h tmp'
@@ -87,7 +87,7 @@ test_expect_success \
     'rm -fr path0 path1 path2 tmp* &&
      mkdir tmp1 &&
      ln -s tmp1 tmp-path1 &&
-     git-checkout-index --prefix=tmp- -f -a &&
+     git checkout-index --prefix=tmp- -f -a &&
      test -f tmp-path0 &&
      test ! -h tmp-path1 &&
      test -d tmp-path1 &&
index c100959cad3f017ec282c10bbc31bdc4982d5fbc..39133b8c7a4b56cb7273cec607ea89081a426eff 100755 (executable)
@@ -3,9 +3,9 @@
 # Copyright (c) 2006 Shawn Pearce
 #
 
-test_description='git-checkout-index --temp test.
+test_description='git checkout-index --temp test.
 
-With --temp flag, git-checkout-index writes to temporary merge files
+With --temp flag, git checkout-index writes to temporary merge files
 rather than the tracked path.'
 
 . ./test-lib.sh
@@ -18,28 +18,28 @@ echo tree1path1 >path1 &&
 echo tree1path3 >path3 &&
 echo tree1path4 >path4 &&
 echo tree1asubdir/path5 >asubdir/path5 &&
-git-update-index --add path0 path1 path3 path4 asubdir/path5 &&
-t1=$(git-write-tree) &&
+git update-index --add path0 path1 path3 path4 asubdir/path5 &&
+t1=$(git write-tree) &&
 rm -f path* .merge_* out .git/index &&
 echo tree2path0 >path0 &&
 echo tree2path1 >path1 &&
 echo tree2path2 >path2 &&
 echo tree2path4 >path4 &&
-git-update-index --add path0 path1 path2 path4 &&
-t2=$(git-write-tree) &&
+git update-index --add path0 path1 path2 path4 &&
+t2=$(git write-tree) &&
 rm -f path* .merge_* out .git/index &&
 echo tree2path0 >path0 &&
 echo tree3path1 >path1 &&
 echo tree3path2 >path2 &&
 echo tree3path3 >path3 &&
-git-update-index --add path0 path1 path2 path3 &&
-t3=$(git-write-tree)'
+git update-index --add path0 path1 path2 path3 &&
+t3=$(git write-tree)'
 
 test_expect_success \
 'checkout one stage 0 to temporary file' '
 rm -f path* .merge_* out .git/index &&
-git-read-tree $t1 &&
-git-checkout-index --temp -- path1 >out &&
+git read-tree $t1 &&
+git checkout-index --temp -- path1 >out &&
 test $(wc -l <out) = 1 &&
 test $(cut "-d " -f2 out) = path1 &&
 p=$(cut "-d    " -f1 out) &&
@@ -49,8 +49,8 @@ test $(cat $p) = tree1path1'
 test_expect_success \
 'checkout all stage 0 to temporary files' '
 rm -f path* .merge_* out .git/index &&
-git-read-tree $t1 &&
-git-checkout-index -a --temp >out &&
+git read-tree $t1 &&
+git checkout-index -a --temp >out &&
 test $(wc -l <out) = 5 &&
 for f in path0 path1 path3 path4 asubdir/path5
 do
@@ -63,12 +63,12 @@ done'
 test_expect_success \
 'prepare 3-way merge' '
 rm -f path* .merge_* out .git/index &&
-git-read-tree -m $t1 $t2 $t3'
+git read-tree -m $t1 $t2 $t3'
 
 test_expect_success \
 'checkout one stage 2 to temporary file' '
 rm -f path* .merge_* out &&
-git-checkout-index --stage=2 --temp -- path1 >out &&
+git checkout-index --stage=2 --temp -- path1 >out &&
 test $(wc -l <out) = 1 &&
 test $(cut "-d " -f2 out) = path1 &&
 p=$(cut "-d    " -f1 out) &&
@@ -78,7 +78,7 @@ test $(cat $p) = tree2path1'
 test_expect_success \
 'checkout all stage 2 to temporary files' '
 rm -f path* .merge_* out &&
-git-checkout-index --all --stage=2 --temp >out &&
+git checkout-index --all --stage=2 --temp >out &&
 test $(wc -l <out) = 3 &&
 for f in path1 path2 path4
 do
@@ -91,13 +91,13 @@ done'
 test_expect_success \
 'checkout all stages/one file to nothing' '
 rm -f path* .merge_* out &&
-git-checkout-index --stage=all --temp -- path0 >out &&
+git checkout-index --stage=all --temp -- path0 >out &&
 test $(wc -l <out) = 0'
 
 test_expect_success \
 'checkout all stages/one file to temporary files' '
 rm -f path* .merge_* out &&
-git-checkout-index --stage=all --temp -- path1 >out &&
+git checkout-index --stage=all --temp -- path1 >out &&
 test $(wc -l <out) = 1 &&
 test $(cut "-d " -f2 out) = path1 &&
 cut "-d        " -f1 out | (read s1 s2 s3 &&
@@ -111,7 +111,7 @@ test $(cat $s3) = tree3path1)'
 test_expect_success \
 'checkout some stages/one file to temporary files' '
 rm -f path* .merge_* out &&
-git-checkout-index --stage=all --temp -- path2 >out &&
+git checkout-index --stage=all --temp -- path2 >out &&
 test $(wc -l <out) = 1 &&
 test $(cut "-d " -f2 out) = path2 &&
 cut "-d        " -f1 out | (read s1 s2 s3 &&
@@ -124,7 +124,7 @@ test $(cat $s3) = tree3path2)'
 test_expect_success \
 'checkout all stages/all files to temporary files' '
 rm -f path* .merge_* out &&
-git-checkout-index -a --stage=all --temp >out &&
+git checkout-index -a --stage=all --temp >out &&
 test $(wc -l <out) = 5'
 
 test_expect_success \
@@ -184,7 +184,7 @@ test $(cat $s1) = tree1asubdir/path5)'
 test_expect_success \
 'checkout --temp within subdir' '
 (cd asubdir &&
- git-checkout-index -a --stage=all >out &&
+ git checkout-index -a --stage=all >out &&
  test $(wc -l <out) = 1 &&
  test $(grep path5 out | cut "-d       " -f2) = path5 &&
  grep path5 out | cut "-d      " -f1 | (read s1 s2 s3 &&
@@ -198,11 +198,11 @@ test_expect_success \
 'checkout --temp symlink' '
 rm -f path* .merge_* out .git/index &&
 ln -s b a &&
-git-update-index --add a &&
-t4=$(git-write-tree) &&
+git update-index --add a &&
+t4=$(git write-tree) &&
 rm -f .git/index &&
-git-read-tree $t4 &&
-git-checkout-index --temp -a >out &&
+git read-tree $t4 &&
+git checkout-index --temp -a >out &&
 test $(wc -l <out) = 1 &&
 test $(cut "-d " -f2 out) = a &&
 p=$(cut "-d    " -f1 out) &&
index e34a51533353e9e88a228b7ec2d88a08550f7ae8..a84c5a6af9e69ffec7689827ce1ba653a658a73f 100755 (executable)
@@ -3,22 +3,22 @@
 # Copyright (c) 2007 Johannes Sixt
 #
 
-test_description='git-checkout-index on filesystem w/o symlinks test.
+test_description='git checkout-index on filesystem w/o symlinks test.
 
-This tests that git-checkout-index creates a symbolic link as a plain
+This tests that git checkout-index creates a symbolic link as a plain
 file if core.symlinks is false.'
 
 . ./test-lib.sh
 
 test_expect_success \
 'preparation' '
-git-config core.symlinks false &&
+git config core.symlinks false &&
 l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l        symlink" | git-update-index --index-info'
+echo "120000 $l        symlink" | git update-index --index-info'
 
 test_expect_success \
 'the checked-out symlink must be a file' '
-git-checkout-index symlink &&
+git checkout-index symlink &&
 test -f symlink'
 
 test_expect_success \
diff --git a/t/t2050-git-dir-relative.sh b/t/t2050-git-dir-relative.sh
new file mode 100755 (executable)
index 0000000..88f268b
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+test_description='check problems with relative GIT_DIR
+
+This test creates a working tree state with a file and subdir:
+
+  top (committed several times)
+  subdir (a subdirectory)
+
+It creates a commit-hook and tests it, then moves .git
+into the subdir while keeping the worktree location,
+and tries commits from the top and the subdir, checking
+that the commit-hook still gets called.'
+
+. ./test-lib.sh
+
+COMMIT_FILE="$(pwd)/output"
+export COMMIT_FILE
+
+test_expect_success 'Setting up post-commit hook' '
+mkdir -p .git/hooks &&
+echo >.git/hooks/post-commit "#!/bin/sh
+touch \"\${COMMIT_FILE}\"
+echo Post commit hook was called." &&
+chmod +x .git/hooks/post-commit'
+
+test_expect_success 'post-commit hook used ordinarily' '
+echo initial >top &&
+git-add top
+git-commit -m initial &&
+test -r "${COMMIT_FILE}"
+'
+
+rm -rf "${COMMIT_FILE}"
+mkdir subdir
+mv .git subdir
+
+test_expect_success 'post-commit-hook created and used from top dir' '
+echo changed >top &&
+git --git-dir subdir/.git add top &&
+git --git-dir subdir/.git commit -m topcommit &&
+test -r "${COMMIT_FILE}"
+'
+
+rm -rf "${COMMIT_FILE}"
+
+test_expect_success 'post-commit-hook from sub dir' '
+echo changed again >top
+cd subdir &&
+git --git-dir .git --work-tree .. add ../top &&
+git --git-dir .git --work-tree .. commit -m subcommit &&
+test -r "${COMMIT_FILE}"
+'
+
+test_done
index 5bc0a3bed3e9fb53639bc6a00ce4ff82e8c78ca1..04a1ed1a6b9dd4eabc2b95d348b77b0fd08b0da4 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-update-index nonsense-path test.
+test_description='git update-index nonsense-path test.
 
 This test creates the following structure in the cache:
 
@@ -12,7 +12,7 @@ This test creates the following structure in the cache:
     path2/file2 - a file in a directory
     path3/file3 - a file in a directory
 
-and tries to git-update-index --add the following:
+and tries to git update-index --add the following:
 
     path0/file0 - a file in a directory
     path1/file1 - a file in a directory
@@ -31,8 +31,8 @@ date >path2/file2
 date >path3/file3
 
 test_expect_success \
-    'git-update-index --add to add various paths.' \
-    'git-update-index --add -- path0 path1 path2/file2 path3/file3'
+    'git update-index --add to add various paths.' \
+    'git update-index --add -- path0 path1 path2/file2 path3/file3'
 
 rm -fr path?
 
@@ -45,7 +45,7 @@ date >path1/file1
 for p in path0/file0 path1/file1 path2 path3
 do
        test_expect_failure \
-           "git-update-index to add conflicting path $p should fail." \
-           "git-update-index --add -- $p"
+           "git update-index to add conflicting path $p should fail." \
+           "git update-index --add -- $p"
 done
 test_done
index a78ea7f0b0e4910407c75191fc683daaac1af2b6..59b560bfdf240e87516aadd6a31a2fe84e85d49a 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Junio C Hamano
 #
 
-test_description='git-update-index --again test.
+test_description='git update-index --again test.
 '
 
 . ./test-lib.sh
@@ -15,29 +15,29 @@ EOF
 test_expect_success 'update-index --add' \
        'echo hello world >file1 &&
         echo goodbye people >file2 &&
-        git-update-index --add file1 file2 &&
-        git-ls-files -s >current &&
+        git update-index --add file1 file2 &&
+        git ls-files -s >current &&
         cmp current expected'
 
 test_expect_success 'update-index --again' \
        'rm -f file1 &&
        echo hello everybody >file2 &&
-       if git-update-index --again
+       if git update-index --again
        then
                echo should have refused to remove file1
                exit 1
        else
                echo happy - failed as expected
        fi &&
-        git-ls-files -s >current &&
+        git ls-files -s >current &&
         cmp current expected'
 
 cat > expected <<\EOF
 100644 0f1ae1422c2bf43f117d3dbd715c988a9ed2103f 0      file2
 EOF
 test_expect_success 'update-index --remove --again' \
-       'git-update-index --remove --again &&
-        git-ls-files -s >current &&
+       'git update-index --remove --again &&
+        git ls-files -s >current &&
         cmp current expected'
 
 test_expect_success 'first commit' 'git-commit -m initial'
@@ -50,11 +50,11 @@ test_expect_success 'update-index again' \
        'mkdir -p dir1 &&
        echo hello world >dir1/file3 &&
        echo goodbye people >file2 &&
-       git-update-index --add file2 dir1/file3 &&
+       git update-index --add file2 dir1/file3 &&
        echo hello everybody >file2
        echo happy >dir1/file3 &&
-       git-update-index --again &&
-       git-ls-files -s >current &&
+       git update-index --again &&
+       git ls-files -s >current &&
        cmp current expected'
 
 cat > expected <<\EOF
@@ -65,9 +65,9 @@ test_expect_success 'update-index --update from subdir' \
        'echo not so happy >file2 &&
        cd dir1 &&
        cat ../file2 >file3 &&
-       git-update-index --again &&
+       git update-index --again &&
        cd .. &&
-       git-ls-files -s >current &&
+       git ls-files -s >current &&
        cmp current expected'
 
 cat > expected <<\EOF
@@ -77,8 +77,8 @@ EOF
 test_expect_success 'update-index --update with pathspec' \
        'echo very happy >file2 &&
        cat file2 >dir1/file3 &&
-       git-update-index --again dir1/ &&
-       git-ls-files -s >current &&
+       git update-index --again dir1/ &&
+       git ls-files -s >current &&
        cmp current expected'
 
 test_done
index 969ef891d3152106e192e84595c0de9b1010cd4b..19d0894d260787d37a43199d7a3f6c3aa37d32aa 100755 (executable)
@@ -3,29 +3,29 @@
 # Copyright (c) 2007 Johannes Sixt
 #
 
-test_description='git-update-index on filesystem w/o symlinks test.
+test_description='git update-index on filesystem w/o symlinks test.
 
-This tests that git-update-index keeps the symbolic link property
+This tests that git update-index keeps the symbolic link property
 even if a plain file is in the working tree if core.symlinks is false.'
 
 . ./test-lib.sh
 
 test_expect_success \
 'preparation' '
-git-config core.symlinks false &&
+git config core.symlinks false &&
 l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l        symlink" | git-update-index --index-info'
+echo "120000 $l        symlink" | git update-index --index-info'
 
 test_expect_success \
 'modify the symbolic link' '
 echo -n new-file > symlink &&
-git-update-index symlink'
+git update-index symlink'
 
 test_expect_success \
 'the index entry must still be a symbolic link' '
-case "`git-ls-files --stage --cached symlink`" in
+case "`git ls-files --stage --cached symlink`" in
 120000" "*symlink) echo ok;;
-*) echo fail; git-ls-files --stage --cached symlink; (exit 1);;
+*) echo fail; git ls-files --stage --cached symlink; (exit 1);;
 esac'
 
 test_done
index 58cd7f31bed90fd24d1f5cd43ff0e26c922ea4ab..61d08bb431ca18d6223e944cd4ca43fba6c1d332 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-add -u with path limiting
+test_description='git add -u with path limiting
 
 This test creates a working tree state with three files:
 
@@ -8,7 +8,7 @@ This test creates a working tree state with three files:
   dir/sub (previously committed, modified)
   dir/other (untracked)
 
-and issues a git-add -u with path limiting on "dir" to add
+and issues a git add -u with path limiting on "dir" to add
 only the updates to dir/sub.'
 
 . ./test-lib.sh
index adcbe03d561b2a7295a881d923b013bbefb9c7c7..bc0a3513920cab41e4335b8c1b5163e25e8354d3 100755 (executable)
@@ -3,9 +3,9 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-ls-files test (--others should pick up symlinks).
+test_description='git ls-files test (--others should pick up symlinks).
 
-This test runs git-ls-files --others with the following on the
+This test runs git ls-files --others with the following on the
 filesystem.
 
     path0       - a file
@@ -23,7 +23,7 @@ date >path2/file2
 date >path2-junk
 date >path3/file3
 date >path3-junk
-git-update-index --add path3-junk path3/file3
+git update-index --add path3-junk path3/file3
 
 cat >expected1 <<EOF
 expected1
@@ -37,20 +37,20 @@ EOF
 sed -e 's|path2/file2|path2/|' <expected1 >expected2
 
 test_expect_success \
-    'git-ls-files --others to show output.' \
-    'git-ls-files --others >output'
+    'git ls-files --others to show output.' \
+    'git ls-files --others >output'
 
 test_expect_success \
-    'git-ls-files --others should pick up symlinks.' \
+    'git ls-files --others should pick up symlinks.' \
     'diff output expected1'
 
 test_expect_success \
-    'git-ls-files --others --directory to show output.' \
-    'git-ls-files --others --directory >output'
+    'git ls-files --others --directory to show output.' \
+    'git ls-files --others --directory >output'
 
 
 test_expect_success \
-    'git-ls-files --others --directory should not get confused.' \
+    'git ls-files --others --directory should not get confused.' \
     'diff output expected2'
 
 test_done
index fcfcfbba7df50b55df7d002bf9745b912cfcae9b..ae0639d8f3a3fa428dca31df55193487a74b5b57 100755 (executable)
@@ -3,9 +3,9 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-ls-files --others --exclude
+test_description='git ls-files --others --exclude
 
-This test runs git-ls-files --others and tests --exclude patterns.
+This test runs git ls-files --others and tests --exclude patterns.
 '
 
 . ./test-lib.sh
@@ -59,8 +59,8 @@ echo '!*.2
 !*.8' >one/two/.gitignore
 
 test_expect_success \
-    'git-ls-files --others with various exclude options.' \
-    'git-ls-files --others \
+    'git ls-files --others with various exclude options.' \
+    'git ls-files --others \
        --exclude=\*.6 \
        --exclude-per-directory=.gitignore \
        --exclude-from=.git/ignore \
@@ -71,8 +71,8 @@ test_expect_success \
 printf '*.1\r\n/*.3\r\n!*.6\r\n' >.gitignore
 
 test_expect_success \
-    'git-ls-files --others with \r\n line endings.' \
-    'git-ls-files --others \
+    'git ls-files --others with \r\n line endings.' \
+    'git ls-files --others \
        --exclude=\*.6 \
        --exclude-per-directory=.gitignore \
        --exclude-from=.git/ignore \
@@ -84,9 +84,9 @@ cat > excludes-file << EOF
 e*
 EOF
 
-git-config core.excludesFile excludes-file
+git config core.excludesFile excludes-file
 
-git-runstatus | grep "^#       " > output
+git runstatus | grep "^#       " > output
 
 cat > expect << EOF
 #      .gitignore
index cc8967d76b10e18eb6b7db69fbb31baf702f2efc..8687a01d2b169c1afafcd811d9108a85ad9fdd56 100755 (executable)
@@ -3,9 +3,9 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-ls-files test (-- to terminate the path list).
+test_description='git ls-files test (-- to terminate the path list).
 
-This test runs git-ls-files --others with the following on the
+This test runs git ls-files --others with the following on the
 filesystem.
 
     path0       - a file
@@ -21,8 +21,8 @@ test_expect_success \
        echo frotz >./--'
 
 test_expect_success \
-    'git-ls-files without path restriction.' \
-    'git-ls-files --others >output &&
+    'git ls-files without path restriction.' \
+    'git ls-files --others >output &&
      git diff output - <<EOF
 --
 -foo
@@ -32,32 +32,32 @@ EOF
 '
 
 test_expect_success \
-    'git-ls-files with path restriction.' \
-    'git-ls-files --others path0 >output &&
+    'git ls-files with path restriction.' \
+    'git ls-files --others path0 >output &&
        git diff output - <<EOF
 path0
 EOF
 '
 
 test_expect_success \
-    'git-ls-files with path restriction with --.' \
-    'git-ls-files --others -- path0 >output &&
+    'git ls-files with path restriction with --.' \
+    'git ls-files --others -- path0 >output &&
        git diff output - <<EOF
 path0
 EOF
 '
 
 test_expect_success \
-    'git-ls-files with path restriction with -- --.' \
-    'git-ls-files --others -- -- >output &&
+    'git ls-files with path restriction with -- --.' \
+    'git ls-files --others -- -- >output &&
        git diff output - <<EOF
 --
 EOF
 '
 
 test_expect_success \
-    'git-ls-files with no path restriction.' \
-    'git-ls-files --others -- >output &&
+    'git ls-files with no path restriction.' \
+    'git ls-files --others -- >output &&
        git diff output - <<EOF
 --
 -foo
index 5fc19767117fae3e0f41dd6061a69ff177ede1a4..ec1404063701eef04667d5ffbbb4bdc8051c773b 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-ls-files -k and -m flags test.
+test_description='git ls-files -k and -m flags test.
 
 This test prepares the following in the cache:
 
@@ -22,7 +22,7 @@ and the following on the filesystem:
     path5      - a symlink
     path6/file6 - a file in a directory
 
-git-ls-files -k should report that existing filesystem
+git ls-files -k should report that existing filesystem
 objects except path4, path5 and path6/file6 to be killed.
 
 Also for modification test, the cache and working tree have:
@@ -47,8 +47,8 @@ date >path8
 : >path9
 date >path10
 test_expect_success \
-    'git-update-index --add to add various paths.' \
-    "git-update-index --add -- path0 path1 path?/file? path7 path8 path9 path10"
+    'git update-index --add to add various paths.' \
+    "git update-index --add -- path0 path1 path?/file? path7 path8 path9 path10"
 
 rm -fr path? ;# leave path10 alone
 date >path2
@@ -64,8 +64,8 @@ date >path7
 touch path10
 
 test_expect_success \
-    'git-ls-files -k to show killed files.' \
-    'git-ls-files -k >.output'
+    'git ls-files -k to show killed files.' \
+    'git ls-files -k >.output'
 cat >.expected <<EOF
 path0/file0
 path1/file1
@@ -74,12 +74,12 @@ path3
 EOF
 
 test_expect_success \
-    'validate git-ls-files -k output.' \
+    'validate git ls-files -k output.' \
     'diff .output .expected'
 
 test_expect_success \
-    'git-ls-files -m to show modified files.' \
-    'git-ls-files -m >.output'
+    'git ls-files -m to show modified files.' \
+    'git ls-files -m >.output'
 cat >.expected <<EOF
 path0
 path1
@@ -90,7 +90,7 @@ path8
 EOF
 
 test_expect_success \
-    'validate git-ls-files -m output.' \
+    'validate git ls-files -m output.' \
     'diff .output .expected'
 
 test_done
index d55559e553d3bd4d9432ac9778b6bcc7888c5a04..c83f820ad2d8588b8e5e15b3cb55172b26b7c33e 100755 (executable)
@@ -3,25 +3,25 @@
 # Copyright (c) 2006 Carl D. Worth
 #
 
-test_description='git-ls-files test for --error-unmatch option
+test_description='git ls-files test for --error-unmatch option
 
-This test runs git-ls-files --error-unmatch to ensure it correctly
+This test runs git ls-files --error-unmatch to ensure it correctly
 returns an error when a non-existent path is provided on the command
 line.
 '
 . ./test-lib.sh
 
 touch foo bar
-git-update-index --add foo bar
+git update-index --add foo bar
 git-commit -m "add foo bar"
 
 test_expect_failure \
-    'git-ls-files --error-unmatch should fail with unmatched path.' \
-    'git-ls-files --error-unmatch foo bar-does-not-match'
+    'git ls-files --error-unmatch should fail with unmatched path.' \
+    'git ls-files --error-unmatch foo bar-does-not-match'
 
 test_expect_success \
-    'git-ls-files --error-unmatch should succeed eith matched paths.' \
-    'git-ls-files --error-unmatch foo bar'
+    'git ls-files --error-unmatch should succeed eith matched paths.' \
+    'git ls-files --error-unmatch foo bar'
 
 test_done
 1
index 86ee2b0bd3737f94ebab32a344a6b1c5afc5ea92..607f57ff941b7da5296ab58cbf5e30db66087669 100755 (executable)
@@ -525,4 +525,3 @@ test_expect_success 'reset and bind merge' '
 '
 
 test_done
-
index e10749245b454f4c71486e536049cbf5b09259d5..46427e3f365ad0ec09c432f8cedf2106afe4dc08 100755 (executable)
@@ -3,9 +3,9 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-ls-tree test.
+test_description='git ls-tree test.
 
-This test runs git-ls-tree with the following in a tree.
+This test runs git ls-tree with the following in a tree.
 
     path0       - a file
     path1      - a symlink
@@ -27,8 +27,8 @@ test_expect_success \
      ln -s ../path1 path2/bazbo &&
      echo Mi >path2/baz/b &&
      find path? \( -type f -o -type l \) -print |
-     xargs git-update-index --add &&
-     tree=`git-write-tree` &&
+     xargs git update-index --add &&
+     tree=`git write-tree` &&
      echo $tree'
 
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
@@ -40,7 +40,7 @@ test_output () {
 
 test_expect_success \
     'ls-tree plain' \
-    'git-ls-tree $tree >current &&
+    'git ls-tree $tree >current &&
      cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
@@ -50,7 +50,7 @@ EOF
 
 test_expect_success \
     'ls-tree recursive' \
-    'git-ls-tree -r $tree >current &&
+    'git ls-tree -r $tree >current &&
      cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
@@ -62,7 +62,7 @@ EOF
 
 test_expect_success \
     'ls-tree recursive with -t' \
-    'git-ls-tree -r -t $tree >current &&
+    'git ls-tree -r -t $tree >current &&
      cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
@@ -76,7 +76,7 @@ EOF
 
 test_expect_success \
     'ls-tree recursive with -d' \
-    'git-ls-tree -r -d $tree >current &&
+    'git ls-tree -r -d $tree >current &&
      cat >expected <<\EOF &&
 040000 tree X  path2
 040000 tree X  path2/baz
@@ -85,7 +85,7 @@ EOF
 
 test_expect_success \
     'ls-tree filtered with path' \
-    'git-ls-tree $tree path >current &&
+    'git ls-tree $tree path >current &&
      cat >expected <<\EOF &&
 EOF
      test_output'
@@ -95,7 +95,7 @@ EOF
 # they are shown in canonical order.
 test_expect_success \
     'ls-tree filtered with path1 path0' \
-    'git-ls-tree $tree path1 path0 >current &&
+    'git ls-tree $tree path1 path0 >current &&
      cat >expected <<\EOF &&
 100644 blob X  path0
 120000 blob X  path1
@@ -104,7 +104,7 @@ EOF
 
 test_expect_success \
     'ls-tree filtered with path0/' \
-    'git-ls-tree $tree path0/ >current &&
+    'git ls-tree $tree path0/ >current &&
      cat >expected <<\EOF &&
 EOF
      test_output'
@@ -113,7 +113,7 @@ EOF
 # with pathspec semantics it shows only path2
 test_expect_success \
     'ls-tree filtered with path2' \
-    'git-ls-tree $tree path2 >current &&
+    'git ls-tree $tree path2 >current &&
      cat >expected <<\EOF &&
 040000 tree X  path2
 EOF
@@ -122,7 +122,7 @@ EOF
 # ... and path2/ shows the children.
 test_expect_success \
     'ls-tree filtered with path2/' \
-    'git-ls-tree $tree path2/ >current &&
+    'git ls-tree $tree path2/ >current &&
      cat >expected <<\EOF &&
 040000 tree X  path2/baz
 120000 blob X  path2/bazbo
@@ -134,7 +134,7 @@ EOF
 # path2/baz
 test_expect_success \
     'ls-tree filtered with path2/baz' \
-    'git-ls-tree $tree path2/baz >current &&
+    'git ls-tree $tree path2/baz >current &&
      cat >expected <<\EOF &&
 040000 tree X  path2/baz
 EOF
@@ -142,14 +142,14 @@ EOF
 
 test_expect_success \
     'ls-tree filtered with path2/bak' \
-    'git-ls-tree $tree path2/bak >current &&
+    'git ls-tree $tree path2/bak >current &&
      cat >expected <<\EOF &&
 EOF
      test_output'
 
 test_expect_success \
     'ls-tree -t filtered with path2/bak' \
-    'git-ls-tree -t $tree path2/bak >current &&
+    'git ls-tree -t $tree path2/bak >current &&
      cat >expected <<\EOF &&
 040000 tree X  path2
 EOF
index 087929a4bfdf053124f81af3bfb73ab1e9e1709a..39fe2676dcd8e22451309d2321dee45410f90963 100755 (executable)
@@ -4,9 +4,9 @@
 # Copyright (c) 2005 Robert Fitzsimons
 #
 
-test_description='git-ls-tree directory and filenames handling.
+test_description='git ls-tree directory and filenames handling.
 
-This test runs git-ls-tree with the following in a tree.
+This test runs git ls-tree with the following in a tree.
 
     1.txt              - a file
     2.txt              - a file
@@ -35,8 +35,8 @@ test_expect_success \
      echo 111 >path3/1.txt &&
      echo 222 >path3/2.txt &&
      find *.txt path* \( -type f -o -type l \) -print |
-     xargs git-update-index --add &&
-     tree=`git-write-tree` &&
+     xargs git update-index --add &&
+     tree=`git write-tree` &&
      echo $tree'
 
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
@@ -48,7 +48,7 @@ test_output () {
 
 test_expect_success \
     'ls-tree plain' \
-    'git-ls-tree $tree >current &&
+    'git ls-tree $tree >current &&
      cat >expected <<\EOF &&
 100644 blob X  1.txt
 100644 blob X  2.txt
@@ -62,7 +62,7 @@ EOF
 # Recursive does not show tree nodes anymore...
 test_expect_success \
     'ls-tree recursive' \
-    'git-ls-tree -r $tree >current &&
+    'git ls-tree -r $tree >current &&
      cat >expected <<\EOF &&
 100644 blob X  1.txt
 100644 blob X  2.txt
@@ -76,7 +76,7 @@ EOF
 
 test_expect_success \
     'ls-tree filter 1.txt' \
-    'git-ls-tree $tree 1.txt >current &&
+    'git ls-tree $tree 1.txt >current &&
      cat >expected <<\EOF &&
 100644 blob X  1.txt
 EOF
@@ -84,7 +84,7 @@ EOF
 
 test_expect_success \
     'ls-tree filter path1/b/c/1.txt' \
-    'git-ls-tree $tree path1/b/c/1.txt >current &&
+    'git ls-tree $tree path1/b/c/1.txt >current &&
      cat >expected <<\EOF &&
 100644 blob X  path1/b/c/1.txt
 EOF
@@ -92,7 +92,7 @@ EOF
 
 test_expect_success \
     'ls-tree filter all 1.txt files' \
-    'git-ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
+    'git ls-tree $tree 1.txt path0/a/b/c/1.txt path1/b/c/1.txt path2/1.txt path3/1.txt >current &&
      cat >expected <<\EOF &&
 100644 blob X  1.txt
 100644 blob X  path0/a/b/c/1.txt
@@ -107,7 +107,7 @@ EOF
 # it behaves as if path0/a/b/c, path1/b/c, path2 and path3 are specified.
 test_expect_success \
     'ls-tree filter directories' \
-    'git-ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
+    'git ls-tree $tree path3 path2 path0/a/b/c path1/b/c path0/a >current &&
      cat >expected <<\EOF &&
 040000 tree X  path0/a/b/c
 040000 tree X  path1/b/c
@@ -120,7 +120,7 @@ EOF
 # having 1.txt and path3
 test_expect_success \
     'ls-tree filter odd names' \
-    'git-ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
+    'git ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&
      cat >expected <<\EOF &&
 100644 blob X  1.txt
 100644 blob X  path3/1.txt
@@ -130,7 +130,7 @@ EOF
 
 test_expect_success \
     'ls-tree filter missing files and extra slashes' \
-    'git-ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
+    'git ls-tree $tree 1.txt/ abc.txt path3//23.txt path3/2.txt/// >current &&
      cat >expected <<\EOF &&
 EOF
      test_output'
index 6f6d8844e8628d006b1d4c11eb02a5ee14a8fd35..ef1eeb7d8ac349821559dd358470f10ae0531739 100755 (executable)
@@ -13,22 +13,22 @@ handled.  Specifically, that a bogus branch is not created.
 test_expect_success \
     'prepare a trivial repository' \
     'echo Hello > A &&
-     git-update-index --add A &&
+     git update-index --add A &&
      git-commit -m "Initial commit." &&
-     HEAD=$(git-rev-parse --verify HEAD)'
+     HEAD=$(git rev-parse --verify HEAD)'
 
 test_expect_failure \
     'git branch --help should not have created a bogus branch' \
-    'git-branch --help </dev/null >/dev/null 2>/dev/null || :
+    'git branch --help </dev/null >/dev/null 2>/dev/null || :
      test -f .git/refs/heads/--help'
 
 test_expect_success \
     'git branch abc should create a branch' \
-    'git-branch abc && test -f .git/refs/heads/abc'
+    'git branch abc && test -f .git/refs/heads/abc'
 
 test_expect_success \
     'git branch a/b/c should create a branch' \
-    'git-branch a/b/c && test -f .git/refs/heads/a/b/c'
+    'git branch a/b/c && test -f .git/refs/heads/a/b/c'
 
 cat >expect <<EOF
 0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     branch: Created from master
@@ -36,140 +36,159 @@ EOF
 test_expect_success \
     'git branch -l d/e/f should create a branch and a log' \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
-     git-branch -l d/e/f &&
+     git branch -l d/e/f &&
         test -f .git/refs/heads/d/e/f &&
         test -f .git/logs/refs/heads/d/e/f &&
         diff expect .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch -d d/e/f should delete a branch and a log' \
-       'git-branch -d d/e/f &&
+       'git branch -d d/e/f &&
         test ! -f .git/refs/heads/d/e/f &&
         test ! -f .git/logs/refs/heads/d/e/f'
 
 test_expect_success \
     'git branch j/k should work after branch j has been deleted' \
-       'git-branch j &&
-        git-branch -d j &&
-        git-branch j/k'
+       'git branch j &&
+        git branch -d j &&
+        git branch j/k'
 
 test_expect_success \
     'git branch l should work after branch l/m has been deleted' \
-       'git-branch l/m &&
-        git-branch -d l/m &&
-        git-branch l'
+       'git branch l/m &&
+        git branch -d l/m &&
+        git branch l'
 
 test_expect_success \
     'git branch -m m m/m should work' \
-       'git-branch -l m &&
-        git-branch -m m m/m &&
+       'git branch -l m &&
+        git branch -m m m/m &&
         test -f .git/logs/refs/heads/m/m'
 
 test_expect_success \
     'git branch -m n/n n should work' \
-       'git-branch -l n/n &&
-        git-branch -m n/n n
+       'git branch -l n/n &&
+        git branch -m n/n n
         test -f .git/logs/refs/heads/n'
 
 test_expect_failure \
     'git branch -m o/o o should fail when o/p exists' \
-       'git-branch o/o &&
-        git-branch o/p &&
-        git-branch -m o/o o'
+       'git branch o/o &&
+        git branch o/p &&
+        git branch -m o/o o'
 
 test_expect_failure \
     'git branch -m q r/q should fail when r exists' \
-       'git-branch q &&
-         git-branch r &&
-         git-branch -m q r/q'
+       'git branch q &&
+         git branch r &&
+         git branch -m q r/q'
 
 mv .git/config .git/config-saved
 
 test_expect_success 'git branch -m q q2 without config should succeed' '
-       git-branch -m q q2 &&
-       git-branch -m q2 q
+       git branch -m q q2 &&
+       git branch -m q2 q
 '
 
 mv .git/config-saved .git/config
 
-git-config branch.s/s.dummy Hello
+git config branch.s/s.dummy Hello
 
 test_expect_success \
     'git branch -m s/s s should work when s/t is deleted' \
-       'git-branch -l s/s &&
+       'git branch -l s/s &&
         test -f .git/logs/refs/heads/s/s &&
-        git-branch -l s/t &&
+        git branch -l s/t &&
         test -f .git/logs/refs/heads/s/t &&
-        git-branch -d s/t &&
-        git-branch -m s/s s &&
+        git branch -d s/t &&
+        git branch -m s/s s &&
         test -f .git/logs/refs/heads/s'
 
 test_expect_success 'config information was renamed, too' \
-       "test $(git-config branch.s.dummy) = Hello &&
-        ! git-config branch.s/s/dummy"
+       "test $(git config branch.s.dummy) = Hello &&
+        ! git config branch.s/s/dummy"
 
 test_expect_failure \
-    'git-branch -m u v should fail when the reflog for u is a symlink' \
-    'git-branch -l u &&
+    '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 &&
      ln -s real-u .git/logs/refs/heads/u &&
-     git-branch -m u v'
+     git branch -m u v'
 
 test_expect_success 'test tracking setup via --track' \
-    'git-config remote.local.url . &&
-     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
-     git-branch --track my1 local/master &&
-     test $(git-config branch.my1.remote) = local &&
-     test $(git-config branch.my1.merge) = refs/heads/master'
+    'git config remote.local.url . &&
+     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+     (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git branch --track my1 local/master &&
+     test $(git config branch.my1.remote) = local &&
+     test $(git config branch.my1.merge) = refs/heads/master'
 
 test_expect_success 'test tracking setup (non-wildcard, matching)' \
-    'git-config remote.local.url . &&
-     git-config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
-     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
-     git-branch --track my4 local/master &&
-     test $(git-config branch.my4.remote) = local &&
-     test $(git-config branch.my4.merge) = refs/heads/master'
+    'git config remote.local.url . &&
+     git config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
+     (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git branch --track my4 local/master &&
+     test $(git config branch.my4.remote) = local &&
+     test $(git config branch.my4.merge) = refs/heads/master'
 
 test_expect_success 'test tracking setup (non-wildcard, not matching)' \
-    'git-config remote.local.url . &&
-     git-config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
-     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
-     git-branch --track my5 local/master &&
-     ! test "$(git-config branch.my5.remote)" = local &&
-     ! test "$(git-config branch.my5.merge)" = refs/heads/master'
+    'git config remote.local.url . &&
+     git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
+     (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git branch --track my5 local/master &&
+     ! test "$(git config branch.my5.remote)" = local &&
+     ! test "$(git config branch.my5.merge)" = refs/heads/master'
 
 test_expect_success 'test tracking setup via config' \
-    'git-config branch.autosetupmerge true &&
-     git-config remote.local.url . &&
-     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
-     git-branch my3 local/master &&
-     test $(git-config branch.my3.remote) = local &&
-     test $(git-config branch.my3.merge) = refs/heads/master'
+    'git config branch.autosetupmerge true &&
+     git config remote.local.url . &&
+     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+     (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git branch my3 local/master &&
+     test $(git config branch.my3.remote) = local &&
+     test $(git config branch.my3.merge) = refs/heads/master'
+
+test_expect_success 'avoid ambiguous track' '
+       git config branch.autosetupmerge true &&
+       git config remote.ambi1.url = lalala &&
+       git config remote.ambi1.fetch = refs/heads/lalala:refs/heads/master &&
+       git config remote.ambi2.url = lilili &&
+       git config remote.ambi2.fetch = refs/heads/lilili:refs/heads/master &&
+       git branch all1 master &&
+       test -z "$(git config branch.all1.merge)"
+'
 
 test_expect_success 'test overriding tracking setup via --no-track' \
-    'git-config branch.autosetupmerge true &&
-     git-config remote.local.url . &&
-     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git-show-ref -q refs/remotes/local/master || git-fetch local) &&
-     git-branch --no-track my2 local/master &&
-     git-config branch.autosetupmerge false &&
-     ! test "$(git-config branch.my2.remote)" = local &&
-     ! test "$(git-config branch.my2.merge)" = refs/heads/master'
-
-test_expect_success 'test local tracking setup' \
+    'git config branch.autosetupmerge true &&
+     git config remote.local.url . &&
+     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+     (git show-ref -q refs/remotes/local/master || git-fetch local) &&
+     git branch --no-track my2 local/master &&
+     git config branch.autosetupmerge false &&
+     ! test "$(git config branch.my2.remote)" = local &&
+     ! test "$(git config branch.my2.merge)" = refs/heads/master'
+
+test_expect_success 'no tracking without .fetch entries' \
     'git branch --track my6 s &&
-     test $(git-config branch.my6.remote) = . &&
-     test $(git-config branch.my6.merge) = refs/heads/s'
+     test -z "$(git config branch.my6.remote)" &&
+     test -z "$(git config branch.my6.merge)"'
 
 test_expect_success 'test tracking setup via --track but deeper' \
-    'git-config remote.local.url . &&
-     git-config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
-     (git-show-ref -q refs/remotes/local/o/o || git-fetch local) &&
-     git-branch --track my7 local/o/o &&
-     test "$(git-config branch.my7.remote)" = local &&
-     test "$(git-config branch.my7.merge)" = refs/heads/o/o'
+    'git config remote.local.url . &&
+     git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+     (git show-ref -q refs/remotes/local/o/o || git-fetch local) &&
+     git branch --track my7 local/o/o &&
+     test "$(git config branch.my7.remote)" = local &&
+     test "$(git config branch.my7.merge)" = refs/heads/o/o'
+
+test_expect_success 'test deleting branch deletes branch config' \
+    'git branch -d my7 &&
+     test -z "$(git config branch.my7.remote)" &&
+     test -z "$(git config branch.my7.merge)"'
+
+test_expect_success 'test deleting branch without config' \
+    'git branch my7 s &&
+     test "$(git branch -d my7 2>&1)" = "Deleted branch my7."'
 
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
index f0c7e22b36c66234e2a46bac659506afb454dfa7..4ddc6342a94b9e7b39153cf5ac93ae3c3fb3c31e 100755 (executable)
@@ -16,92 +16,92 @@ echo '[core] logallrefupdates = true' >>.git/config
 test_expect_success \
     'prepare a trivial repository' \
     'echo Hello > A &&
-     git-update-index --add A &&
+     git update-index --add A &&
      git-commit -m "Initial commit." &&
-     HEAD=$(git-rev-parse --verify HEAD)'
+     HEAD=$(git rev-parse --verify HEAD)'
 
 SHA1=
 
 test_expect_success \
     'see if git show-ref works as expected' \
-    'git-branch a &&
+    'git branch a &&
      SHA1=`cat .git/refs/heads/a` &&
      echo "$SHA1 refs/heads/a" >expect &&
-     git-show-ref a >result &&
+     git show-ref a >result &&
      diff expect result'
 
 test_expect_success \
     'see if a branch still exists when packed' \
-    'git-branch b &&
-     git-pack-refs --all &&
+    'git branch b &&
+     git pack-refs --all &&
      rm -f .git/refs/heads/b &&
      echo "$SHA1 refs/heads/b" >expect &&
-     git-show-ref b >result &&
+     git show-ref b >result &&
      diff expect result'
 
 test_expect_failure \
     'git branch c/d should barf if branch c exists' \
-    'git-branch c &&
-     git-pack-refs --all &&
+    'git branch c &&
+     git pack-refs --all &&
      rm .git/refs/heads/c &&
-     git-branch c/d'
+     git branch c/d'
 
 test_expect_success \
     'see if a branch still exists after git pack-refs --prune' \
-    'git-branch e &&
-     git-pack-refs --all --prune &&
+    'git branch e &&
+     git pack-refs --all --prune &&
      echo "$SHA1 refs/heads/e" >expect &&
-     git-show-ref e >result &&
+     git show-ref e >result &&
      diff expect result'
 
 test_expect_failure \
     'see if git pack-refs --prune remove ref files' \
-    'git-branch f &&
-     git-pack-refs --all --prune &&
+    'git branch f &&
+     git pack-refs --all --prune &&
      ls .git/refs/heads/f'
 
 test_expect_success \
     'git branch g should work when git branch g/h has been deleted' \
-    'git-branch g/h &&
-     git-pack-refs --all --prune &&
-     git-branch -d g/h &&
-     git-branch g &&
-     git-pack-refs --all &&
-     git-branch -d g'
+    'git branch g/h &&
+     git pack-refs --all --prune &&
+     git branch -d g/h &&
+     git branch g &&
+     git pack-refs --all &&
+     git branch -d g'
 
 test_expect_failure \
     'git branch i/j/k should barf if branch i exists' \
-    'git-branch i &&
-     git-pack-refs --all --prune &&
-     git-branch i/j/k'
+    'git branch i &&
+     git pack-refs --all --prune &&
+     git branch i/j/k'
 
 test_expect_success \
     'test git branch k after branch k/l/m and k/lm have been deleted' \
-    'git-branch k/l &&
-     git-branch k/lm &&
-     git-branch -d k/l &&
-     git-branch k/l/m &&
-     git-branch -d k/l/m &&
-     git-branch -d k/lm &&
-     git-branch k'
+    'git branch k/l &&
+     git branch k/lm &&
+     git branch -d k/l &&
+     git branch k/l/m &&
+     git branch -d k/l/m &&
+     git branch -d k/lm &&
+     git branch k'
 
 test_expect_success \
     'test git branch n after some branch deletion and pruning' \
-    'git-branch n/o &&
-     git-branch n/op &&
-     git-branch -d n/o &&
-     git-branch n/o/p &&
-     git-branch -d n/op &&
-     git-pack-refs --all --prune &&
-     git-branch -d n/o/p &&
-     git-branch n'
+    'git branch n/o &&
+     git branch n/op &&
+     git branch -d n/o &&
+     git branch n/o/p &&
+     git branch -d n/op &&
+     git pack-refs --all --prune &&
+     git branch -d n/o/p &&
+     git branch n'
 
 test_expect_success 'pack, prune and repack' '
        git-tag foo &&
-       git-pack-refs --all --prune &&
-       git-show-ref >all-of-them &&
-       git-pack-refs &&
-       git-show-ref >again &&
+       git pack-refs --all --prune &&
+       git show-ref >all-of-them &&
+       git pack-refs &&
+       git show-ref >again &&
        diff all-of-them again
 '
 
index b5a1400e18b0fa5190fde4d0b60d1563581b2a19..dc8c36931098b85d802284d33dfc8f8ebaa5e6f5 100755 (executable)
@@ -32,12 +32,12 @@ test -f "$p1" && cmp "$p0" "$p1" || {
 
 echo 'just space
 no-funny' >expected
-test_expect_success 'git-ls-files no-funny' \
-       'git-update-index --add "$p0" "$p2" &&
-       git-ls-files >current &&
+test_expect_success 'git ls-files no-funny' \
+       'git update-index --add "$p0" "$p2" &&
+       git ls-files >current &&
        git diff expected current'
 
-t0=`git-write-tree`
+t0=`git write-tree`
 echo "$t0" >t0
 
 cat > expected <<\EOF
@@ -45,19 +45,19 @@ just space
 no-funny
 "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git-ls-files with-funny' \
-       'git-update-index --add "$p1" &&
-       git-ls-files >current &&
+test_expect_success 'git ls-files with-funny' \
+       'git update-index --add "$p1" &&
+       git ls-files >current &&
        git diff expected current'
 
 echo 'just space
 no-funny
 tabs   ," (dq) and spaces' >expected
-test_expect_success 'git-ls-files -z with-funny' \
-       'git-ls-files -z | tr \\0 \\012 >current &&
+test_expect_success 'git ls-files -z with-funny' \
+       'git ls-files -z | tr \\0 \\012 >current &&
        git diff expected current'
 
-t1=`git-write-tree`
+t1=`git write-tree`
 echo "$t1" >t1
 
 cat > expected <<\EOF
@@ -65,45 +65,45 @@ just space
 no-funny
 "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git-ls-tree with funny' \
-       'git-ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
+test_expect_success 'git ls-tree with funny' \
+       'git ls-tree -r $t1 | sed -e "s/^[^     ]*      //" >current &&
         git diff expected current'
 
 cat > expected <<\EOF
 A      "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git-diff-index with-funny' \
-       'git-diff-index --name-status $t0 >current &&
+test_expect_success 'git diff-index with-funny' \
+       'git diff-index --name-status $t0 >current &&
        git diff expected current'
 
-test_expect_success 'git-diff-tree with-funny' \
-       'git-diff-tree --name-status $t0 $t1 >current &&
+test_expect_success 'git diff-tree with-funny' \
+       'git diff-tree --name-status $t0 $t1 >current &&
        git diff expected current'
 
 echo 'A
 tabs   ," (dq) and spaces' >expected
-test_expect_success 'git-diff-index -z with-funny' \
-       'git-diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
+test_expect_success 'git diff-index -z with-funny' \
+       'git diff-index -z --name-status $t0 | tr \\0 \\012 >current &&
        git diff expected current'
 
-test_expect_success 'git-diff-tree -z with-funny' \
-       'git-diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
+test_expect_success 'git diff-tree -z with-funny' \
+       'git diff-tree -z --name-status $t0 $t1 | tr \\0 \\012 >current &&
        git diff expected current'
 
 cat > expected <<\EOF
 CNUM   no-funny        "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git-diff-tree -C with-funny' \
-       'git-diff-tree -C --find-copies-harder --name-status \
+test_expect_success 'git diff-tree -C with-funny' \
+       'git diff-tree -C --find-copies-harder --name-status \
                $t0 $t1 | sed -e 's/^C[0-9]*/CNUM/' >current &&
        git diff expected current'
 
 cat > expected <<\EOF
 RNUM   no-funny        "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git-diff-tree delete with-funny' \
-       'git-update-index --force-remove "$p0" &&
-       git-diff-index -M --name-status \
+test_expect_success 'git diff-tree delete with-funny' \
+       'git update-index --force-remove "$p0" &&
+       git diff-index -M --name-status \
                $t0 | sed -e 's/^R[0-9]*/RNUM/' >current &&
        git diff expected current'
 
@@ -113,8 +113,8 @@ similarity index NUM%
 rename from no-funny
 rename to "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git-diff-tree delete with-funny' \
-       'git-diff-index -M -p $t0 |
+test_expect_success 'git diff-tree delete with-funny' \
+       'git diff-index -M -p $t0 |
         sed -e "s/index [0-9]*%/index NUM%/" >current &&
         git diff expected current'
 
@@ -127,8 +127,8 @@ similarity index NUM%
 rename from no-funny
 rename to "tabs\t,\" (dq) and spaces"
 EOF
-test_expect_success 'git-diff-tree delete with-funny' \
-       'git-diff-index -M -p $t0 |
+test_expect_success 'git diff-tree delete with-funny' \
+       'git diff-index -M -p $t0 |
         sed -e "s/index [0-9]*%/index NUM%/" >current &&
         git diff expected current'
 
@@ -136,9 +136,9 @@ cat >expected <<\EOF
  "tabs\t,\" (dq) and spaces"
  1 files changed, 0 insertions(+), 0 deletions(-)
 EOF
-test_expect_success 'git-diff-tree rename with-funny applied' \
-       'git-diff-index -M -p $t0 |
-        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+test_expect_success 'git diff-tree rename with-funny applied' \
+       'git diff-index -M -p $t0 |
+        git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
         git diff expected current'
 
 cat > expected <<\EOF
@@ -146,15 +146,15 @@ cat > expected <<\EOF
  "tabs\t,\" (dq) and spaces"
  2 files changed, 3 insertions(+), 3 deletions(-)
 EOF
-test_expect_success 'git-diff-tree delete with-funny applied' \
-       'git-diff-index -p $t0 |
-        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+test_expect_success 'git diff-tree delete with-funny applied' \
+       'git diff-index -p $t0 |
+        git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
         git diff expected current'
 
-test_expect_success 'git-apply non-git diff' \
-       'git-diff-index -p $t0 |
+test_expect_success 'git apply non-git diff' \
+       'git diff-index -p $t0 |
         sed -ne "/^[-+@]/p" |
-        git-apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
+        git apply --stat | sed -e "s/|.*//" -e "s/ *\$//" >current &&
         git diff expected current'
 
 test_done
index b9d3131cc2167df027d4d6eebc87ad51223c8f34..62205b25312812f3197b38a655d92da4fa7c0a91 100755 (executable)
@@ -12,23 +12,60 @@ This test runs git rebase and checks that the author information is not lost.
 export GIT_AUTHOR_EMAIL=bogus_email_address
 
 test_expect_success \
-    'prepare repository with topic branch, then rebase against master' \
+    'prepare repository with topic branches' \
     'echo First > A &&
-     git-update-index --add A &&
+     git update-index --add A &&
      git-commit -m "Add A." &&
      git checkout -b my-topic-branch &&
      echo Second > B &&
-     git-update-index --add B &&
+     git update-index --add B &&
      git-commit -m "Add B." &&
      git checkout -f master &&
      echo Third >> A &&
-     git-update-index A &&
+     git update-index A &&
      git-commit -m "Modify A." &&
+     git checkout -b side my-topic-branch &&
+     echo Side >> C &&
+     git add C &&
+     git commit -m "Add C" &&
+     git checkout -b nonlinear my-topic-branch &&
+     echo Edit >> B &&
+     git add B &&
+     git commit -m "Modify B" &&
+     git merge side &&
+     git checkout -b upstream-merged-nonlinear &&
+     git merge master &&
      git checkout -f my-topic-branch &&
+     git tag topic
+'
+
+test_expect_success 'rebase against master' '
      git rebase master'
 
 test_expect_failure \
     'the rebase operation should not have destroyed author information' \
     'git log | grep "Author:" | grep "<>"'
 
+test_expect_success 'rebase after merge master' '
+     git reset --hard topic &&
+     git merge master &&
+     git rebase master &&
+     ! git show | grep "^Merge:"
+'
+
+test_expect_success 'rebase of history with merges is linearized' '
+     git checkout nonlinear &&
+     test 4 = $(git rev-list master.. | wc -l) &&
+     git rebase master &&
+     test 3 = $(git rev-list master.. | wc -l)
+'
+
+test_expect_success \
+    'rebase of history with merges after upstream merge is linearized' '
+     git checkout upstream-merged-nonlinear &&
+     test 5 = $(git rev-list master.. | wc -l) &&
+     git rebase master &&
+     test 3 = $(git rev-list master.. | wc -l)
+'
+
 test_done
index 8b19d3ccea5a0d5ff697661202347fbdfc6c330a..4934a4e01092e27e07254b10ed3e54e8699b54ac 100755 (executable)
@@ -14,37 +14,37 @@ local branch.
 test_expect_success \
     'prepare repository with topic branch' \
     'echo First > A &&
-     git-update-index --add A &&
+     git update-index --add A &&
      git-commit -m "Add A." &&
 
      git-checkout -b my-topic-branch &&
 
      echo Second > B &&
-     git-update-index --add B &&
+     git update-index --add B &&
      git-commit -m "Add B." &&
 
      echo AnotherSecond > C &&
-     git-update-index --add C &&
+     git update-index --add C &&
      git-commit -m "Add C." &&
 
      git-checkout -f master &&
 
      echo Third >> A &&
-     git-update-index A &&
+     git update-index A &&
      git-commit -m "Modify A."
 '
 
 test_expect_success \
     'pick top patch from topic branch into master' \
-    'git-cherry-pick my-topic-branch^0 &&
+    'git cherry-pick my-topic-branch^0 &&
      git-checkout -f my-topic-branch &&
-     git-branch master-merge master &&
-     git-branch my-topic-branch-merge my-topic-branch
+     git branch master-merge master &&
+     git branch my-topic-branch-merge my-topic-branch
 '
 
 test_debug \
-    'git-cherry master &&
-     git-format-patch -k --stdout --full-index master >/dev/null &&
+    'git cherry master &&
+     git format-patch -k --stdout --full-index master >/dev/null &&
      gitk --all & sleep 1
 '
 
index 977c498f00f2d8e2066bd586130ee84fe1a196a3..eab053c3e0e1cbe82bc7824d43109ba56acdb54d 100755 (executable)
@@ -49,9 +49,8 @@ test_expect_success 'rebase --skip with --merge' '
        '
 
 test_expect_success 'merge and reference trees equal' \
-       'test -z "`git-diff-tree skip-merge skip-reference`"'
+       'test -z "`git diff-tree skip-merge skip-reference`"'
 
 test_debug 'gitk --all & sleep 1'
 
 test_done
-
diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh
new file mode 100755 (executable)
index 0000000..40d6799
--- /dev/null
@@ -0,0 +1,279 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='git rebase interactive
+
+This test runs git rebase "interactively", by faking an edit, and verifies
+that the result still makes sense.
+'
+. ./test-lib.sh
+
+# set up two branches like this:
+#
+# A - B - C - D - E
+#   \
+#     F - G - H
+#       \
+#         I
+#
+# 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 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 &&
+       : > file5 &&
+       git add file5 &&
+       test_tick &&
+       git commit -m H &&
+       git checkout -b branch2 F &&
+       : > file6 &&
+       git add file6 &&
+       test_tick &&
+       git commit -m I &&
+       git tag I
+'
+
+cat > fake-editor.sh <<\EOF
+#!/bin/sh
+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)
+               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
+
+chmod a+x fake-editor.sh
+VISUAL="$(pwd)/fake-editor.sh"
+export VISUAL
+
+test_expect_success 'no changes are a nop' '
+       git rebase -i F &&
+       test $(git rev-parse I) = $(git rev-parse HEAD)
+'
+
+test_expect_success 'test the [branch] option' '
+       git checkout -b dead-end &&
+       git rm file6 &&
+       git commit -m "stop here" &&
+       git rebase -i F branch2 &&
+       test $(git rev-parse I) = $(git rev-parse HEAD)
+'
+
+test_expect_success 'rebase on top of a non-conflicting commit' '
+       git checkout branch1 &&
+       git tag original-branch1 &&
+       git rebase -i branch2 &&
+       test file6 = $(git diff --name-only original-branch1) &&
+       test $(git rev-parse I) = $(git rev-parse HEAD~2)
+'
+
+test_expect_success 'reflog for the branch shows state before rebase' '
+       test $(git rev-parse branch1@{1}) = $(git rev-parse original-branch1)
+'
+
+test_expect_success 'exchange two commits' '
+       FAKE_LINES="2 1" git rebase -i HEAD~2 &&
+       test H = $(git cat-file commit HEAD^ | tail -n 1) &&
+       test G = $(git cat-file commit HEAD | tail -n 1)
+'
+
+cat > expect << EOF
+diff --git a/file1 b/file1
+index e69de29..00750ed 100644
+--- a/file1
++++ b/file1
+@@ -0,0 +1 @@
++3
+EOF
+
+cat > expect2 << EOF
+<<<<<<< HEAD:file1
+2
+=======
+3
+>>>>>>> b7ca976... G:file1
+EOF
+
+test_expect_success 'stop on conflicting pick' '
+       git tag new-branch1 &&
+       ! git rebase -i master &&
+       diff -u expect .git/.dotest-merge/patch &&
+       diff -u expect2 file1 &&
+       test 4 = $(grep -v "^#" < .git/.dotest-merge/done | wc -l) &&
+       test 0 = $(grep -v "^#" < .git/.dotest-merge/todo | wc -l)
+'
+
+test_expect_success 'abort' '
+       git rebase --abort &&
+       test $(git rev-parse new-branch1) = $(git rev-parse HEAD) &&
+       ! test -d .git/.dotest-merge
+'
+
+test_expect_success 'retain authorship' '
+       echo A > file7 &&
+       git add file7 &&
+       test_tick &&
+       GIT_AUTHOR_NAME="Twerp Snog" git commit -m "different author" &&
+       git tag twerp &&
+       git rebase -i --onto master HEAD^ &&
+       git show HEAD | grep "^Author: Twerp Snog"
+'
+
+test_expect_success 'squash' '
+       git reset --hard twerp &&
+       echo B > file7 &&
+       test_tick &&
+       GIT_AUTHOR_NAME="Nitfol" git commit -m "nitfol" file7 &&
+       echo "******************************" &&
+       FAKE_LINES="1 squash 2" git rebase -i --onto master HEAD~2 &&
+       test B = $(cat file7) &&
+       test $(git rev-parse HEAD^) = $(git rev-parse master)
+'
+
+test_expect_success 'retain authorship when squashing' '
+       git show HEAD | grep "^Author: Nitfol"
+'
+
+test_expect_success 'preserve merges with -p' '
+       git checkout -b to-be-preserved master^ &&
+       : > unrelated-file &&
+       git add unrelated-file &&
+       test_tick &&
+       git commit -m "unrelated" &&
+       git checkout -b to-be-rebased master &&
+       echo B > file1 &&
+       test_tick &&
+       git commit -m J file1 &&
+       test_tick &&
+       git merge to-be-preserved &&
+       echo C > file1 &&
+       test_tick &&
+       git commit -m K file1 &&
+       test_tick &&
+       git rebase -i -p --onto branch1 master &&
+       test $(git rev-parse HEAD^^2) = $(git rev-parse to-be-preserved) &&
+       test $(git rev-parse HEAD~3) = $(git rev-parse branch1) &&
+       test $(git show HEAD:file1) = C &&
+       test $(git show HEAD~2:file1) = B
+'
+
+test_expect_success '--continue tries to commit' '
+       test_tick &&
+       ! git rebase -i --onto new-branch1 HEAD^ &&
+       echo resolved > file1 &&
+       git add file1 &&
+       FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
+       test $(git rev-parse HEAD^) = $(git rev-parse new-branch1) &&
+       git show HEAD | grep chouette
+'
+
+test_expect_success 'verbose flag is heeded, even after --continue' '
+       git reset --hard HEAD@{1} &&
+       test_tick &&
+       ! git rebase -v -i --onto new-branch1 HEAD^ &&
+       echo resolved > file1 &&
+       git add file1 &&
+       git rebase --continue > output &&
+       grep "^ file1 |    2 +-$" output
+'
+
+test_expect_success 'multi-squash only fires up editor once' '
+       base=$(git rev-parse HEAD~4) &&
+       FAKE_COMMIT_AMEND="ONCE" FAKE_LINES="1 squash 2 squash 3 squash 4" \
+               git rebase -i $base &&
+       test $base = $(git rev-parse HEAD^) &&
+       test 1 = $(git show | grep ONCE | wc -l)
+'
+
+test_expect_success 'squash works as expected' '
+       for n in one two three four
+       do
+               echo $n >> file$n &&
+               git add file$n &&
+               git commit -m $n
+       done &&
+       one=$(git rev-parse HEAD~3) &&
+       FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+       test $one = $(git rev-parse HEAD~2)
+'
+
+test_expect_success 'interrupted squash works as expected' '
+       for n in one two three four
+       do
+               echo $n >> conflict &&
+               git add conflict &&
+               git commit -m $n
+       done &&
+       one=$(git rev-parse HEAD~3) &&
+       ! FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 &&
+       (echo one; echo two; echo four) > conflict &&
+       git add conflict &&
+       ! git rebase --continue &&
+       echo resolved > conflict &&
+       git add conflict &&
+       git rebase --continue &&
+       test $one = $(git rev-parse HEAD~2)
+'
+
+test_expect_success 'ignore patch if in upstream' '
+       HEAD=$(git rev-parse HEAD) &&
+       git checkout -b has-cherry-picked HEAD^ &&
+       echo unrelated > file7 &&
+       git add file7 &&
+       test_tick &&
+       git commit -m "unrelated change" &&
+       git cherry-pick $HEAD &&
+       EXPECT_COUNT=1 git rebase -i $HEAD &&
+       test $HEAD = $(git rev-parse HEAD^)
+'
+
+test_done
diff --git a/t/t3405-rebase-malformed.sh b/t/t3405-rebase-malformed.sh
new file mode 100755 (executable)
index 0000000..e4e2e64
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+test_description='rebase should not insist on git message convention'
+
+. ./test-lib.sh
+
+cat >F <<\EOF
+This is an example of a commit log message
+that does not  conform to git commit convention.
+
+It has two paragraphs, but its first paragraph is not friendly
+to oneline summary format.
+EOF
+
+test_expect_success setup '
+
+       >file1 &&
+       >file2 &&
+       git add file1 file2 &&
+       test_tick &&
+       git commit -m "Initial commit" &&
+
+       git checkout -b side &&
+       cat F >file2 &&
+       git add file2 &&
+       test_tick &&
+       git commit -F F &&
+
+       git cat-file commit HEAD | sed -e "1,/^\$/d" >F0 &&
+
+       git checkout master &&
+
+       echo One >file1 &&
+       test_tick &&
+       git add file1 &&
+       git commit -m "Second commit"
+'
+
+test_expect_success rebase '
+
+       git rebase master side &&
+       git cat-file commit HEAD | sed -e "1,/^\$/d" >F1 &&
+
+       diff -u F0 F1 &&
+       diff -u F F0
+'
+
+test_done
index e83bbee07402d864a7241d4edbf5d393dcccdd4a..d0a440feba5f501a2320d52b36d1bb51ca8944dd 100755 (executable)
@@ -3,10 +3,10 @@
 # Copyright (c) 2006 Yann Dirson, based on t3400 by Amos Waterland
 #
 
-test_description='git-cherry should detect patches integrated upstream
+test_description='git cherry should detect patches integrated upstream
 
 This test cherry-picks one local change of two into master branch, and
-checks that git-cherry only returns the second patch in the local branch
+checks that git cherry only returns the second patch in the local branch
 '
 . ./test-lib.sh
 
@@ -15,40 +15,40 @@ export GIT_AUTHOR_EMAIL=bogus_email_address
 test_expect_success \
     'prepare repository with topic branch, and check cherry finds the 2 patches from there' \
     'echo First > A &&
-     git-update-index --add A &&
+     git update-index --add A &&
      git-commit -m "Add A." &&
 
      git-checkout -b my-topic-branch &&
 
      echo Second > B &&
-     git-update-index --add B &&
+     git update-index --add B &&
      git-commit -m "Add B." &&
 
      sleep 2 &&
      echo AnotherSecond > C &&
-     git-update-index --add C &&
+     git update-index --add C &&
      git-commit -m "Add C." &&
 
      git-checkout -f master &&
      rm -f B C &&
 
      echo Third >> A &&
-     git-update-index A &&
+     git update-index A &&
      git-commit -m "Modify A." &&
 
-     expr "$(echo $(git-cherry master my-topic-branch) )" : "+ [^ ]* + .*"
+     expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* + .*"
 '
 
 test_expect_success \
     'check that cherry with limit returns only the top patch'\
-    'expr "$(echo $(git-cherry master my-topic-branch my-topic-branch^1) )" : "+ [^ ]*"
+    'expr "$(echo $(git cherry master my-topic-branch my-topic-branch^1) )" : "+ [^ ]*"
 '
 
 test_expect_success \
     'cherry-pick one of the 2 patches, and check cherry recognized one and only one as new' \
-    'git-cherry-pick my-topic-branch^0 &&
-     echo $(git-cherry master my-topic-branch) &&
-     expr "$(echo $(git-cherry master my-topic-branch) )" : "+ [^ ]* - .*"
+    'git cherry-pick my-topic-branch^0 &&
+     echo $(git cherry master my-topic-branch) &&
+     expr "$(echo $(git cherry master my-topic-branch) )" : "+ [^ ]* - .*"
 '
 
 test_done
index 0a97b75288d44cf93e0a8f8d9ab1b76715f946d1..5c001aa4897a02098904f2c492b23690a9e1f6ab 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Carl D. Worth
 #
 
-test_description='Test of the various options to git-rm.'
+test_description='Test of the various options to git rm.'
 
 . ./test-lib.sh
 
@@ -11,13 +11,13 @@ test_description='Test of the various options to git-rm.'
 test_expect_success \
     'Initialize test directory' \
     "touch -- foo bar baz 'space embedded' -q &&
-     git-add -- 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-add -- 'tab   embedded' 'newline
+     git add -- 'tab   embedded' 'newline
 embedded' &&
      git-commit -m 'add files with tabs and newlines'
      else
@@ -26,7 +26,7 @@ embedded' &&
      fi"
 
 # 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
+# git rm barfs, but if the test is run as root that cannot be
 # arranged.
 test_expect_success \
     'Determine rm behavior' \
@@ -38,51 +38,85 @@ test_expect_success \
      rm -f test-file'
 
 test_expect_success \
-    'Pre-check that foo exists and is in index before git-rm foo' \
-    '[ -f foo ] && git-ls-files --error-unmatch foo'
+    'Pre-check that foo exists and is in index before git rm foo' \
+    '[ -f foo ] && git ls-files --error-unmatch foo'
 
 test_expect_success \
-    'Test that git-rm foo succeeds' \
-    'git-rm --cached foo'
+    'Test that git rm foo succeeds' \
+    'git rm --cached foo'
 
 test_expect_success \
-    'Post-check that foo exists but is not in index after git-rm foo' \
-    '[ -f foo ] && ! git-ls-files --error-unmatch foo'
+    'Test that git rm --cached foo succeeds if the index matches the file' \
+    'echo content > foo
+     git add foo
+     git rm --cached foo'
 
 test_expect_success \
-    'Pre-check that bar exists and is in index before "git-rm bar"' \
-    '[ -f bar ] && git-ls-files --error-unmatch bar'
+    'Test that git rm --cached foo succeeds if the index matches the file' \
+    'echo content > foo
+     git add foo
+     git commit -m foo
+     echo "other content" > foo
+     git rm --cached foo'
+
+test_expect_failure \
+    'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' \
+    'echo content > foo
+     git add foo
+     git commit -m foo
+     echo "other content" > foo
+     git add foo
+     echo "yet another content" > foo
+     git rm --cached foo'
+
+test_expect_success \
+    'Test that git rm --cached -f foo works in case where --cached only did not' \
+    'echo content > foo
+     git add foo
+     git commit -m foo
+     echo "other content" > foo
+     git add foo
+     echo "yet another content" > foo
+     git rm --cached -f foo'
+
+test_expect_success \
+    'Post-check that foo exists but is not in index after git rm foo' \
+    '[ -f foo ] && ! git ls-files --error-unmatch foo'
+
+test_expect_success \
+    'Pre-check that bar exists and is in index before "git rm bar"' \
+    '[ -f bar ] && git ls-files --error-unmatch bar'
 
 test_expect_success \
-    'Test that "git-rm bar" succeeds' \
-    'git-rm bar'
+    'Test that "git rm bar" succeeds' \
+    'git rm bar'
 
 test_expect_success \
-    'Post-check that bar does not exist and is not in index after "git-rm -f bar"' \
-    '! [ -f bar ] && ! git-ls-files --error-unmatch bar'
+    'Post-check that bar does not exist and is not in index after "git rm -f bar"' \
+    '! [ -f bar ] && ! git ls-files --error-unmatch bar'
 
 test_expect_success \
-    'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \
-    'git-rm -- -q'
+    '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 that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \
-    "git-rm -f 'space embedded' 'tab   embedded' 'newline
+    "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_failure \
-    'Test that "git-rm -f" fails if its rm fails' \
-    'git-rm -f baz'
+    'Test that "git rm -f" fails if its rm fails' \
+    'git rm -f baz'
 chmod 775 .
 else
     test_expect_success 'skipping removal failure (perhaps running as root?)' :
 fi
 
 test_expect_success \
-    'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
-    'git-ls-files --error-unmatch baz'
+    'When the rm in "git rm -f" fails, it should not remove the file from the index' \
+    'git ls-files --error-unmatch baz'
 
 test_expect_success 'Remove nonexistent file with --ignore-unmatch' '
        git rm --ignore-unmatch nonexistent
index ad8cc7d4ae88e2066d2f51b6a6a5a192780d5e9a..a328bf57eb67fd96a88020f02948d380ec699496 100755 (executable)
@@ -3,72 +3,72 @@
 # Copyright (c) 2006 Carl D. Worth
 #
 
-test_description='Test of git-add, including the -- option.'
+test_description='Test of git add, including the -- option.'
 
 . ./test-lib.sh
 
 test_expect_success \
-    'Test of git-add' \
-    'touch foo && git-add foo'
+    'Test of git add' \
+    'touch foo && git add foo'
 
 test_expect_success \
     'Post-check that foo is in the index' \
-    'git-ls-files foo | grep foo'
+    'git ls-files foo | grep foo'
 
 test_expect_success \
-    'Test that "git-add -- -q" works' \
-    'touch -- -q && git-add -- -q'
+    'Test that "git add -- -q" works' \
+    'touch -- -q && git add -- -q'
 
 test_expect_success \
-       'git-add: Test that executable bit is not used if core.filemode=0' \
+       'git add: Test that executable bit is not used if core.filemode=0' \
        'git config core.filemode 0 &&
         echo foo >xfoo1 &&
         chmod 755 xfoo1 &&
-        git-add xfoo1 &&
-        case "`git-ls-files --stage xfoo1`" in
+        git add xfoo1 &&
+        case "`git ls-files --stage xfoo1`" in
         100644" "*xfoo1) echo ok;;
-        *) echo fail; git-ls-files --stage xfoo1; (exit 1);;
+        *) 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 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo1 &&
        ln -s foo xfoo1 &&
-       git-add xfoo1 &&
-       case "`git-ls-files --stage xfoo1`" in
+       git add xfoo1 &&
+       case "`git ls-files --stage xfoo1`" in
        120000" "*xfoo1) echo ok;;
-       *) echo fail; git-ls-files --stage xfoo1; (exit 1);;
+       *) echo fail; git ls-files --stage xfoo1; (exit 1);;
        esac
 '
 
 test_expect_success \
-       'git-update-index --add: Test that executable bit is not used...' \
+       'git update-index --add: Test that executable bit is not used...' \
        'git config core.filemode 0 &&
         echo foo >xfoo2 &&
         chmod 755 xfoo2 &&
-        git-update-index --add xfoo2 &&
-        case "`git-ls-files --stage xfoo2`" in
+        git update-index --add xfoo2 &&
+        case "`git ls-files --stage xfoo2`" in
         100644" "*xfoo2) echo ok;;
-        *) echo fail; git-ls-files --stage xfoo2; (exit 1);;
+        *) 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 'git add: filemode=0 should not get confused by symlink' '
        rm -f xfoo2 &&
        ln -s foo xfoo2 &&
        git update-index --add xfoo2 &&
-       case "`git-ls-files --stage xfoo2`" in
+       case "`git ls-files --stage xfoo2`" in
        120000" "*xfoo2) echo ok;;
-       *) echo fail; git-ls-files --stage xfoo2; (exit 1);;
+       *) echo fail; git ls-files --stage xfoo2; (exit 1);;
        esac
 '
 
 test_expect_success \
-       'git-update-index --add: Test that executable bit is not used...' \
+       'git update-index --add: Test that executable bit is not used...' \
        'git config core.filemode 0 &&
         ln -s xfoo2 xfoo3 &&
-        git-update-index --add xfoo3 &&
-        case "`git-ls-files --stage xfoo3`" in
+        git update-index --add xfoo3 &&
+        case "`git ls-files --stage xfoo3`" in
         120000" "*xfoo3) echo ok;;
-        *) echo fail; git-ls-files --stage xfoo3; (exit 1);;
+        *) echo fail; git ls-files --stage xfoo3; (exit 1);;
         esac'
 
 test_expect_success '.gitignore test setup' '
@@ -80,28 +80,28 @@ test_expect_success '.gitignore test setup' '
 '
 
 test_expect_success '.gitignore is honored' '
-       git-add . &&
-       ! git-ls-files | grep "\\.ig"
+       git add . &&
+       ! git ls-files | grep "\\.ig"
 '
 
 test_expect_success 'error out when attempting to add ignored ones without -f' '
-       ! git-add a.?? &&
-       ! git-ls-files | grep "\\.ig"
+       ! git add a.?? &&
+       ! git ls-files | grep "\\.ig"
 '
 
 test_expect_success 'error out when attempting to add ignored ones without -f' '
-       ! git-add d.?? &&
-       ! git-ls-files | grep "\\.ig"
+       ! git add d.?? &&
+       ! git ls-files | grep "\\.ig"
 '
 
 test_expect_success 'add ignored ones with -f' '
-       git-add -f a.?? &&
-       git-ls-files --error-unmatch a.ig
+       git add -f a.?? &&
+       git ls-files --error-unmatch a.ig
 '
 
 test_expect_success 'add ignored ones with -f' '
-       git-add -f d.??/* &&
-       git-ls-files --error-unmatch d.ig/d.if d.ig/d.ig
+       git add -f d.??/* &&
+       git ls-files --error-unmatch d.ig/d.if d.ig/d.ig
 '
 
 mkdir 1 1/2 1/3
@@ -110,4 +110,49 @@ test_expect_success 'check correct prefix detection' '
        git add 1/2/a 1/3/b 1/2/c
 '
 
+test_expect_success 'git add with filemode=0, symlinks=0, and unmerged entries' '
+       for s in 1 2 3
+       do
+               echo $s > stage$s
+               echo "100755 $(git hash-object -w stage$s) $s   file"
+               echo "120000 $(printf $s | git hash-object -w -t blob --stdin) $s       symlink"
+       done | git update-index --index-info &&
+       git config core.filemode 0 &&
+       git config core.symlinks 0 &&
+       echo new > file &&
+       echo new > symlink &&
+       git add file symlink &&
+       git ls-files --stage | grep "^100755 .* 0       file$" &&
+       git ls-files --stage | grep "^120000 .* 0       symlink$"
+'
+
+test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over stage 1' '
+       git rm --cached -f file symlink &&
+       (
+               echo "100644 $(git hash-object -w stage1) 1     file"
+               echo "100755 $(git hash-object -w stage2) 2     file"
+               echo "100644 $(printf 1 | git hash-object -w -t blob --stdin) 1 symlink"
+               echo "120000 $(printf 2 | git hash-object -w -t blob --stdin) 2 symlink"
+       ) | git update-index --index-info &&
+       git config core.filemode 0 &&
+       git config core.symlinks 0 &&
+       echo new > file &&
+       echo new > symlink &&
+       git add file symlink &&
+       git ls-files --stage | grep "^100755 .* 0       file$" &&
+       git ls-files --stage | grep "^120000 .* 0       symlink$"
+'
+
+test_expect_success 'git add --refresh' '
+       >foo && git add foo && git commit -a -m "commit all" &&
+       test -z "`git diff-index HEAD -- foo`" &&
+       git read-tree HEAD &&
+       case "`git diff-index HEAD -- foo`" in
+       :100644" "*"M   foo") echo ok;;
+       *) echo fail; (exit 1);;
+       esac &&
+       git add --refresh -- foo &&
+       test -z "`git diff-index HEAD -- foo`"
+'
+
 test_done
index 7c7e4335d6e941a79d85c11502178b36331785a2..261f199a0cb32864bbb3cf103ddd2a63e62d2590 100755 (executable)
@@ -22,9 +22,9 @@ check_verify_failure () {
 # first create a commit, so we have a valid object/type
 # for the tag.
 echo Hello >A
-git-update-index --add A
+git update-index --add A
 git-commit -m "Initial commit"
-head=$(git-rev-parse --verify HEAD)
+head=$(git rev-parse --verify HEAD)
 
 ############################################################
 #  1. length check
index ffddb68db3abbf1ab14bf99f3c9514a7ee0c276b..fcbabe8ec32d61888e6cb28d393381066d1b0f3c 100755 (executable)
@@ -8,28 +8,28 @@ test_description='commit and log output encodings'
 . ./test-lib.sh
 
 compare_with () {
-       git-show -s $1 | sed -e '1,/^$/d' -e 's/^    //' -e '$d' >current &&
+       git show -s $1 | sed -e '1,/^$/d' -e 's/^    //' -e '$d' >current &&
        git diff current "$2"
 }
 
 test_expect_success setup '
        : >F &&
-       git-add F &&
-       T=$(git-write-tree) &&
-       C=$(git-commit-tree $T <../t3900/1-UTF-8.txt) &&
-       git-update-ref HEAD $C &&
+       git add F &&
+       T=$(git write-tree) &&
+       C=$(git commit-tree $T <../t3900/1-UTF-8.txt) &&
+       git update-ref HEAD $C &&
        git-tag C0
 '
 
 test_expect_success 'no encoding header for base case' '
-       E=$(git-cat-file commit C0 | sed -ne "s/^encoding //p") &&
+       E=$(git cat-file commit C0 | sed -ne "s/^encoding //p") &&
        test z = "z$E"
 '
 
 for H in ISO-8859-1 EUCJP ISO-2022-JP
 do
        test_expect_success "$H setup" '
-               git-config i18n.commitencoding $H &&
+               git config i18n.commitencoding $H &&
                git-checkout -b $H C0 &&
                echo $H >F &&
                git-commit -a -F ../t3900/$H.txt
@@ -39,21 +39,21 @@ done
 for H in ISO-8859-1 EUCJP ISO-2022-JP
 do
        test_expect_success "check encoding header for $H" '
-               E=$(git-cat-file commit '$H' | sed -ne "s/^encoding //p") &&
+               E=$(git cat-file commit '$H' | sed -ne "s/^encoding //p") &&
                test "z$E" = "z'$H'"
        '
 done
 
 test_expect_success 'config to remove customization' '
-       git-config --unset-all i18n.commitencoding &&
-       if Z=$(git-config --get-all i18n.commitencoding)
+       git config --unset-all i18n.commitencoding &&
+       if Z=$(git config --get-all i18n.commitencoding)
        then
                echo Oops, should have failed.
                false
        else
                test z = "z$Z"
        fi &&
-       git-config i18n.commitencoding utf-8
+       git config i18n.commitencoding utf-8
 '
 
 test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
@@ -68,8 +68,8 @@ do
 done
 
 test_expect_success 'config to add customization' '
-       git-config --unset-all i18n.commitencoding &&
-       if Z=$(git-config --get-all i18n.commitencoding)
+       git config --unset-all i18n.commitencoding &&
+       if Z=$(git config --get-all i18n.commitencoding)
        then
                echo Oops, should have failed.
                false
@@ -81,13 +81,13 @@ test_expect_success 'config to add customization' '
 for H in ISO-8859-1 EUCJP ISO-2022-JP
 do
        test_expect_success "$H should be shown in itself now" '
-               git-config i18n.commitencoding '$H' &&
+               git config i18n.commitencoding '$H' &&
                compare_with '$H' ../t3900/'$H'.txt
        '
 done
 
 test_expect_success 'config to tweak customization' '
-       git-config i18n.logoutputencoding utf-8
+       git config i18n.logoutputencoding utf-8
 '
 
 test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
@@ -103,7 +103,7 @@ done
 
 for J in EUCJP ISO-2022-JP
 do
-       git-config i18n.logoutputencoding $J
+       git config i18n.logoutputencoding $J
        for H in EUCJP ISO-2022-JP
        do
                test_expect_success "$H should be shown in $J now" '
index 24bf0ee0184bab6f3703f17f280e413041d2a686..28e9e372f34818ab5427ef0178dc2003a8018656 100755 (executable)
@@ -14,7 +14,7 @@ check_encoding () {
        do
                git format-patch --encoding=UTF-8 --stdout HEAD~$i..HEAD~$j |
                grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD=20=C3=B3=C3=BA?=" &&
-               git-cat-file commit HEAD~$j |
+               git cat-file commit HEAD~$j |
                case "$header" in
                8859)
                        grep "^encoding ISO-8859-1" ;;
@@ -31,7 +31,7 @@ check_encoding () {
 }
 
 test_expect_success setup '
-       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.commitencoding UTF-8 &&
 
        # use UTF-8 in author and committer name to match the
        # i18n.commitencoding settings
@@ -55,7 +55,7 @@ test_expect_success setup '
        git commit -s -m "Second on side" &&
 
        # the second one on the side branch is ISO-8859-1
-       git-config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        # use author and committer name in ISO-8859-1 to match it.
        . ../t3901-8859-1.txt &&
        test_tick &&
@@ -64,11 +64,11 @@ test_expect_success setup '
        git commit -s -m "Third on side" &&
 
        # Back to default
-       git-config i18n.commitencoding UTF-8
+       git config i18n.commitencoding UTF-8
 '
 
 test_expect_success 'format-patch output (ISO-8859-1)' '
-       git-config i18n.logoutputencoding ISO-8859-1 &&
+       git config i18n.logoutputencoding ISO-8859-1 &&
 
        git format-patch --stdout master..HEAD^ >out-l1 &&
        git format-patch --stdout HEAD^ >out-l2 &&
@@ -91,7 +91,7 @@ test_expect_success 'format-patch output (UTF-8)' '
 
 test_expect_success 'rebase (U/U)' '
        # We want the result of rebase in UTF-8
-       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.commitencoding UTF-8 &&
 
        # The test is about logoutputencoding not affecting the
        # final outcome -- it is used internally to generate the
@@ -109,7 +109,7 @@ test_expect_success 'rebase (U/U)' '
 '
 
 test_expect_success 'rebase (U/L)' '
-       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
        . ../t3901-utf8.txt &&
 
@@ -121,7 +121,7 @@ test_expect_success 'rebase (U/L)' '
 
 test_expect_success 'rebase (L/L)' '
        # In this test we want ISO-8859-1 encoded commits as the result
-       git-config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
        . ../t3901-8859-1.txt &&
 
@@ -134,7 +134,7 @@ test_expect_success 'rebase (L/L)' '
 test_expect_success 'rebase (L/U)' '
        # This is pathological -- use UTF-8 as intermediate form
        # to get ISO-8859-1 results.
-       git-config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
        . ../t3901-8859-1.txt &&
 
@@ -147,7 +147,7 @@ test_expect_success 'rebase (L/U)' '
 test_expect_success 'cherry-pick(U/U)' '
        # Both the commitencoding and logoutputencoding is set to UTF-8.
 
-       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding UTF-8 &&
        . ../t3901-utf8.txt &&
 
@@ -162,7 +162,7 @@ test_expect_success 'cherry-pick(U/U)' '
 test_expect_success 'cherry-pick(L/L)' '
        # Both the commitencoding and logoutputencoding is set to ISO-8859-1
 
-       git-config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
        . ../t3901-8859-1.txt &&
 
@@ -177,7 +177,7 @@ test_expect_success 'cherry-pick(L/L)' '
 test_expect_success 'cherry-pick(U/L)' '
        # Commitencoding is set to UTF-8 but logoutputencoding is ISO-8859-1
 
-       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
        . ../t3901-utf8.txt &&
 
@@ -193,7 +193,7 @@ test_expect_success 'cherry-pick(L/U)' '
        # Again, the commitencoding is set to ISO-8859-1 but
        # logoutputencoding is set to UTF-8.
 
-       git-config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
        . ../t3901-8859-1.txt &&
 
@@ -206,7 +206,7 @@ test_expect_success 'cherry-pick(L/U)' '
 '
 
 test_expect_success 'rebase --merge (U/U)' '
-       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding UTF-8 &&
        . ../t3901-utf8.txt &&
 
@@ -217,7 +217,7 @@ test_expect_success 'rebase --merge (U/U)' '
 '
 
 test_expect_success 'rebase --merge (U/L)' '
-       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.commitencoding UTF-8 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
        . ../t3901-utf8.txt &&
 
@@ -229,7 +229,7 @@ test_expect_success 'rebase --merge (U/L)' '
 
 test_expect_success 'rebase --merge (L/L)' '
        # In this test we want ISO-8859-1 encoded commits as the result
-       git-config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding ISO-8859-1 &&
        . ../t3901-8859-1.txt &&
 
@@ -242,7 +242,7 @@ test_expect_success 'rebase --merge (L/L)' '
 test_expect_success 'rebase --merge (L/U)' '
        # This is pathological -- use UTF-8 as intermediate form
        # to get ISO-8859-1 results.
-       git-config i18n.commitencoding ISO-8859-1 &&
+       git config i18n.commitencoding ISO-8859-1 &&
        git config i18n.logoutputencoding UTF-8 &&
        . ../t3901-8859-1.txt &&
 
diff --git a/t/t3902-quoted.sh b/t/t3902-quoted.sh
new file mode 100755 (executable)
index 0000000..245fb3b
--- /dev/null
@@ -0,0 +1,132 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='quoted output'
+
+. ./test-lib.sh
+
+P1='pathname   with HT'
+: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || {
+       echo >&2 'Filesystem does not support HT in names'
+       test_done
+}
+
+FN='濱野'
+GN='純'
+HT='   '
+LF='
+'
+DQ='"'
+
+for_each_name () {
+       for name in \
+           Name "Name and a${LF}LF" "Name and an${HT}HT" "Name${DQ}" \
+           "$FN$HT$GN" "$FN$LF$GN" "$FN $GN" "$FN$GN" "$FN$DQ$GN" \
+           "With SP in it"
+       do
+               eval "$1"
+       done
+}
+
+test_expect_success setup '
+
+       for_each_name "echo initial >\"\$name\""
+       git add . &&
+       git commit -q -m Initial &&
+
+       for_each_name "echo second >\"\$name\"" &&
+       git commit -a -m Second
+
+       for_each_name "echo modified >\"\$name\""
+
+'
+
+cat >expect.quoted <<\EOF
+Name
+"Name and a\nLF"
+"Name and an\tHT"
+"Name\""
+With SP in it
+"\346\277\261\351\207\216\t\347\264\224"
+"\346\277\261\351\207\216\n\347\264\224"
+"\346\277\261\351\207\216 \347\264\224"
+"\346\277\261\351\207\216\"\347\264\224"
+"\346\277\261\351\207\216\347\264\224"
+EOF
+
+cat >expect.raw <<\EOF
+Name
+"Name and a\nLF"
+"Name and an\tHT"
+"Name\""
+With SP in it
+"濱野\t純"
+"濱野\n純"
+濱野 純
+"濱野\"純"
+濱野純
+EOF
+
+test_expect_success 'check fully quoted output from ls-files' '
+
+       git ls-files >current && diff -u expect.quoted current
+
+'
+
+test_expect_success 'check fully quoted output from diff-files' '
+
+       git diff --name-only >current &&
+       diff -u expect.quoted current
+
+'
+
+test_expect_success 'check fully quoted output from diff-index' '
+
+       git diff --name-only HEAD >current &&
+       diff -u expect.quoted current
+
+'
+
+test_expect_success 'check fully quoted output from diff-tree' '
+
+       git diff --name-only HEAD^ HEAD >current &&
+       diff -u expect.quoted current
+
+'
+
+test_expect_success 'setting core.quotepath' '
+
+       git config --bool core.quotepath false
+
+'
+
+test_expect_success 'check fully quoted output from ls-files' '
+
+       git ls-files >current && diff -u expect.raw current
+
+'
+
+test_expect_success 'check fully quoted output from diff-files' '
+
+       git diff --name-only >current &&
+       diff -u expect.raw current
+
+'
+
+test_expect_success 'check fully quoted output from diff-index' '
+
+       git diff --name-only HEAD >current &&
+       diff -u expect.raw current
+
+'
+
+test_expect_success 'check fully quoted output from diff-tree' '
+
+       git diff --name-only HEAD^ HEAD >current &&
+       diff -u expect.raw current
+
+'
+
+test_done
diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh
new file mode 100755 (executable)
index 0000000..9a9a250
--- /dev/null
@@ -0,0 +1,76 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E Schindelin
+#
+
+test_description='Test git-stash'
+
+. ./test-lib.sh
+
+test_expect_success 'stash some dirty working directory' '
+       echo 1 > file &&
+       git add file &&
+       test_tick &&
+       git commit -m initial &&
+       echo 2 > file &&
+       git add file &&
+       echo 3 > file &&
+       test_tick &&
+       git stash &&
+       git diff-files --quiet &&
+       git diff-index --cached --quiet HEAD
+'
+
+cat > expect << EOF
+diff --git a/file b/file
+index 0cfbf08..00750ed 100644
+--- a/file
++++ b/file
+@@ -1 +1 @@
+-2
++3
+EOF
+
+test_expect_success 'parents of stash' '
+       test $(git rev-parse stash^) = $(git rev-parse HEAD) &&
+       git diff stash^2..stash > output &&
+       diff -u output expect
+'
+
+test_expect_success 'apply needs clean working directory' '
+       echo 4 > other-file &&
+       git add other-file &&
+       echo 5 > other-file
+       ! git stash apply
+'
+
+test_expect_success 'apply stashed changes' '
+       git add other-file &&
+       test_tick &&
+       git commit -m other-file &&
+       git stash apply &&
+       test 3 = $(cat file) &&
+       test 1 = $(git show :file) &&
+       test 1 = $(git show HEAD:file)
+'
+
+test_expect_success 'apply stashed changes (including index)' '
+       git reset --hard HEAD^ &&
+       echo 6 > other-file &&
+       git add other-file &&
+       test_tick &&
+       git commit -m other-file &&
+       git stash apply --index &&
+       test 3 = $(cat file) &&
+       test 2 = $(git show :file) &&
+       test 1 = $(git show HEAD:file)
+'
+
+test_expect_success 'unstashing in a subdirectory' '
+       git reset --hard HEAD &&
+       mkdir subdir &&
+       cd subdir &&
+       git stash apply
+'
+
+test_done
index 9c58d77cc22a062e571116e2dee2599ac13cb464..7d92ae3e996b31f8fa9e0b62f40a04ec1940cf70 100755 (executable)
@@ -17,15 +17,15 @@ chmod +x path1
 
 test_expect_success \
     'update-cache --add two files with and without +x.' \
-    'git-update-index --add path0 path1'
+    'git update-index --add path0 path1'
 
 mv path0 path0-
 sed -e 's/line/Line/' <path0- >path0
 chmod +x path0
 rm -f path1
 test_expect_success \
-    'git-diff-files -p after editing work tree.' \
-    'git-diff-files -p >current'
+    'git diff-files -p after editing work tree.' \
+    'git diff-files -p >current'
 
 # that's as far as it comes
 if [ "$(git config --get core.filemode)" = false ]
@@ -56,7 +56,7 @@ deleted file mode 100755
 EOF
 
 test_expect_success \
-    'validate git-diff-files -p output.' \
+    'validate git diff-files -p output.' \
     'compare_diff_patch current expected'
 
 test_done
index 2e3c20d6b9468bf413e97d422e7dbe13ac4238cd..063e79257a6e96d9021fa41d18a632560a272776 100755 (executable)
@@ -28,21 +28,21 @@ Line 15
 
 test_expect_success \
     'update-cache --add a file.' \
-    'git-update-index --add path0'
+    'git update-index --add path0'
 
 test_expect_success \
     'write that tree.' \
-    'tree=$(git-write-tree) && echo $tree'
+    'tree=$(git write-tree) && echo $tree'
 
 sed -e 's/line/Line/' <path0 >path1
 rm -f path0
 test_expect_success \
     'renamed and edited the file.' \
-    'git-update-index --add --remove path0 path1'
+    'git update-index --add --remove path0 path1'
 
 test_expect_success \
-    'git-diff-index -p -M after rename and editing.' \
-    'git-diff-index -p -M $tree >current'
+    'git diff-index -p -M after rename and editing.' \
+    'git diff-index -p -M $tree >current'
 cat >expected <<\EOF
 diff --git a/path0 b/path1
 rename from path0
@@ -64,4 +64,17 @@ test_expect_success \
     'validate the output.' \
     'compare_diff_patch current expected'
 
+test_expect_success 'favour same basenames over different ones' '
+       cp path1 another-path &&
+       git add another-path &&
+       git commit -m 1 &&
+       git rm path1 &&
+       mkdir subdir &&
+       git mv another-path subdir/path1 &&
+       git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+
+test_expect_success  'favour same basenames even with minor differences' '
+       git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
+       git runstatus | grep "renamed: .*path1 -> subdir/path1"'
+
 test_done
index 56eda63fc2f442a57fbe1f3877f17744b6c114d1..a4cfde6b2927a4655f582d7e92dad4568f7b5f89 100755 (executable)
@@ -140,80 +140,80 @@ cmp_diff_files_output () {
 
 test_expect_success \
     'diff-tree of known trees.' \
-    'git-diff-tree $tree_O $tree_A >.test-a &&
+    'git diff-tree $tree_O $tree_A >.test-a &&
      cmp -s .test-a .test-plain-OA'
 
 test_expect_success \
     'diff-tree of known trees.' \
-    'git-diff-tree -r $tree_O $tree_A >.test-a &&
+    'git diff-tree -r $tree_O $tree_A >.test-a &&
      cmp -s .test-a .test-recursive-OA'
 
 test_expect_success \
     'diff-tree of known trees.' \
-    'git-diff-tree $tree_O $tree_B >.test-a &&
+    'git diff-tree $tree_O $tree_B >.test-a &&
      cmp -s .test-a .test-plain-OB'
 
 test_expect_success \
     'diff-tree of known trees.' \
-    'git-diff-tree -r $tree_O $tree_B >.test-a &&
+    'git diff-tree -r $tree_O $tree_B >.test-a &&
      cmp -s .test-a .test-recursive-OB'
 
 test_expect_success \
     'diff-tree of known trees.' \
-    'git-diff-tree $tree_A $tree_B >.test-a &&
+    'git diff-tree $tree_A $tree_B >.test-a &&
      cmp -s .test-a .test-plain-AB'
 
 test_expect_success \
     'diff-tree of known trees.' \
-    'git-diff-tree -r $tree_A $tree_B >.test-a &&
+    'git diff-tree -r $tree_A $tree_B >.test-a &&
      cmp -s .test-a .test-recursive-AB'
 
 test_expect_success \
     'diff-cache O with A in cache' \
-    'git-read-tree $tree_A &&
-     git-diff-index --cached $tree_O >.test-a &&
+    'git read-tree $tree_A &&
+     git diff-index --cached $tree_O >.test-a &&
      cmp -s .test-a .test-recursive-OA'
 
 test_expect_success \
     'diff-cache O with B in cache' \
-    'git-read-tree $tree_B &&
-     git-diff-index --cached $tree_O >.test-a &&
+    'git read-tree $tree_B &&
+     git diff-index --cached $tree_O >.test-a &&
      cmp -s .test-a .test-recursive-OB'
 
 test_expect_success \
     'diff-cache A with B in cache' \
-    'git-read-tree $tree_B &&
-     git-diff-index --cached $tree_A >.test-a &&
+    'git read-tree $tree_B &&
+     git diff-index --cached $tree_A >.test-a &&
      cmp -s .test-a .test-recursive-AB'
 
 test_expect_success \
     'diff-files with O in cache and A checked out' \
     'rm -fr Z [A-Z][A-Z] &&
-     git-read-tree $tree_A &&
-     git-checkout-index -f -a &&
-     git-read-tree --reset $tree_O || return 1
-     git-update-index --refresh >/dev/null ;# this can exit non-zero
-     git-diff-files >.test-a &&
+     git read-tree $tree_A &&
+     git checkout-index -f -a &&
+     git read-tree --reset $tree_O || return 1
+     git update-index --refresh >/dev/null ;# this can exit non-zero
+     git diff-files >.test-a &&
      cmp_diff_files_output .test-a .test-recursive-OA'
 
 test_expect_success \
     'diff-files with O in cache and B checked out' \
     'rm -fr Z [A-Z][A-Z] &&
-     git-read-tree $tree_B &&
-     git-checkout-index -f -a &&
-     git-read-tree --reset $tree_O || return 1
-     git-update-index --refresh >/dev/null ;# this can exit non-zero
-     git-diff-files >.test-a &&
+     git read-tree $tree_B &&
+     git checkout-index -f -a &&
+     git read-tree --reset $tree_O || return 1
+     git update-index --refresh >/dev/null ;# this can exit non-zero
+     git diff-files >.test-a &&
      cmp_diff_files_output .test-a .test-recursive-OB'
 
 test_expect_success \
     'diff-files with A in cache and B checked out' \
     'rm -fr Z [A-Z][A-Z] &&
-     git-read-tree $tree_B &&
-     git-checkout-index -f -a &&
-     git-read-tree --reset $tree_A || return 1
-     git-update-index --refresh >/dev/null ;# this can exit non-zero
-     git-diff-files >.test-a &&
+     git read-tree $tree_B &&
+     git checkout-index -f -a &&
+     git read-tree --reset $tree_A || return 1
+     git update-index --refresh >/dev/null ;# this can exit non-zero
+     git diff-files >.test-a &&
      cmp_diff_files_output .test-a .test-recursive-AB'
 
 ################################################################
@@ -222,26 +222,26 @@ test_expect_success \
 
 test_expect_success \
     'diff-tree O A == diff-tree -R A O' \
-    'git-diff-tree $tree_O $tree_A >.test-a &&
-    git-diff-tree -R $tree_A $tree_O >.test-b &&
+    'git diff-tree $tree_O $tree_A >.test-a &&
+    git diff-tree -R $tree_A $tree_O >.test-b &&
     cmp -s .test-a .test-b'
 
 test_expect_success \
     'diff-tree -r O A == diff-tree -r -R A O' \
-    'git-diff-tree -r $tree_O $tree_A >.test-a &&
-    git-diff-tree -r -R $tree_A $tree_O >.test-b &&
+    'git diff-tree -r $tree_O $tree_A >.test-a &&
+    git diff-tree -r -R $tree_A $tree_O >.test-b &&
     cmp -s .test-a .test-b'
 
 test_expect_success \
     'diff-tree B A == diff-tree -R A B' \
-    'git-diff-tree $tree_B $tree_A >.test-a &&
-    git-diff-tree -R $tree_A $tree_B >.test-b &&
+    'git diff-tree $tree_B $tree_A >.test-a &&
+    git diff-tree -R $tree_A $tree_B >.test-b &&
     cmp -s .test-a .test-b'
 
 test_expect_success \
     'diff-tree -r B A == diff-tree -r -R A B' \
-    'git-diff-tree -r $tree_B $tree_A >.test-a &&
-    git-diff-tree -r -R $tree_A $tree_B >.test-b &&
+    'git diff-tree -r $tree_B $tree_A >.test-a &&
+    git diff-tree -r -R $tree_A $tree_B >.test-b &&
     cmp -s .test-a .test-b'
 
 test_done
index 27519704d46d95deddbe8af75f985ede16bb4aaf..8b1f875286b25b5fc1a527b5b0281f866724a82d 100755 (executable)
@@ -13,8 +13,8 @@ test_expect_success \
     'prepare reference tree' \
     'cat ../../COPYING >COPYING &&
      echo frotz >rezrov &&
-    git-update-index --add COPYING rezrov &&
-    tree=$(git-write-tree) &&
+    git update-index --add COPYING rezrov &&
+    tree=$(git write-tree) &&
     echo $tree'
 
 test_expect_success \
@@ -22,14 +22,14 @@ test_expect_success \
     'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
     sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
     rm -f COPYING &&
-    git-update-index --add --remove COPYING COPYING.?'
+    git update-index --add --remove COPYING COPYING.?'
 
 # tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
 # both are slightly edited, and unchanged rezrov.  So we say you
 # copy-and-edit one, and rename-and-edit the other.  We do not say
 # anything about rezrov.
 
-GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
 cat >expected <<\EOF
 diff --git a/COPYING b/COPYING.1
 copy from COPYING
@@ -62,14 +62,14 @@ test_expect_success \
 test_expect_success \
     'prepare work tree again' \
     'mv COPYING.2 COPYING &&
-     git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+     git update-index --add --remove COPYING COPYING.1 COPYING.2'
 
 # tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
 # both are slightly edited, and unchanged rezrov.  So we say you
 # edited one, and copy-and-edit the other.  We do not say
 # anything about rezrov.
 
-GIT_DIFF_OPTS=--unified=0 git-diff-index -C -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -C -p $tree >current
 cat >expected <<\EOF
 diff --git a/COPYING b/COPYING
 --- a/COPYING
@@ -100,16 +100,16 @@ test_expect_success \
 test_expect_success \
     'prepare work tree once again' \
     'cat ../../COPYING >COPYING &&
-     git-update-index --add --remove COPYING COPYING.1'
+     git update-index --add --remove COPYING COPYING.1'
 
 # tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
 # but COPYING is not edited.  We say you copy-and-edit COPYING.1; this
 # is only possible because -C mode now reports the unmodified file to
 # the diff-core.  Unchanged rezrov, although being fed to
-# git-diff-index as well, should not be mentioned.
+# git diff-index as well, should not be mentioned.
 
 GIT_DIFF_OPTS=--unified=0 \
-    git-diff-index -C --find-copies-harder -p $tree >current
+    git diff-index -C --find-copies-harder -p $tree >current
 cat >expected <<\EOF
 diff --git a/COPYING b/COPYING.1
 copy from COPYING
index a23aaa0a9471c68b233480cf34c7115d1f40e154..3d25be7a6709cdd23e0d583a8f1a3e19a3927cd8 100755 (executable)
@@ -16,8 +16,8 @@ test_expect_success \
     'prepare reference tree' \
     'echo xyzzy | tr -d '\\\\'012 >yomin &&
      ln -s xyzzy frotz &&
-    git-update-index --add frotz yomin &&
-    tree=$(git-write-tree) &&
+    git update-index --add frotz yomin &&
+    tree=$(git write-tree) &&
     echo $tree'
 
 test_expect_success \
@@ -26,7 +26,7 @@ test_expect_success \
      rm -f yomin &&
      ln -s xyzzy nitfol &&
      ln -s xzzzy bozbar &&
-    git-update-index --add --remove frotz rezrov nitfol bozbar yomin'
+    git update-index --add --remove frotz rezrov nitfol bozbar yomin'
 
 # tree has frotz pointing at xyzzy, and yomin that contains xyzzy to
 # confuse things.  work tree has rezrov (xyzzy) nitfol (xyzzy) and
@@ -34,7 +34,7 @@ test_expect_success \
 # rezrov and nitfol are rename/copy of frotz and bozbar should be
 # a new creation.
 
-GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree >current
+GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree >current
 cat >expected <<\EOF
 diff --git a/bozbar b/bozbar
 new file mode 120000
index 684fd23a419a2449a1a656783b09e0566c56c916..66300173124eee8480e7214322faf8bc493ad940 100755 (executable)
@@ -13,8 +13,8 @@ test_expect_success \
     'prepare reference tree' \
     'cat ../../COPYING >COPYING &&
      echo frotz >rezrov &&
-    git-update-index --add COPYING rezrov &&
-    tree=$(git-write-tree) &&
+    git update-index --add COPYING rezrov &&
+    tree=$(git write-tree) &&
     echo $tree'
 
 test_expect_success \
@@ -22,14 +22,14 @@ test_expect_success \
     'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
     sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
     rm -f COPYING &&
-    git-update-index --add --remove COPYING COPYING.?'
+    git update-index --add --remove COPYING COPYING.?'
 
 # tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
 # both are slightly edited, and unchanged rezrov.  We say COPYING.1
 # and COPYING.2 are based on COPYING, and do not say anything about
 # rezrov.
 
-git-diff-index -M $tree >current
+git diff-index -M $tree >current
 
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
@@ -45,14 +45,14 @@ test_expect_success \
 test_expect_success \
     'prepare work tree again' \
     'mv COPYING.2 COPYING &&
-     git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+     git update-index --add --remove COPYING COPYING.1 COPYING.2'
 
 # tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
 # both are slightly edited, and unchanged rezrov.  We say COPYING.1
 # is based on COPYING and COPYING is still there, and do not say anything
 # about rezrov.
 
-git-diff-index -C $tree >current
+git diff-index -C $tree >current
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M     COPYING
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
@@ -72,9 +72,9 @@ test_expect_success \
 test_expect_success \
     'prepare work tree once again' \
     'cat ../../COPYING >COPYING &&
-     git-update-index --add --remove COPYING COPYING.1'
+     git update-index --add --remove COPYING COPYING.1'
 
-git-diff-index -C --find-copies-harder $tree >current
+git diff-index -C --find-copies-harder $tree >current
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234 COPYING COPYING.1
 EOF
index e72c6fd1b4fa294e3e52fe55ab68c672bfc57e15..ab5406dd9f241edee7a6067a1426f0a65076eb10 100755 (executable)
@@ -11,24 +11,24 @@ test_description='Test mode change diffs.
 test_expect_success \
     'setup' \
     'echo frotz >rezrov &&
-     git-update-index --add rezrov &&
-     tree=`git-write-tree` &&
+     git update-index --add rezrov &&
+     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'
+           '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'
+            git update-index rezrov &&
+            git diff-index $tree >current'
 fi
 
 _x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
@@ -41,4 +41,3 @@ test_expect_success \
     'git diff expected check'
 
 test_done
-
index bb6ba69258237ed40e736a0a7e218d621164998a..104a4e1492b65a64d061e1ce1df0b0a2a49fb20e 100755 (executable)
@@ -13,20 +13,20 @@ test_expect_success \
     'prepare reference tree' \
     'mkdir path0 path1 &&
      cp ../../COPYING path0/COPYING &&
-     git-update-index --add path0/COPYING &&
-    tree=$(git-write-tree) &&
+     git update-index --add path0/COPYING &&
+    tree=$(git write-tree) &&
     echo $tree'
 
 test_expect_success \
     'prepare work tree' \
     'cp path0/COPYING path1/COPYING &&
-     git-update-index --add --remove path0/COPYING path1/COPYING'
+     git update-index --add --remove path0/COPYING path1/COPYING'
 
 # In the tree, there is only path0/COPYING.  In the cache, path0 and
 # path1 both have COPYING and the latter is a copy of path0/COPYING.
 # Comparing the full tree with cache should tell us so.
 
-git-diff-index -C --find-copies-harder $tree >current
+git diff-index -C --find-copies-harder $tree >current
 
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100  path0/COPYING   path1/COPYING
@@ -42,7 +42,7 @@ test_expect_success \
 # path1/COPYING suddenly appearing from nowhere, not detected as
 # a copy from path0/COPYING.
 
-git-diff-index -C $tree path1 >current
+git diff-index -C $tree path1 >current
 
 cat >expected <<\EOF
 :000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
@@ -55,14 +55,14 @@ test_expect_success \
 test_expect_success \
     'tweak work tree' \
     'rm -f path0/COPYING &&
-     git-update-index --remove path0/COPYING'
+     git update-index --remove path0/COPYING'
 
 # In the tree, there is only path0/COPYING.  In the cache, path0 does
 # not have COPYING anymore and path1 has COPYING which is a copy of
 # path0/COPYING.  Showing the full tree with cache should tell us about
 # the rename.
 
-git-diff-index -C $tree >current
+git diff-index -C $tree >current
 
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  path0/COPYING   path1/COPYING
@@ -77,7 +77,7 @@ test_expect_success \
 # path0/COPYING.  When we say we care only about path1, we should just
 # see path1/COPYING appearing from nowhere.
 
-git-diff-index -C $tree path1 >current
+git diff-index -C $tree path1 >current
 
 cat >expected <<\EOF
 :000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A     path1/COPYING
index 263ac1ebf7d5ef2fa6b278c4a6694d369c283f88..5836e3a899d62c3c86482ea254eb53c015a52823 100755 (executable)
@@ -28,19 +28,19 @@ test_expect_success \
     setup \
     'cat ../../README >file0 &&
      cat ../../COPYING >file1 &&
-    git-update-index --add file0 file1 &&
-    tree=$(git-write-tree) &&
+    git update-index --add file0 file1 &&
+    tree=$(git write-tree) &&
     echo "$tree"'
 
 test_expect_success \
     'change file1 with copy-edit of file0 and remove file0' \
     'sed -e "s/git/GIT/" file0 >file1 &&
      rm -f file0 &&
-    git-update-index --remove file0 file1'
+    git update-index --remove file0 file1'
 
 test_expect_success \
     'run diff with -B' \
-    'git-diff-index -B --cached "$tree" >current'
+    'git diff-index -B --cached "$tree" >current'
 
 cat >expected <<\EOF
 :100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
@@ -53,7 +53,7 @@ test_expect_success \
 
 test_expect_success \
     'run diff with -B and -M' \
-    'git-diff-index -B -M "$tree" >current'
+    'git diff-index -B -M "$tree" >current'
 
 cat >expected <<\EOF
 :100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c R100  file0   file1
@@ -66,16 +66,16 @@ test_expect_success \
 test_expect_success \
     'swap file0 and file1' \
     'rm -f file0 file1 &&
-     git-read-tree -m $tree &&
-     git-checkout-index -f -u -a &&
+     git read-tree -m $tree &&
+     git checkout-index -f -u -a &&
      mv file0 tmp &&
      mv file1 file0 &&
      mv tmp file1 &&
-     git-update-index file0 file1'
+     git update-index file0 file1'
 
 test_expect_success \
     'run diff with -B' \
-    'git-diff-index -B "$tree" >current'
+    'git diff-index -B "$tree" >current'
 
 cat >expected <<\EOF
 :100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100  file0
@@ -88,7 +88,7 @@ test_expect_success \
 
 test_expect_success \
     'run diff with -B and -M' \
-    'git-diff-index -B -M "$tree" >current'
+    'git diff-index -B -M "$tree" >current'
 
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100  file1   file0
@@ -103,11 +103,11 @@ test_expect_success \
     'make file0 into something completely different' \
     'rm -f file0 &&
      ln -s frotz file0 &&
-     git-update-index file0 file1'
+     git update-index file0 file1'
 
 test_expect_success \
     'run diff with -B' \
-    'git-diff-index -B "$tree" >current'
+    'git diff-index -B "$tree" >current'
 
 cat >expected <<\EOF
 :100644 120000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 67be421f88824578857624f7b3dc75e99a8a1481 T     file0
@@ -120,7 +120,7 @@ test_expect_success \
 
 test_expect_success \
     'run diff with -B' \
-    'git-diff-index -B -M "$tree" >current'
+    'git diff-index -B -M "$tree" >current'
 
 # This should not mistake file0 as the copy source of new file1
 # due to type differences.
@@ -135,7 +135,7 @@ test_expect_success \
 
 test_expect_success \
     'run diff with -M' \
-    'git-diff-index -M "$tree" >current'
+    'git diff-index -M "$tree" >current'
 
 # This should not mistake file0 as the copy source of new file1
 # due to type differences.
@@ -151,16 +151,16 @@ test_expect_success \
 test_expect_success \
     'file1 edited to look like file0 and file0 rename-edited to file2' \
     'rm -f file0 file1 &&
-     git-read-tree -m $tree &&
-     git-checkout-index -f -u -a &&
+     git read-tree -m $tree &&
+     git checkout-index -f -u -a &&
      sed -e "s/git/GIT/" file0 >file1 &&
      sed -e "s/git/GET/" file0 >file2 &&
      rm -f file0
-     git-update-index --add --remove file0 file1 file2'
+     git update-index --add --remove file0 file1 file2'
 
 test_expect_success \
     'run diff with -B' \
-    'git-diff-index -B "$tree" >current'
+    'git diff-index -B "$tree" >current'
 
 cat >expected <<\EOF
 :100644 000000 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 0000000000000000000000000000000000000000 D     file0
@@ -174,7 +174,7 @@ test_expect_success \
 
 test_expect_success \
     'run diff with -B -M' \
-    'git-diff-index -B -M "$tree" >current'
+    'git diff-index -B -M "$tree" >current'
 
 cat >expected <<\EOF
 :100644 100644 f5deac7be59e7eeab8657fd9ae706fd6a57daed2 08bb2fb671deff4c03a4d4a0a1315dff98d5732c C095  file0   file1
index 2f2f8b121663a3647e88c308b541c6106f5d9039..d2b45e7b8fb3902cb740e0df582f92195a295f24 100755 (executable)
@@ -13,8 +13,8 @@ test_expect_success \
     'prepare reference tree' \
     'cat ../../COPYING >COPYING &&
      echo frotz >rezrov &&
-    git-update-index --add COPYING rezrov &&
-    tree=$(git-write-tree) &&
+    git update-index --add COPYING rezrov &&
+    tree=$(git write-tree) &&
     echo $tree'
 
 test_expect_success \
@@ -22,14 +22,14 @@ test_expect_success \
     'sed -e 's/HOWEVER/However/' <COPYING >COPYING.1 &&
     sed -e 's/GPL/G.P.L/g' <COPYING >COPYING.2 &&
     rm -f COPYING &&
-    git-update-index --add --remove COPYING COPYING.?'
+    git update-index --add --remove COPYING COPYING.?'
 
 # tree has COPYING and rezrov.  work tree has COPYING.1 and COPYING.2,
 # both are slightly edited, and unchanged rezrov.  We say COPYING.1
 # and COPYING.2 are based on COPYING, and do not say anything about
 # rezrov.
 
-git-diff-index -z -M $tree >current
+git diff-index -z -M $tree >current
 
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
@@ -49,14 +49,14 @@ test_expect_success \
 test_expect_success \
     'prepare work tree again' \
     'mv COPYING.2 COPYING &&
-     git-update-index --add --remove COPYING COPYING.1 COPYING.2'
+     git update-index --add --remove COPYING COPYING.1 COPYING.2'
 
 # tree has COPYING and rezrov.  work tree has COPYING and COPYING.1,
 # both are slightly edited, and unchanged rezrov.  We say COPYING.1
 # is based on COPYING and COPYING is still there, and do not say anything
 # about rezrov.
 
-git-diff-index -z -C $tree >current
+git diff-index -z -C $tree >current
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 06c67961bbaed34a127f76d261f4c0bf73eda471 M
 COPYING
@@ -79,9 +79,9 @@ test_expect_success \
 test_expect_success \
     'prepare work tree once again' \
     'cat ../../COPYING >COPYING &&
-     git-update-index --add --remove COPYING COPYING.1'
+     git update-index --add --remove COPYING COPYING.1'
 
-git-diff-index -z -C --find-copies-harder $tree >current
+git diff-index -z -C --find-copies-harder $tree >current
 cat >expected <<\EOF
 :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0603b3238a076dc6c8022aedc6648fa523a17178 C1234
 COPYING
index 9e1544df9d868b505f795c22ea9b734d6b6fc279..ad3d9e48454d2e72afce682df009cdaaee9ba3c5 100755 (executable)
@@ -17,18 +17,18 @@ test_expect_success \
     'echo frotz >file0 &&
      mkdir path1 &&
      echo rezrov >path1/file1 &&
-     git-update-index --add file0 path1/file1 &&
-     tree=`git-write-tree` &&
+     git update-index --add file0 path1/file1 &&
+     tree=`git write-tree` &&
      echo "$tree" &&
      echo nitfol >file0 &&
      echo yomin >path1/file1 &&
-     git-update-index file0 path1/file1'
+     git update-index file0 path1/file1'
 
 cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path should show nothing' \
-    'git-diff-index --cached $tree -- path >current &&
+    'git diff-index --cached $tree -- path >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -36,7 +36,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path1 should show path1/file1' \
-    'git-diff-index --cached $tree -- path1 >current &&
+    'git diff-index --cached $tree -- path1 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -44,7 +44,7 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to path1/ should show path1/file1' \
-    'git-diff-index --cached $tree -- path1/ >current &&
+    'git diff-index --cached $tree -- path1/ >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
@@ -52,14 +52,14 @@ cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to file0 should show file0' \
-    'git-diff-index --cached $tree -- file0 >current &&
+    'git diff-index --cached $tree -- file0 >current &&
      compare_diff_raw current expected'
 
 cat >expected <<\EOF
 EOF
 test_expect_success \
     'limit to file0/ should emit nothing.' \
-    'git-diff-index --cached $tree -- file0/ >current &&
+    'git diff-index --cached $tree -- file0/ >current &&
      compare_diff_raw current expected'
 
 test_done
index 379a831f0bd698273c695468f4d3a2c834b104c5..c6d13693ba45b594704c2ef55d40540ee3f9c25f 100755 (executable)
@@ -23,17 +23,17 @@ EOF
 test_expect_success \
     'diff new symlink' \
     'ln -s xyzzy frotz &&
-    git-update-index &&
-    tree=$(git-write-tree) &&
-    git-update-index --add frotz &&
-    GIT_DIFF_OPTS=--unified=0 git-diff-index -M -p $tree > current &&
+    git update-index &&
+    tree=$(git write-tree) &&
+    git update-index --add frotz &&
+    GIT_DIFF_OPTS=--unified=0 git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
 test_expect_success \
     'diff unchanged symlink' \
-    'tree=$(git-write-tree) &&
-    git-update-index frotz &&
-    test -z "$(git-diff-index --name-only $tree)"'
+    'tree=$(git write-tree) &&
+    git update-index frotz &&
+    test -z "$(git diff-index --name-only $tree)"'
 
 cat > expected << EOF
 diff --git a/frotz b/frotz
@@ -49,7 +49,7 @@ EOF
 test_expect_success \
     'diff removed symlink' \
     'rm frotz &&
-    git-diff-index -M -p $tree > current &&
+    git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
 cat > expected << EOF
@@ -60,7 +60,7 @@ test_expect_success \
     'diff identical, but newly created symlink' \
     'sleep 3 &&
     ln -s xyzzy frotz &&
-    git-diff-index -M -p $tree > current &&
+    git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
 cat > expected << EOF
@@ -79,7 +79,7 @@ test_expect_success \
     'diff different symlink' \
     'rm frotz &&
     ln -s yxyyz frotz &&
-    git-diff-index -M -p $tree > current &&
+    git diff-index -M -p $tree > current &&
     compare_diff_patch current expected'
 
 test_done
index 323606c65c424814afc4847d40af8796e39ce4cf..eced1f30fb8475739aef52230bbb79946a0f76d8 100755 (executable)
@@ -10,7 +10,7 @@ test_description='Binary diff and apply
 
 test_expect_success 'prepare repository' \
        'echo AIT >a && echo BIT >b && echo CIT >c && echo DIT >d &&
-        git-update-index --add a b c d &&
+        git update-index --add a b c d &&
         echo git >a &&
         cat ../test4012.png >b &&
         echo git >c &&
@@ -24,18 +24,18 @@ cat > expected <<\EOF
  4 files changed, 2 insertions(+), 2 deletions(-)
 EOF
 test_expect_success 'diff without --binary' \
-       'git-diff | git-apply --stat --summary >current &&
+       'git diff | git apply --stat --summary >current &&
         cmp current expected'
 
 test_expect_success 'diff with --binary' \
-       'git-diff --binary | git-apply --stat --summary >current &&
+       'git diff --binary | git apply --stat --summary >current &&
         cmp current expected'
 
 # apply needs to be able to skip the binary material correctly
 # in order to report the line number of a corrupt patch.
 test_expect_success 'apply detecting corrupt patch correctly' \
-       'git-diff | sed -e 's/-CIT/xCIT/' >broken &&
-        if git-apply --stat --summary broken 2>detected
+       'git diff | sed -e 's/-CIT/xCIT/' >broken &&
+        if git apply --stat --summary broken 2>detected
         then
                echo unhappy - should have detected an error
                (exit 1)
@@ -48,8 +48,8 @@ test_expect_success 'apply detecting corrupt patch correctly' \
         test "$detected" = xCIT'
 
 test_expect_success 'apply detecting corrupt patch correctly' \
-       'git-diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
-        if git-apply --stat --summary broken 2>detected
+       'git diff --binary | sed -e 's/-CIT/xCIT/' >broken &&
+        if git apply --stat --summary broken 2>detected
         then
                echo unhappy - should have detected an error
                (exit 1)
@@ -66,15 +66,15 @@ test_expect_success 'initial commit' 'git-commit -a -m initial'
 # Try removal (b), modification (d), and creation (e).
 test_expect_success 'diff-index with --binary' \
        'echo AIT >a && mv b e && echo CIT >c && cat e >d &&
-        git-update-index --add --remove a b c d e &&
-        tree0=`git-write-tree` &&
-        git-diff --cached --binary >current &&
-        git-apply --stat --summary current'
+        git update-index --add --remove a b c d e &&
+        tree0=`git write-tree` &&
+        git diff --cached --binary >current &&
+        git apply --stat --summary current'
 
 test_expect_success 'apply binary patch' \
        'git-reset --hard &&
-        git-apply --binary --index <current &&
-        tree1=`git-write-tree` &&
+        git apply --binary --index <current &&
+        tree1=`git write-tree` &&
         test "$tree1" = "$tree0"'
 
 test_done
index 4e7d68dda28a54cdc4f46e11717e113fa8dd479c..9eec754221d85856613b01ec878ef4cb492aceb0 100755 (executable)
@@ -243,6 +243,8 @@ format-patch --inline --stdout initial..side
 format-patch --inline --stdout initial..master^
 format-patch --inline --stdout initial..master
 format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
+config format.subjectprefix DIFFERENT_PREFIX
+format-patch --inline --stdout initial..master^^
 
 diff --abbrev initial..side
 diff -r initial..side
diff --git a/t/t4013/diff.config_format.subjectprefix_DIFFERENT_PREFIX b/t/t4013/diff.config_format.subjectprefix_DIFFERENT_PREFIX
new file mode 100644 (file)
index 0000000..78f8970
--- /dev/null
@@ -0,0 +1,2 @@
+$ git config format.subjectprefix DIFFERENT_PREFIX
+$
diff --git a/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^ b/t/t4013/diff.format-patch_--inline_--stdout_initial..master^^
new file mode 100644 (file)
index 0000000..b8e81e1
--- /dev/null
@@ -0,0 +1,60 @@
+$ git format-patch --inline --stdout 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: [DIFFERENT_PREFIX] 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="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+Content-Transfer-Encoding: 8bit
+Content-Disposition: inline; filename="1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44.diff"
+
+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--
+
+
+$
index 930e209d3136c855c3257e81d889e4dcf765595a..79fdff3f3a87cfe45b7fefa96e350675bd4e048c 100755 (executable)
@@ -17,7 +17,7 @@ do {
 } while (0);
 EOF
 
-git-update-index --add x
+git update-index --add x
 
 cat << EOF > x
 do
@@ -42,13 +42,13 @@ index adf3937..6edc172 100644
 +while (0);
 EOF
 
-git-diff > out
+git diff > out
 test_expect_success "Ray's example without options" 'git diff expect out'
 
-git-diff -w > out
+git diff -w > out
 test_expect_success "Ray's example with -w" 'git diff expect out'
 
-git-diff -b > out
+git diff -b > out
 test_expect_success "Ray's example with -b" 'git diff expect out'
 
 tr 'Q' '\015' << EOF > x
@@ -60,7 +60,7 @@ unchanged line
 CR at endQ
 EOF
 
-git-update-index x
+git update-index x
 
 cat << EOF > x
        whitespace at beginning
@@ -89,14 +89,14 @@ index d99af23..8b32fb5 100644
 -CR at endQ
 +CR at end
 EOF
-git-diff > out
+git diff > out
 test_expect_success 'another test, without options' 'git diff expect out'
 
 cat << EOF > expect
 diff --git a/x b/x
 index d99af23..8b32fb5 100644
 EOF
-git-diff -w > out
+git diff -w > out
 test_expect_success 'another test, with -w' 'git diff expect out'
 
 tr 'Q' '\015' << EOF > expect
@@ -114,7 +114,7 @@ index d99af23..8b32fb5 100644
  unchanged line
  CR at endQ
 EOF
-git-diff -b > out
+git diff -b > out
 test_expect_success 'another test, with -b' 'git diff expect out'
 
 test_done
diff --git a/t/t4018-diff-funcname.sh b/t/t4018-diff-funcname.sh
new file mode 100644 (file)
index 0000000..f9db81d
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Johannes E. Schindelin
+#
+
+test_description='Test custom diff function name patterns'
+
+. ./test-lib.sh
+
+LF='
+'
+
+cat > Beer.java << EOF
+public class Beer
+{
+       int special;
+       public static void main(String args[])
+       {
+               String s=" ";
+               for(int x = 99; x > 0; x--)
+               {
+                       System.out.print(x + " bottles of beer on the wall "
+                               + x + " bottles of beer\n"
+                               + "Take one down, pass it around, " + (x - 1)
+                               + " bottles of beer on the wall.\n");
+               }
+               System.out.print("Go to the store, buy some more,\n"
+                       + "99 bottles of beer on the wall.\n");
+       }
+}
+EOF
+
+sed 's/beer\\/beer,\\/' < Beer.java > Beer-correct.java
+
+test_expect_success 'default behaviour' '
+       git diff Beer.java Beer-correct.java |
+       grep "^@@.*@@ public class Beer"
+'
+
+test_expect_success 'preset java pattern' '
+       echo "*.java diff=java" >.gitattributes &&
+       git diff Beer.java Beer-correct.java |
+       grep "^@@.*@@ public static void main("
+'
+
+git config diff.java.funcname '!static
+!String
+[^     ].*s.*'
+
+test_expect_success 'custom pattern' '
+       git diff Beer.java Beer-correct.java |
+       grep "^@@.*@@ int special;$"
+'
+
+test_expect_success 'last regexp must not be negated' '
+       git config diff.java.funcname "!static" &&
+       ! git diff Beer.java Beer-correct.java
+'
+
+test_done
index f0045cd788a275f525bbd94912c5ad09cf6226bd..ed3bd5b3fe7c8b5ca52067e3015e0d770ee79bf2 100755 (executable)
@@ -94,4 +94,16 @@ test_expect_success 'diff attribute should apply only to diff' '
 
 '
 
+test_expect_success 'no diff with -diff' '
+       echo >.gitattributes "file -diff" &&
+       git diff | grep Binary
+'
+
+echo NULZbetweenZwords | tr Z '\0' > file
+
+test_expect_success 'force diff with "diff"' '
+       echo >.gitattributes "file diff" &&
+       git diff | grep -a second
+'
+
 test_done
index 7b81c32e57f502f2c6d952bd9b7e6b432e092372..435f65b370e8855c4aca6adfbf12c098338f3b45 100755 (executable)
@@ -3,45 +3,44 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply --stat --summary test.
+test_description='git apply --stat --summary test.
 
 '
 . ./test-lib.sh
 
 test_expect_success \
     'rename' \
-    'git-apply --stat --summary <../t4100/t-apply-1.patch >current &&
+    'git apply --stat --summary <../t4100/t-apply-1.patch >current &&
     git diff ../t4100/t-apply-1.expect current'
 
 test_expect_success \
     'copy' \
-    'git-apply --stat --summary <../t4100/t-apply-2.patch >current &&
+    'git apply --stat --summary <../t4100/t-apply-2.patch >current &&
     git diff ../t4100/t-apply-2.expect current'
 
 test_expect_success \
     'rewrite' \
-    'git-apply --stat --summary <../t4100/t-apply-3.patch >current &&
+    'git apply --stat --summary <../t4100/t-apply-3.patch >current &&
     git diff ../t4100/t-apply-3.expect current'
 
 test_expect_success \
     'mode' \
-    'git-apply --stat --summary <../t4100/t-apply-4.patch >current &&
+    'git apply --stat --summary <../t4100/t-apply-4.patch >current &&
     git diff ../t4100/t-apply-4.expect current'
 
 test_expect_success \
     'non git' \
-    'git-apply --stat --summary <../t4100/t-apply-5.patch >current &&
+    'git apply --stat --summary <../t4100/t-apply-5.patch >current &&
     git diff ../t4100/t-apply-5.expect current'
 
 test_expect_success \
     'non git' \
-    'git-apply --stat --summary <../t4100/t-apply-6.patch >current &&
+    'git apply --stat --summary <../t4100/t-apply-6.patch >current &&
     git diff ../t4100/t-apply-6.expect current'
 
 test_expect_success \
     'non git' \
-    'git-apply --stat --summary <../t4100/t-apply-7.patch >current &&
+    'git apply --stat --summary <../t4100/t-apply-7.patch >current &&
     git diff ../t4100/t-apply-7.expect current'
 
 test_done
-
index 026fac8c5534c5a269c1a89088bbe97a4dd30b87..da8abcf36418dbd2e9d8ec85871c245991f96fda 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply should handle files with incomplete lines.
+test_description='git apply should handle files with incomplete lines.
 
 '
 . ./test-lib.sh
@@ -23,7 +23,7 @@ do
     cat frotz.$i >frotz
     test_expect_success \
         "apply diff between $i and $j" \
-       "git-apply <../t4101/diff.$i-$j && diff frotz.$j frotz"
+       "git apply <../t4101/diff.$i-$j && diff frotz.$j frotz"
   done
 done
 
index b4662b03647bd2f36c07ffac24adc6b5df5eef76..d42abff1ad59343fa1c84bded9a82c3212370da0 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply handling copy/rename patch.
+test_description='git apply handling copy/rename patch.
 
 '
 . ./test-lib.sh
@@ -26,10 +26,10 @@ echo 'This is foo' >foo
 chmod +x foo
 
 test_expect_success setup \
-    'git-update-index --add foo'
+    'git update-index --add foo'
 
 test_expect_success apply \
-    'git-apply --index --stat --summary --apply test-patch'
+    'git apply --index --stat --summary --apply test-patch'
 
 if [ "$(git config --get core.filemode)" = false ]
 then
@@ -40,7 +40,7 @@ else
 fi
 
 test_expect_success 'apply reverse' \
-    'git-apply -R --index --stat --summary --apply test-patch &&
+    'git apply -R --index --stat --summary --apply test-patch &&
      test "$(cat foo)" = "This is foo"'
 
 cat >test-patch <<\EOF
@@ -56,7 +56,7 @@ copy to bar
 EOF
 
 test_expect_success 'apply copy' \
-    'git-apply --index --stat --summary --apply test-patch &&
+    'git apply --index --stat --summary --apply test-patch &&
      test "$(cat bar)" = "This is bar" -a "$(cat foo)" = "This is foo"'
 
 test_done
index e2b1124c78540ed9613877d10eed3641268c2c8f..011126f336d4f15600ea8905b3a5b1d4c69c9a6b 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply handling binary patches
+test_description='git apply handling binary patches
 
 '
 . ./test-lib.sh
@@ -20,55 +20,55 @@ EOF
 cat file1 >file2
 cat file1 >file4
 
-git-update-index --add --remove file1 file2 file4
+git update-index --add --remove file1 file2 file4
 git-commit -m 'Initial Version' 2>/dev/null
 
 git-checkout -b binary
 tr 'x' '\0' <file1 >file3
 cat file3 >file4
-git-add file2
+git add file2
 tr '\0' 'v' <file3 >file1
 rm -f file2
-git-update-index --add --remove file1 file2 file3 file4
+git update-index --add --remove file1 file2 file3 file4
 git-commit -m 'Second Version'
 
-git-diff-tree -p master binary >B.diff
-git-diff-tree -p -C master binary >C.diff
+git diff-tree -p master binary >B.diff
+git diff-tree -p -C master binary >C.diff
 
-git-diff-tree -p --binary master binary >BF.diff
-git-diff-tree -p --binary -C master binary >CF.diff
+git diff-tree -p --binary master binary >BF.diff
+git diff-tree -p --binary -C master binary >CF.diff
 
 test_expect_success 'stat binary diff -- should not fail.' \
        'git-checkout master
-        git-apply --stat --summary B.diff'
+        git apply --stat --summary B.diff'
 
 test_expect_success 'stat binary diff (copy) -- should not fail.' \
        'git-checkout master
-        git-apply --stat --summary C.diff'
+        git apply --stat --summary C.diff'
 
 test_expect_failure 'check binary diff -- should fail.' \
        'git-checkout master
-        git-apply --check B.diff'
+        git apply --check B.diff'
 
 test_expect_failure 'check binary diff (copy) -- should fail.' \
        'git-checkout master
-        git-apply --check C.diff'
+        git apply --check C.diff'
 
 test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \
        'git-checkout master
-        git-apply --check --allow-binary-replacement B.diff'
+        git apply --check --allow-binary-replacement B.diff'
 
 test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \
        'git-checkout master
-        git-apply --check --allow-binary-replacement C.diff'
+        git apply --check --allow-binary-replacement C.diff'
 
 test_expect_success 'check binary diff with replacement.' \
        'git-checkout master
-        git-apply --check --allow-binary-replacement BF.diff'
+        git apply --check --allow-binary-replacement BF.diff'
 
 test_expect_success 'check binary diff with replacement (copy).' \
        'git-checkout master
-        git-apply --check --allow-binary-replacement CF.diff'
+        git apply --check --allow-binary-replacement CF.diff'
 
 # Now we start applying them.
 
@@ -80,36 +80,36 @@ do_reset () {
 
 test_expect_failure 'apply binary diff -- should fail.' \
        'do_reset
-        git-apply B.diff'
+        git apply B.diff'
 
 test_expect_failure 'apply binary diff -- should fail.' \
        'do_reset
-        git-apply --index B.diff'
+        git apply --index B.diff'
 
 test_expect_failure 'apply binary diff (copy) -- should fail.' \
        'do_reset
-        git-apply C.diff'
+        git apply C.diff'
 
 test_expect_failure 'apply binary diff (copy) -- should fail.' \
        'do_reset
-        git-apply --index C.diff'
+        git apply --index C.diff'
 
 test_expect_success 'apply binary diff without replacement.' \
        'do_reset
-        git-apply BF.diff'
+        git apply BF.diff'
 
 test_expect_success 'apply binary diff without replacement (copy).' \
        'do_reset
-        git-apply CF.diff'
+        git apply CF.diff'
 
 test_expect_success 'apply binary diff.' \
        'do_reset
-        git-apply --allow-binary-replacement --index BF.diff &&
-        test -z "$(git-diff --name-status binary)"'
+        git apply --allow-binary-replacement --index BF.diff &&
+        test -z "$(git diff --name-status binary)"'
 
 test_expect_success 'apply binary diff (copy).' \
        'do_reset
-        git-apply --allow-binary-replacement --index CF.diff &&
-        test -z "$(git-diff --name-status binary)"'
+        git apply --allow-binary-replacement --index CF.diff &&
+        test -z "$(git diff --name-status binary)"'
 
 test_done
index a5fb3ea40e4fde9126e66ed46fd3fc337b40ff66..64f34e329867cbef3c122c96a63cfd844b0953af 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply boundary tests
+test_description='git apply boundary tests
 
 '
 . ./test-lib.sh
index 5988e1ae4c0b394adfa743a159c32024db62bf1e..bd40a218cd81fdcb4417cb693cfbf047bc0e64c7 100755 (executable)
@@ -4,7 +4,7 @@
 # Copyright (c) 2005 Robert Fitzsimons
 #
 
-test_description='git-apply test patches with multiple fragments.
+test_description='git apply test patches with multiple fragments.
 
 '
 . ./test-lib.sh
@@ -138,8 +138,8 @@ diff --git a/main.c b/main.c
  
 EOF
 
-test_expect_success "S = git-apply (1)" \
-    'git-apply patch1.patch patch2.patch'
+test_expect_success "S = git apply (1)" \
+    'git apply patch1.patch patch2.patch'
 mv main.c main.c.git
 
 test_expect_success "S = patch (1)" \
@@ -150,8 +150,8 @@ test_expect_success "S = cmp (1)" \
 
 rm -f main.c main.c.git
 
-test_expect_success "S = git-apply (2)" \
-    'git-apply patch1.patch patch2.patch patch3.patch'
+test_expect_success "S = git apply (2)" \
+    'git apply patch1.patch patch2.patch patch3.patch'
 mv main.c main.c.git
 
 test_expect_success "S = patch (2)" \
@@ -162,8 +162,8 @@ test_expect_success "S = cmp (2)" \
 
 rm -f main.c main.c.git
 
-test_expect_success "S = git-apply (3)" \
-    'git-apply patch1.patch patch4.patch'
+test_expect_success "S = git apply (3)" \
+    'git apply patch1.patch patch4.patch'
 mv main.c main.c.git
 
 test_expect_success "S = patch (3)" \
index 005f74481625e88a4f36bd5f17d339c1924778c4..db60652a37085352837567cf8bf4b6cabb860b61 100755 (executable)
@@ -4,7 +4,7 @@
 # Copyright (c) 2005 Robert Fitzsimons
 #
 
-test_description='git-apply test for patches which require scanning forwards and backwards.
+test_description='git apply test for patches which require scanning forwards and backwards.
 
 '
 . ./test-lib.sh
@@ -86,8 +86,8 @@ diff --git a/new.txt b/new.txt
 +c2222
 EOF
 
-test_expect_success "S = git-apply scan" \
-    'git-apply patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch'
+test_expect_success "S = git apply scan" \
+    'git apply patch1.patch patch2.patch patch3.patch patch4.patch patch5.patch'
 mv new.txt apply.txt
 
 test_expect_success "S = patch scan" \
@@ -98,4 +98,3 @@ test_expect_success "S = cmp" \
     'cmp apply.txt patch.txt'
 
 test_done
-
index 69e9603c784b580ff143e4249c72aea79f64f471..70a1859503c7ee6a5b1a6db19174c1c359eec13f 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply should not get confused with rename/copy.
+test_description='git apply should not get confused with rename/copy.
 
 '
 
@@ -49,10 +49,10 @@ copy to include/arch/cris/klibc/archsetjmp.h
 - * arch/x86_64/include/klibc/archsetjmp.h
 + * arch/cris/include/klibc/archsetjmp.h
   */
+
  #ifndef _KLIBC_ARCHSETJMP_H
  #define _KLIBC_ARCHSETJMP_H
+
  struct __jmp_buf {
 -  unsigned long __rbx;
 -  unsigned long __rsp;
@@ -74,9 +74,9 @@ copy to include/arch/cris/klibc/archsetjmp.h
 +  unsigned long __sp;
 +  unsigned long __srp;
  };
+
  typedef struct __jmp_buf jmp_buf[1];
+
 -#endif /* _SETJMP_H */
 +#endif /* _KLIBC_ARCHSETJMP_H */
 diff --git a/klibc/arch/x86_64/include/klibc/archsetjmp.h b/include/arch/m32r/klibc/archsetjmp.h
@@ -90,10 +90,10 @@ rename to include/arch/m32r/klibc/archsetjmp.h
 - * arch/x86_64/include/klibc/archsetjmp.h
 + * arch/m32r/include/klibc/archsetjmp.h
   */
+
  #ifndef _KLIBC_ARCHSETJMP_H
  #define _KLIBC_ARCHSETJMP_H
+
  struct __jmp_buf {
 -  unsigned long __rbx;
 -  unsigned long __rsp;
@@ -108,17 +108,17 @@ rename to include/arch/m32r/klibc/archsetjmp.h
    unsigned long __r15;
 -  unsigned long __rip;
  };
+
  typedef struct __jmp_buf jmp_buf[1];
+
 -#endif /* _SETJMP_H */
 +#endif /* _KLIBC_ARCHSETJMP_H */
 EOF
 
-find klibc -type f -print | xargs git-update-index --add --
+find klibc -type f -print | xargs git update-index --add --
 
-test_expect_success 'check rename/copy patch' 'git-apply --check patch'
+test_expect_success 'check rename/copy patch' 'git apply --check patch'
 
-test_expect_success 'apply rename/copy patch' 'git-apply --index patch'
+test_expect_success 'apply rename/copy patch' 'git apply --index patch'
 
 test_done
index 7fd0cf62ecd1bc0723f8ad7b619e8df4afcb0acc..1c6bec044a00faf24e275280e0b9fa667356f2b3 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Catalin Marinas
 #
 
-test_description='git-apply trying to add an ending line.
+test_description='git apply trying to add an ending line.
 
 '
 . ./test-lib.sh
@@ -25,12 +25,12 @@ echo 'b' >>file
 echo 'c' >>file
 
 test_expect_success setup \
-    'git-update-index --add file'
+    'git update-index --add file'
 
 # test
 
 test_expect_failure 'apply at the end' \
-    'git-apply --index test-patch'
+    'git apply --index test-patch'
 
 cat >test-patch <<\EOF
 diff a/file b/file
@@ -45,9 +45,9 @@ EOF
 echo >file 'a
 b
 c'
-git-update-index file
+git update-index file
 
 test_expect_failure 'apply at the beginning' \
-       'git-apply --index test-patch'
+       'git apply --index test-patch'
 
 test_done
index ca81d7215710c274ca681bcb90991afa8cba5974..55334927abb33864a55f8ff49fd0c0c94a3c1769 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Eric Wong
 #
 
-test_description='git-apply should not get confused with type changes.
+test_description='git apply should not get confused with type changes.
 
 '
 
index b947ed83bb1b1d61690df1140ede337b15cb04ed..a07ff42c2ff8e9a918d83435043a73f6404a9a9e 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply symlinks and partial files
+test_description='git apply symlinks and partial files
 
 '
 
index 11e02807a5324ad263c9b2bce53be3df9daf2117..9ae2b3a8efaad4430bbcea02152470b064263b2b 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply in reverse
+test_description='git apply in reverse
 
 '
 
index 91931f0e3fde4874c865aa8dd44615b415d324ee..659e17c92e474ad5828656f83d28cab2a5ecac64 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-apply with rejects
+test_description='git apply with rejects
 
 '
 
index 27cc6f2b88974051cd521755800342e375d84af7..1d531caf798b9e8dfbe17714ad1eb09be14f7f93 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Junio C Hamano
 #
 
-test_description='git-apply with new style GNU diff with empty context
+test_description='git apply with new style GNU diff with empty context
 
 '
 
@@ -53,4 +53,3 @@ test_expect_success 'apply --apply' '
 '
 
 test_done
-
index 620a9207bf14f5a7e2506742f1e7d0ddb91d0a78..65571e05496eb3710cb89d93f5b95d34b77d1998 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Junio C Hamano
 #
 
-test_description='git-apply --whitespace=strip and configuration file.
+test_description='git apply --whitespace=strip and configuration file.
 
 '
 
@@ -24,7 +24,7 @@ cat >gpatch.file <<\EOF &&
 +++ file1+     2007-02-21 01:07:44.000000000 -0800
 @@ -1 +1 @@
 -A
-+B 
++B
 EOF
 
 sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file &&
index 2f672f30d424ace13d24236a0a9e90324cb2b4da..83d4ba679850c2ae2548bcfcce3f22227fcde8c7 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2007 Shawn O. Pearce
 #
 
-test_description='git-apply -p handling.'
+test_description='git apply -p handling.'
 
 . ./test-lib.sh
 
index 2b2f1eda21c647c5c8aa0f0d2b41cecf8d90f6c4..aff551a1d787477eb2db34d96217f66ca03c435d 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-apply for contextually independent diffs'
+test_description='git apply for contextually independent diffs'
 . ./test-lib.sh
 
 echo '1
@@ -30,4 +30,3 @@ test_expect_success \
        '( git diff test~2 test~1; git diff test~1 test~0 )| git apply'
 
 test_done
-
index 3ddfe64b02490c2fa2e8cf7445aa518e0de8bf1b..841773f75fc085d07836b39b3775f49bde5d8d19 100755 (executable)
@@ -53,4 +53,3 @@ test_expect_success 'check result' '
 '
 
 test_done
-
index c64ebbb2e9e9eaf3fbb792a4f119cd9a1c2b4352..cfcdb69dc890aed55eabf9ec8d13529538e50465 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Johannes E. Schindelin
 #
 
-test_description='git-rerere
+test_description='git rerere
 '
 
 . ./test-lib.sh
@@ -39,15 +39,32 @@ sed -e 's/To die, t/To die! T/' > a1
 echo "* END *" >>a1
 git commit -q -a -m second
 
-# activate rerere
-mkdir .git/rr-cache
+test_expect_success 'nothing recorded without rerere' '
+       (rm -rf .git/rr-cache; git config rerere.enabled false) &&
+       ! git merge first &&
+       ! test -d .git/rr-cache
+'
 
-test_expect_failure 'conflicting merge' 'git pull . first'
+# activate rerere, old style
+test_expect_success 'conflicting merge' '
+       git reset --hard &&
+       mkdir .git/rr-cache &&
+       git config --unset rerere.enabled &&
+       ! git merge first
+'
 
 sha1=$(sed -e 's/      .*//' .git/rr-cache/MERGE_RR)
 rr=.git/rr-cache/$sha1
 test_expect_success 'recorded preimage' "grep ======= $rr/preimage"
 
+test_expect_success 'rerere.enabled works, too' '
+       rm -rf .git/rr-cache &&
+       git config rerere.enabled true &&
+       git reset --hard &&
+       ! git merge first &&
+       grep ======= $rr/preimage
+'
+
 test_expect_success 'no postimage or thisimage yet' \
        "test ! -f $rr/postimage -a ! -f $rr/thisimage"
 
@@ -99,11 +116,12 @@ test_expect_success 'commit succeeds' \
 
 test_expect_success 'recorded postimage' "test -f $rr/postimage"
 
-git checkout -b third master
-git show second^:a1 | sed 's/To die: t/To die! T/' > a1
-git commit -q -a -m third
-
-test_expect_failure 'another conflicting merge' 'git pull . first'
+test_expect_success 'another conflicting merge' '
+       git checkout -b third master &&
+       git show second^:a1 | sed "s/To die: t/To die! T/" > a1 &&
+       git commit -q -a -m third &&
+       ! git pull . first
+'
 
 git show first:a1 | sed 's/To die: t/To die! T/' > expect
 test_expect_success 'rerere kicked in' "! grep ======= a1"
@@ -147,6 +165,37 @@ test_expect_success 'garbage collection (part2)' 'git rerere gc'
 test_expect_success 'old records rest in peace' \
        "test ! -f $rr/preimage && test ! -f $rr2/preimage"
 
-test_done
+test_expect_success 'file2 added differently in two branches' '
+       git reset --hard &&
+       git checkout -b fourth &&
+       echo Hallo > file2 &&
+       git add file2 &&
+       git commit -m version1 &&
+       git checkout third &&
+       echo Bello > file2 &&
+       git add file2 &&
+       git commit -m version2 &&
+       ! git merge fourth &&
+       sha1=$(sed -e "s/       .*//" .git/rr-cache/MERGE_RR) &&
+       rr=.git/rr-cache/$sha1 &&
+       echo Cello > file2 &&
+       git add file2 &&
+       git commit -m resolution
+'
 
+test_expect_success 'resolution was recorded properly' '
+       git reset --hard HEAD~2 &&
+       git checkout -b fifth &&
+       echo Hallo > file3 &&
+       git add file3 &&
+       git commit -m version1 &&
+       git checkout third &&
+       echo Bello > file3 &&
+       git add file3 &&
+       git commit -m version2 &&
+       ! git merge fifth &&
+       git diff-files -q &&
+       test Cello = "$(cat file3)"
+'
 
+test_done
index a48733cee03d4f4728ad12304b718f9a412c0f71..10a5fa9a3a2add68e883436ba22f47cfc36bc970 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2006 Johannes E. Schindelin
 #
 
-test_description='git-shortlog
+test_description='git shortlog
 '
 
 . ./test-lib.sh
index e223c074f043571ff868a2f4783c7cc155185004..1a4c53a031608a16785e6ac9a0531696156bacba 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (C) 2005 Rene Scharfe
 #
 
-test_description='git-tar-tree and git-get-tar-commit-id test
+test_description='git tar-tree and git get-tar-commit-id test
 
 This test covers the topics of file contents, commit date handling and
 commit id embedding:
@@ -13,13 +13,13 @@ commit id embedding:
   binary file (/bin/sh).  Only paths shorter than 99 characters are
   used.
 
-  git-tar-tree applies the commit date to every file in the archive it
+  git tar-tree applies the commit date to every file in the archive it
   creates.  The test sets the commit date to a specific value and checks
   if the tar archive contains that value.
 
-  When giving git-tar-tree a commit id (in contrast to a tree id) it
+  When giving git tar-tree a commit id (in contrast to a tree id) it
   embeds this commit id into the tar archive as a comment.  The test
-  checks the ability of git-get-tar-commit-id to figure it out from the
+  checks the ability of git get-tar-commit-id to figure it out from the
   tar file.
 
 '
@@ -42,23 +42,23 @@ test_expect_success \
 
 test_expect_success \
     'add files to repository' \
-    'find a -type f | xargs git-update-index --add &&
-     find a -type l | xargs git-update-index --add &&
-     treeid=`git-write-tree` &&
+    'find a -type f | xargs git update-index --add &&
+     find a -type l | xargs git update-index --add &&
+     treeid=`git write-tree` &&
      echo $treeid >treeid &&
-     git-update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
-     git-commit-tree $treeid </dev/null)'
+     git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
+     git commit-tree $treeid </dev/null)'
 
 test_expect_success \
-    'git-archive' \
-    'git-archive HEAD >b.tar'
+    'git archive' \
+    'git archive HEAD >b.tar'
 
 test_expect_success \
-    'git-tar-tree' \
-    'git-tar-tree HEAD >b2.tar'
+    'git tar-tree' \
+    'git tar-tree HEAD >b2.tar'
 
 test_expect_success \
-    'git-archive vs. git-tar-tree' \
+    'git archive vs. git tar-tree' \
     'diff b.tar b2.tar'
 
 test_expect_success \
@@ -70,9 +70,9 @@ test_expect_success \
      diff 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'
+    'git get-tar-commit-id' \
+    'git get-tar-commit-id <b.tar >b.commitid &&
+     diff .git/$(git symbolic-ref HEAD) b.commitid'
 
 test_expect_success \
     'extract tar archive' \
@@ -88,8 +88,8 @@ test_expect_success \
     'diff -r a b/a'
 
 test_expect_success \
-    'git-tar-tree with prefix' \
-    'git-tar-tree HEAD prefix >c.tar'
+    'git tar-tree with prefix' \
+    'git tar-tree HEAD prefix >c.tar'
 
 test_expect_success \
     'extract tar archive with prefix' \
@@ -105,8 +105,15 @@ test_expect_success \
     'diff -r a c/prefix/a'
 
 test_expect_success \
-    'git-archive --format=zip' \
-    'git-archive --format=zip HEAD >d.zip'
+    'git archive --format=zip' \
+    'git archive --format=zip HEAD >d.zip'
+
+$UNZIP -v >/dev/null 2>&1
+if [ $? -eq 127 ]; then
+       echo "Skipping ZIP tests, because unzip was not found"
+       test_done
+       exit
+fi
 
 test_expect_success \
     'extract ZIP archive' \
@@ -122,8 +129,8 @@ test_expect_success \
     'diff -r a d/a'
 
 test_expect_success \
-    'git-archive --format=zip with prefix' \
-    'git-archive --format=zip --prefix=prefix/ HEAD >e.zip'
+    'git archive --format=zip with prefix' \
+    'git archive --format=zip --prefix=prefix/ HEAD >e.zip'
 
 test_expect_success \
     'extract ZIP archive with prefix' \
@@ -139,7 +146,7 @@ test_expect_success \
     'diff -r a e/prefix/a'
 
 test_expect_success \
-    'git-archive --list outside of a git repo' \
-    'GIT_DIR=some/non-existing/directory git-archive --list'
+    'git archive --list outside of a git repo' \
+    'GIT_DIR=some/non-existing/directory git archive --list'
 
 test_done
index ca96918da2008e9036624fac67c6337961e3c2b0..9b1a74542a848ef702c1e6208d6ff2537b0f6ac0 100755 (executable)
@@ -3,12 +3,12 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-test_description='git-mailinfo and git-mailsplit test'
+test_description='git mailinfo and git mailsplit test'
 
 . ./test-lib.sh
 
 test_expect_success 'split sample box' \
-       'git-mailsplit -o. ../t5100/sample.mbox >last &&
+       'git mailsplit -o. ../t5100/sample.mbox >last &&
        last=`cat last` &&
        echo total is $last &&
        test `cat last` = 8'
@@ -16,7 +16,7 @@ test_expect_success 'split sample box' \
 for mail in `echo 00*`
 do
        test_expect_success "mailinfo $mail" \
-               "git-mailinfo -u msg$mail patch$mail <$mail >info$mail &&
+               "git mailinfo -u msg$mail patch$mail <$mail >info$mail &&
                echo msg &&
                diff ../t5100/msg$mail msg$mail &&
                echo patch &&
index f336769836f794c390d6e0ddd19f98da5c7b6e40..ba7579c2510fc4a4d2de4d79037640b5df644bd6 100755 (executable)
@@ -16,26 +16,26 @@ test_expect_success \
      for i in a b c
      do
             dd if=/dev/zero bs=4k count=1 | tr "\\0" $i >$i &&
-            git-update-index --add $i || return 1
+            git update-index --add $i || return 1
      done &&
-     cat c >d && echo foo >>d && git-update-index --add d &&
-     tree=`git-write-tree` &&
-     commit=`git-commit-tree $tree </dev/null` && {
+     cat c >d && echo foo >>d && git update-index --add d &&
+     tree=`git write-tree` &&
+     commit=`git commit-tree $tree </dev/null` && {
         echo $tree &&
         echo $commit &&
-        git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)       .*/\\1/"
+        git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)       .*/\\1/"
      } >obj-list && {
-        git-diff-tree --root -p $commit &&
+        git diff-tree --root -p $commit &&
         while read object
         do
-           t=`git-cat-file -t $object` &&
-           git-cat-file $t $object || return 1
+           t=`git cat-file -t $object` &&
+           git cat-file $t $object || return 1
         done <obj-list
      } >expect'
 
 test_expect_success \
     'pack without delta' \
-    'packname_1=$(git-pack-objects --window=0 test-1 <obj-list)'
+    'packname_1=$(git pack-objects --window=0 test-1 <obj-list)'
 
 rm -fr .git2
 mkdir .git2
@@ -44,9 +44,9 @@ test_expect_success \
     'unpack without delta' \
     "GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     git-init &&
-     git-unpack-objects -n <test-1-${packname_1}.pack &&
-     git-unpack-objects <test-1-${packname_1}.pack"
+     git init &&
+     git unpack-objects -n <test-1-${packname_1}.pack &&
+     git unpack-objects <test-1-${packname_1}.pack"
 
 unset GIT_OBJECT_DIRECTORY
 cd "$TRASH/.git2"
@@ -66,7 +66,7 @@ cd "$TRASH"
 test_expect_success \
     'pack with REF_DELTA' \
     'pwd &&
-     packname_2=$(git-pack-objects test-2 <obj-list)'
+     packname_2=$(git pack-objects test-2 <obj-list)'
 
 rm -fr .git2
 mkdir .git2
@@ -75,9 +75,9 @@ test_expect_success \
     'unpack with REF_DELTA' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     git-init &&
-     git-unpack-objects -n <test-2-${packname_2}.pack &&
-     git-unpack-objects <test-2-${packname_2}.pack'
+     git init &&
+     git unpack-objects -n <test-2-${packname_2}.pack &&
+     git unpack-objects <test-2-${packname_2}.pack'
 
 unset GIT_OBJECT_DIRECTORY
 cd "$TRASH/.git2"
@@ -96,7 +96,7 @@ cd "$TRASH"
 test_expect_success \
     'pack with OFS_DELTA' \
     'pwd &&
-     packname_3=$(git-pack-objects --delta-base-offset test-3 <obj-list)'
+     packname_3=$(git pack-objects --delta-base-offset test-3 <obj-list)'
 
 rm -fr .git2
 mkdir .git2
@@ -105,9 +105,9 @@ test_expect_success \
     'unpack with OFS_DELTA' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     git-init &&
-     git-unpack-objects -n <test-3-${packname_3}.pack &&
-     git-unpack-objects <test-3-${packname_3}.pack'
+     git init &&
+     git unpack-objects -n <test-3-${packname_3}.pack &&
+     git unpack-objects <test-3-${packname_3}.pack'
 
 unset GIT_OBJECT_DIRECTORY
 cd "$TRASH/.git2"
@@ -137,13 +137,13 @@ test_expect_success \
     'use packed objects' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     git-init &&
+     git init &&
      cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
-        git-diff-tree --root -p $commit &&
+        git diff-tree --root -p $commit &&
         while read object
         do
-           t=`git-cat-file -t $object` &&
-           git-cat-file $t $object || return 1
+           t=`git cat-file -t $object` &&
+           git cat-file $t $object || return 1
         done <obj-list
     } >current &&
     diff expect current'
@@ -154,11 +154,11 @@ test_expect_success \
      export GIT_OBJECT_DIRECTORY &&
      rm -f .git2/objects/pack/test-* &&
      cp test-2-${packname_2}.pack test-2-${packname_2}.idx .git2/objects/pack && {
-        git-diff-tree --root -p $commit &&
+        git diff-tree --root -p $commit &&
         while read object
         do
-           t=`git-cat-file -t $object` &&
-           git-cat-file $t $object || return 1
+           t=`git cat-file -t $object` &&
+           git cat-file $t $object || return 1
         done <obj-list
     } >current &&
     diff expect current'
@@ -169,11 +169,11 @@ test_expect_success \
      export GIT_OBJECT_DIRECTORY &&
      rm -f .git2/objects/pack/test-* &&
      cp test-3-${packname_3}.pack test-3-${packname_3}.idx .git2/objects/pack && {
-        git-diff-tree --root -p $commit &&
+        git diff-tree --root -p $commit &&
         while read object
         do
-           t=`git-cat-file -t $object` &&
-           git-cat-file $t $object || return 1
+           t=`git cat-file -t $object` &&
+           git cat-file $t $object || return 1
         done <obj-list
     } >current &&
     diff expect current'
@@ -182,7 +182,7 @@ unset GIT_OBJECT_DIRECTORY
 
 test_expect_success \
     'verify pack' \
-    'git-verify-pack   test-1-${packname_1}.idx \
+    'git verify-pack   test-1-${packname_1}.idx \
                        test-2-${packname_2}.idx \
                        test-3-${packname_3}.idx'
 
@@ -190,7 +190,7 @@ test_expect_success \
     'corrupt a pack and see if verify catches' \
     'cat test-1-${packname_1}.idx >test-3.idx &&
      cat test-2-${packname_2}.pack >test-3.pack &&
-     if git-verify-pack test-3.idx
+     if git verify-pack test-3.idx
      then false
      else :;
      fi &&
@@ -198,7 +198,7 @@ test_expect_success \
      : 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 &&
-     if git-verify-pack test-3.idx
+     if git verify-pack test-3.idx
      then false
      else :;
      fi &&
@@ -206,7 +206,7 @@ test_expect_success \
      : 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 &&
-     if git-verify-pack test-3.idx
+     if git verify-pack test-3.idx
      then false
      else :;
      fi &&
@@ -214,7 +214,7 @@ test_expect_success \
      : TYPE/SIZE byte of the first 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 &&
-     if git-verify-pack test-3.idx
+     if git verify-pack test-3.idx
      then false
      else :;
      fi &&
@@ -224,7 +224,7 @@ test_expect_success \
      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 &&
-     if git-verify-pack test-3.pack
+     if git verify-pack test-3.pack
      then false
      else :;
      fi &&
index fce77f1255378b715c23be5978fcc13e56ba263d..073ac0c6f9dd3d06474b1b81c8c7b622dcfee663 100755 (executable)
@@ -13,48 +13,48 @@ test_expect_success \
      do
          echo $i >$i &&
          test-genrandom "$i" 32768 >>$i &&
-         git-update-index --add $i || return 1
+         git update-index --add $i || return 1
      done &&
-     echo d >d && cat c >>d && git-update-index --add d &&
-     tree=`git-write-tree` &&
-     commit1=`git-commit-tree $tree </dev/null` &&
-     git-update-ref HEAD $commit1 &&
+     echo d >d && cat c >>d && git update-index --add d &&
+     tree=`git write-tree` &&
+     commit1=`git commit-tree $tree </dev/null` &&
+     git update-ref HEAD $commit1 &&
      git-repack -a -d &&
-     test "`git-count-objects`" = "0 objects, 0 kilobytes" &&
+     test "`git count-objects`" = "0 objects, 0 kilobytes" &&
      pack1=`ls .git/objects/pack/*.pack` &&
      test -f "$pack1"'
 
 test_expect_success \
     'verify-pack -v, defaults' \
-    'git-verify-pack -v "$pack1"'
+    'git verify-pack -v "$pack1"'
 
 test_expect_success \
     'verify-pack -v, packedGitWindowSize == 1 page' \
-    'git-config core.packedGitWindowSize 512 &&
-     git-verify-pack -v "$pack1"'
+    'git config core.packedGitWindowSize 512 &&
+     git verify-pack -v "$pack1"'
 
 test_expect_success \
     'verify-pack -v, packedGit{WindowSize,Limit} == 1 page' \
-    'git-config core.packedGitWindowSize 512 &&
-     git-config core.packedGitLimit 512 &&
-     git-verify-pack -v "$pack1"'
+    'git config core.packedGitWindowSize 512 &&
+     git config core.packedGitLimit 512 &&
+     git verify-pack -v "$pack1"'
 
 test_expect_success \
     'repack -a -d, packedGit{WindowSize,Limit} == 1 page' \
-    'git-config core.packedGitWindowSize 512 &&
-     git-config core.packedGitLimit 512 &&
-     commit2=`git-commit-tree $tree -p $commit1 </dev/null` &&
-     git-update-ref HEAD $commit2 &&
+    'git config core.packedGitWindowSize 512 &&
+     git config core.packedGitLimit 512 &&
+     commit2=`git commit-tree $tree -p $commit1 </dev/null` &&
+     git update-ref HEAD $commit2 &&
      git-repack -a -d &&
-     test "`git-count-objects`" = "0 objects, 0 kilobytes" &&
+     test "`git count-objects`" = "0 objects, 0 kilobytes" &&
      pack2=`ls .git/objects/pack/*.pack` &&
      test -f "$pack2"
      test "$pack1" \!= "$pack2"'
 
 test_expect_success \
     'verify-pack -v, defaults' \
-    'git-config --unset core.packedGitWindowSize &&
-     git-config --unset core.packedGitLimit &&
-     git-verify-pack -v "$pack2"'
+    'git config --unset core.packedGitWindowSize &&
+     git config --unset core.packedGitLimit &&
+     git verify-pack -v "$pack2"'
 
 test_done
index 4d06eca6a58dc8f6cb404144a31451bdcd59af11..4f58c4c3f93b1629a564f8b23ad672100d64798d 100755 (executable)
@@ -9,34 +9,34 @@ test_description='pack index with 64-bit offsets and object CRC'
 test_expect_success \
     'setup' \
     'rm -rf .git
-     git-init &&
+     git init &&
      i=1 &&
         while test $i -le 100
      do
                 i=`printf '%03i' $i`
          echo $i >file_$i &&
          test-genrandom "$i" 8192 >>file_$i &&
-         git-update-index --add file_$i &&
+         git update-index --add file_$i &&
                 i=`expr $i + 1` || return 1
      done &&
      { echo 101 && test-genrandom 100 8192; } >file_101 &&
-     git-update-index --add file_101 &&
-     tree=`git-write-tree` &&
-     commit=`git-commit-tree $tree </dev/null` && {
+     git update-index --add file_101 &&
+     tree=`git write-tree` &&
+     commit=`git commit-tree $tree </dev/null` && {
         echo $tree &&
-        git-ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)       .*/\\1/"
+        git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\)       .*/\\1/"
      } >obj-list &&
-     git-update-ref HEAD $commit'
+     git update-ref HEAD $commit'
 
 test_expect_success \
     'pack-objects with index version 1' \
-    'pack1=$(git-pack-objects --index-version=1 test-1 <obj-list) &&
-     git-verify-pack -v "test-1-${pack1}.pack"'
+    'pack1=$(git pack-objects --index-version=1 test-1 <obj-list) &&
+     git verify-pack -v "test-1-${pack1}.pack"'
 
 test_expect_success \
     'pack-objects with index version 2' \
-    'pack2=$(git-pack-objects --index-version=2 test-2 <obj-list) &&
-     git-verify-pack -v "test-2-${pack2}.pack"'
+    'pack2=$(git pack-objects --index-version=2 test-2 <obj-list) &&
+     git verify-pack -v "test-2-${pack2}.pack"'
 
 test_expect_success \
     'both packs should be identical' \
@@ -61,8 +61,8 @@ test_expect_success \
 
 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) &&
-     git-verify-pack -v "test-3-${pack3}.pack"'
+    'pack3=$(git pack-objects --index-version=2,0x40000 test-3 <obj-list) &&
+     git verify-pack -v "test-3-${pack3}.pack"'
 
 test_expect_failure \
     '64-bit offsets: should be different from previous index v2 results' \
@@ -79,22 +79,22 @@ test_expect_success \
 test_expect_success \
     '[index v1] 1) stream pack to repository' \
     'git-index-pack --index-version=1 --stdin < "test-1-${pack1}.pack" &&
-     git-prune-packed &&
-     git-count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+     git prune-packed &&
+     git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
      cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
      cmp "test-1-${pack1}.idx"  ".git/objects/pack/pack-${pack1}.idx"'
 
 test_expect_success \
     '[index v1] 2) create a stealth corruption in a delta base reference' \
     '# this test assumes a delta smaller than 16 bytes at the end of the pack
-     git-show-index <1.idx | sort -n | tail -n 1 | (
+     git show-index <1.idx | sort -n | tail -n 1 | (
        read delta_offs delta_sha1 &&
-       git-cat-file blob "$delta_sha1" > blob_1 &&
+       git cat-file blob "$delta_sha1" > blob_1 &&
        chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
        dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
          if=".git/objects/pack/pack-${pack1}.idx" skip=$((256 * 4 + 4)) \
          bs=1 count=20 conv=notrunc &&
-       git-cat-file blob "$delta_sha1" > blob_2 )'
+       git cat-file blob "$delta_sha1" > blob_2 )'
 
 test_expect_failure \
     '[index v1] 3) corrupted delta happily returned wrong data' \
@@ -102,37 +102,37 @@ test_expect_failure \
 
 test_expect_failure \
     '[index v1] 4) confirm that the pack is actually corrupted' \
-    'git-fsck --full $commit'
+    'git fsck --full $commit'
 
 test_expect_success \
     '[index v1] 5) pack-objects happily reuses corrupted data' \
-    'pack4=$(git-pack-objects test-4 <obj-list) &&
+    'pack4=$(git pack-objects test-4 <obj-list) &&
      test -f "test-4-${pack1}.pack"'
 
 test_expect_failure \
     '[index v1] 6) newly created pack is BAD !' \
-    'git-verify-pack -v "test-4-${pack1}.pack"'
+    'git verify-pack -v "test-4-${pack1}.pack"'
 
 test_expect_success \
     '[index v2] 1) stream pack to repository' \
     'rm -f .git/objects/pack/* &&
      git-index-pack --index-version=2,0x40000 --stdin < "test-1-${pack1}.pack" &&
-     git-prune-packed &&
-     git-count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
+     git prune-packed &&
+     git count-objects | ( read nr rest && test "$nr" -eq 1 ) &&
      cmp "test-1-${pack1}.pack" ".git/objects/pack/pack-${pack1}.pack" &&
      cmp "test-3-${pack1}.idx"  ".git/objects/pack/pack-${pack1}.idx"'
 
 test_expect_success \
     '[index v2] 2) create a stealth corruption in a delta base reference' \
     '# this test assumes a delta smaller than 16 bytes at the end of the pack
-     git-show-index <1.idx | sort -n | tail -n 1 | (
+     git show-index <1.idx | sort -n | tail -n 1 | (
        read delta_offs delta_sha1 delta_crc &&
-       git-cat-file blob "$delta_sha1" > blob_3 &&
+       git cat-file blob "$delta_sha1" > blob_3 &&
        chmod +w ".git/objects/pack/pack-${pack1}.pack" &&
        dd of=".git/objects/pack/pack-${pack1}.pack" seek=$(($delta_offs + 1)) \
          if=".git/objects/pack/pack-${pack1}.idx" skip=$((8 + 256 * 4)) \
          bs=1 count=20 conv=notrunc &&
-       git-cat-file blob "$delta_sha1" > blob_4 )'
+       git cat-file blob "$delta_sha1" > blob_4 )'
 
 test_expect_failure \
     '[index v2] 3) corrupted delta happily returned wrong data' \
@@ -140,10 +140,10 @@ test_expect_failure \
 
 test_expect_failure \
     '[index v2] 4) confirm that the pack is actually corrupted' \
-    'git-fsck --full $commit'
+    'git fsck --full $commit'
 
 test_expect_failure \
     '[index v2] 5) pack-objects refuses to reuse corrupted data' \
-    'git-pack-objects test-5 <obj-list'
+    'git pack-objects test-5 <obj-list'
 
 test_done
index 477b267599512fee5a5a3cf86687c3708ff967b8..6c8767e1df76323f406df56b39d4ba4596e0ab97 100755 (executable)
@@ -13,9 +13,9 @@ test_expect_success setup '
        test_tick &&
        mkdir mozart mozart/is &&
        echo "Commit #0" >mozart/is/pink &&
-       git-update-index --add mozart/is/pink &&
-       tree=$(git-write-tree) &&
-       commit=$(echo "Commit #0" | git-commit-tree $tree) &&
+       git update-index --add mozart/is/pink &&
+       tree=$(git write-tree) &&
+       commit=$(echo "Commit #0" | git commit-tree $tree) &&
        zero=$commit &&
        parent=$zero &&
        i=0 &&
@@ -24,18 +24,18 @@ test_expect_success setup '
            i=$(($i+1)) &&
            test_tick &&
            echo "Commit #$i" >mozart/is/pink &&
-           git-update-index --add mozart/is/pink &&
-           tree=$(git-write-tree) &&
-           commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) &&
-           git-update-ref refs/tags/commit$i $commit &&
+           git update-index --add mozart/is/pink &&
+           tree=$(git write-tree) &&
+           commit=$(echo "Commit #$i" | git commit-tree $tree -p $parent) &&
+           git update-ref refs/tags/commit$i $commit &&
            parent=$commit || return 1
        done &&
-       git-update-ref HEAD "$commit" &&
+       git update-ref HEAD "$commit" &&
        git-clone ./. victim &&
        cd victim &&
-       git-log &&
+       git log &&
        cd .. &&
-       git-update-ref HEAD "$zero" &&
+       git update-ref HEAD "$zero" &&
        parent=$zero &&
        i=0 &&
        while test $i -le $cnt
@@ -43,15 +43,15 @@ test_expect_success setup '
            i=$(($i+1)) &&
            test_tick &&
            echo "Rebase #$i" >mozart/is/pink &&
-           git-update-index --add mozart/is/pink &&
-           tree=$(git-write-tree) &&
-           commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) &&
-           git-update-ref refs/tags/rebase$i $commit &&
+           git update-index --add mozart/is/pink &&
+           tree=$(git write-tree) &&
+           commit=$(echo "Rebase #$i" | git commit-tree $tree -p $parent) &&
+           git update-ref refs/tags/rebase$i $commit &&
            parent=$commit || return 1
        done &&
-       git-update-ref HEAD "$commit" &&
+       git update-ref HEAD "$commit" &&
        echo Rebase &&
-       git-log'
+       git log'
 
 test_expect_success 'pack the source repository' '
        git repack -a -d &&
@@ -66,7 +66,7 @@ test_expect_success 'pack the destination repository' '
 '
 
 test_expect_success \
-        'pushing rewound head should not barf but require --force' ' 
+        'pushing rewound head should not barf but require --force' '
        # should not fail but refuse to update.
        if git-send-pack ./victim/.git/ master
        then
@@ -106,9 +106,9 @@ export HOME ;# this way we force the victim/.git/config to be used.
 test_expect_success \
         'pushing with --force should be denied with denyNonFastforwards' '
        cd victim &&
-       git-config receive.denyNonFastforwards true &&
+       git config receive.denyNonFastforwards true &&
        cd .. &&
-       git-update-ref refs/heads/master master^ || return 1
+       git update-ref refs/heads/master master^ || return 1
        git-send-pack --force ./victim/.git/ master && return 1
        ! git diff .git/refs/heads/master victim/.git/refs/heads/master
 '
index f1c7ff0c0a8082d260b136fa641a3f109172b7e6..c5dd30d0a43a708722cf4868a83c48290f9b9c9b 100755 (executable)
@@ -8,19 +8,19 @@ test_description='Test the update hook infrastructure.'
 
 test_expect_success setup '
        echo This is a test. >a &&
-       git-update-index --add a &&
-       tree0=$(git-write-tree) &&
-       commit0=$(echo setup | git-commit-tree $tree0) &&
+       git update-index --add a &&
+       tree0=$(git write-tree) &&
+       commit0=$(echo setup | git commit-tree $tree0) &&
        echo We hope it works. >a &&
-       git-update-index a &&
-       tree1=$(git-write-tree) &&
-       commit1=$(echo modify | git-commit-tree $tree1 -p $commit0) &&
-       git-update-ref refs/heads/master $commit0 &&
-       git-update-ref refs/heads/tofail $commit1 &&
+       git update-index a &&
+       tree1=$(git write-tree) &&
+       commit1=$(echo modify | git commit-tree $tree1 -p $commit0) &&
+       git update-ref refs/heads/master $commit0 &&
+       git update-ref refs/heads/tofail $commit1 &&
        git-clone ./. victim &&
-       GIT_DIR=victim/.git git-update-ref refs/heads/tofail $commit1 &&
-       git-update-ref refs/heads/master $commit1 &&
-       git-update-ref refs/heads/tofail $commit0
+       GIT_DIR=victim/.git git update-ref refs/heads/tofail $commit1 &&
+       git update-ref refs/heads/master $commit1 &&
+       git update-ref refs/heads/tofail $commit0
 '
 
 cat >victim/.git/hooks/pre-receive <<'EOF'
@@ -65,8 +65,8 @@ test_expect_failure push '
 '
 
 test_expect_success 'updated as expected' '
-       test $(GIT_DIR=victim/.git git-rev-parse master) = $commit1 &&
-       test $(GIT_DIR=victim/.git git-rev-parse tofail) = $commit1
+       test $(GIT_DIR=victim/.git git rev-parse master) = $commit1 &&
+       test $(GIT_DIR=victim/.git git rev-parse tofail) = $commit1
 '
 
 test_expect_success 'hooks ran' '
index 48e3d1705f5e4bc7f206692276b4e3e1fe1ddf66..7b6798d8b50f878c8957a60c058f6ad307f72789 100755 (executable)
@@ -25,12 +25,12 @@ add () {
        done
 
        echo "$text" > test.txt
-       git-update-index --add test.txt
-       tree=$(git-write-tree)
+       git update-index --add test.txt
+       tree=$(git write-tree)
        # make sure timestamps are in correct order
        sec=$(($sec+1))
        commit=$(echo "$text" | GIT_AUTHOR_DATE=$sec \
-               git-commit-tree $tree $parents 2>>log2.txt)
+               git commit-tree $tree $parents 2>>log2.txt)
        export $name=$commit
        echo $commit > .git/refs/heads/$branch
        eval ${branch}TIP=$commit
@@ -61,19 +61,19 @@ pull_to_client () {
                "git-fetch-pack -k -v .. $heads"
        case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
        case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
-       git-symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
+       git symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
 
-       test_expect_success "fsck" 'git-fsck --full > fsck.txt 2>&1'
+       test_expect_success "fsck" 'git fsck --full > fsck.txt 2>&1'
 
        test_expect_success 'check downloaded results' \
        'mv .git/objects/pack/pack-* . &&
         p=`ls -1 pack-*.pack` &&
-        git-unpack-objects <$p &&
-        git-fsck --full'
+        git unpack-objects <$p &&
+        git fsck --full'
 
        test_expect_success "new object count after $number pull" \
        'idx=`echo pack-*.idx` &&
-        pack_count=`git-show-index <$idx | wc -l` &&
+        pack_count=`git show-index <$idx | wc -l` &&
         test $pack_count = $count'
        test -z "$pack_count" && pack_count=0
        if [ -z "$no_strict_count_check" ]; then
@@ -97,7 +97,7 @@ pull_to_client () {
 (
        mkdir client &&
        cd client &&
-       git-init 2>> log2.txt &&
+       git init 2>> log2.txt &&
        git config transfer.unpacklimit 0
 )
 
@@ -113,7 +113,7 @@ add B1 $A1
 
 echo $ATIP > .git/refs/heads/A
 echo $BTIP > .git/refs/heads/B
-git-symbolic-ref HEAD refs/heads/B
+git symbolic-ref HEAD refs/heads/B
 
 pull_to_client 1st "B A" $((11*3))
 
@@ -129,9 +129,9 @@ pull_to_client 2nd "B" $((64*3))
 
 pull_to_client 3rd "A" $((1*3)) # old fails
 
-test_expect_success "clone shallow" "git-clone --depth 2 . shallow"
+test_expect_success "clone shallow" "git-clone --depth 2 file://`pwd`/. shallow"
 
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
 
 test_expect_success "clone shallow object count" \
        "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\""
@@ -145,7 +145,7 @@ test_expect_success "clone shallow object count (part 2)" '
 '
 
 test_expect_success "fsck in shallow repo" \
-       "(cd shallow; git-fsck --full)"
+       "(cd shallow; git fsck --full)"
 
 #test_done; exit
 
@@ -155,7 +155,7 @@ add B67 $B66
 test_expect_success "pull in shallow repo" \
        "(cd shallow; git pull .. B)"
 
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
 test_expect_success "clone shallow object count" \
        "test \"count: 6\" = \"$(grep count count.shallow)\""
 
@@ -165,14 +165,14 @@ add B69 $B68
 test_expect_success "deepening pull in shallow repo" \
        "(cd shallow; git pull --depth 4 .. B)"
 
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
 test_expect_success "clone shallow object count" \
        "test \"count: 12\" = \"$(grep count count.shallow)\""
 
 test_expect_success "deepening fetch in shallow repo" \
        "(cd shallow; git fetch --depth 4 .. A:A)"
 
-(cd shallow; git-count-objects -v) > count.shallow
+(cd shallow; git count-objects -v) > count.shallow
 test_expect_success "clone shallow object count" \
        "test \"count: 18\" = \"$(grep count count.shallow)\""
 
index 426017e1d08aad5aa3a92f9473e02defc4b10aaf..439430f569ca70b5e3b08ef07996949d7259c9b7 100755 (executable)
@@ -145,4 +145,12 @@ test_expect_success 'bundle does not prerequisite objects' '
        test 4 = $(git verify-pack -v bundle.pack | wc -l)
 '
 
+test_expect_success 'bundle should be able to create a full history' '
+
+       cd "$D" &&
+       git tag -a -m '1.0' v1.0 master &&
+       git bundle create bundle4 v1.0
+
+'
+
 test_done
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
new file mode 100755 (executable)
index 0000000..ca46aaf
--- /dev/null
@@ -0,0 +1,247 @@
+#!/bin/sh
+
+test_description='fetching and pushing, with or without wildcard'
+
+. ./test-lib.sh
+
+D=`pwd`
+
+mk_empty () {
+       rm -fr testrepo &&
+       mkdir testrepo &&
+       (
+               cd testrepo &&
+               git init &&
+               mv .git/hooks .git/hooks-disabled
+       )
+}
+
+mk_test () {
+       mk_empty &&
+       (
+               for ref in "$@"
+               do
+                       git push testrepo $the_first_commit:refs/$ref || {
+                               echo "Oops, push refs/$ref failure"
+                               exit 1
+                       }
+               done &&
+               cd testrepo &&
+               for ref in "$@"
+               do
+                       r=$(git show-ref -s --verify refs/$ref) &&
+                       test "z$r" = "z$the_first_commit" || {
+                               echo "Oops, refs/$ref is wrong"
+                               exit 1
+                       }
+               done &&
+               git fsck --full
+       )
+}
+
+check_push_result () {
+       (
+               cd testrepo &&
+               it="$1" &&
+               shift
+               for ref in "$@"
+               do
+                       r=$(git show-ref -s --verify refs/$ref) &&
+                       test "z$r" = "z$it" || {
+                               echo "Oops, refs/$ref is wrong"
+                               exit 1
+                       }
+               done &&
+               git fsck --full
+       )
+}
+
+test_expect_success setup '
+
+       : >path1 &&
+       git add path1 &&
+       test_tick &&
+       git commit -a -m repo &&
+       the_first_commit=$(git show-ref -s --verify refs/heads/master) &&
+
+       : >path2 &&
+       git add path2 &&
+       test_tick &&
+       git commit -a -m second &&
+       the_commit=$(git show-ref -s --verify refs/heads/master)
+
+'
+
+test_expect_success 'fetch without wildcard' '
+       mk_empty &&
+       (
+               cd testrepo &&
+               git fetch .. refs/heads/master:refs/remotes/origin/master &&
+
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
+test_expect_success 'fetch with wildcard' '
+       mk_empty &&
+       (
+               cd testrepo &&
+               git config remote.up.url .. &&
+               git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
+               git fetch up &&
+
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
+test_expect_success 'push without wildcard' '
+       mk_empty &&
+
+       git push testrepo refs/heads/master:refs/remotes/origin/master &&
+       (
+               cd testrepo &&
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
+test_expect_success 'push with wildcard' '
+       mk_empty &&
+
+       git push testrepo "refs/heads/*:refs/remotes/origin/*" &&
+       (
+               cd testrepo &&
+               r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+               test "z$r" = "z$the_commit" &&
+
+               test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+       )
+'
+
+test_expect_success 'push with matching heads' '
+
+       mk_test heads/master &&
+       git push testrepo &&
+       check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with no ambiguity (1)' '
+
+       mk_test heads/master &&
+       git push testrepo master:master &&
+       check_push_result $the_commit heads/master
+
+'
+
+test_expect_success 'push with no ambiguity (2)' '
+
+       mk_test remotes/origin/master &&
+       git push testrepo master:master &&
+       check_push_result $the_commit remotes/origin/master
+
+'
+
+test_expect_success 'push with weak ambiguity (1)' '
+
+       mk_test heads/master remotes/origin/master &&
+       git push testrepo master:master &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_first_commit remotes/origin/master
+
+'
+
+test_expect_success 'push with weak ambiguity (2)' '
+
+       mk_test heads/master remotes/origin/master remotes/another/master &&
+       git push testrepo master:master &&
+       check_push_result $the_commit heads/master &&
+       check_push_result $the_first_commit remotes/origin/master remotes/another/master
+
+'
+
+test_expect_success 'push with ambiguity (1)' '
+
+       mk_test remotes/origin/master remotes/frotz/master &&
+       if git push testrepo master:master
+       then
+               echo "Oops, should have failed"
+               false
+       else
+               check_push_result $the_first_commit remotes/origin/master remotes/frotz/master
+       fi
+'
+
+test_expect_success 'push with ambiguity (2)' '
+
+       mk_test heads/frotz tags/frotz &&
+       if git push testrepo master:frotz
+       then
+               echo "Oops, should have failed"
+               false
+       else
+               check_push_result $the_first_commit heads/frotz tags/frotz
+       fi
+
+'
+
+test_expect_success 'push with colon-less refspec (1)' '
+
+       mk_test heads/frotz tags/frotz &&
+       git branch -f frotz master &&
+       git push testrepo frotz &&
+       check_push_result $the_commit heads/frotz &&
+       check_push_result $the_first_commit tags/frotz
+
+'
+
+test_expect_success 'push with colon-less refspec (2)' '
+
+       mk_test heads/frotz tags/frotz &&
+       if git show-ref --verify -q refs/heads/frotz
+       then
+               git branch -D frotz
+       fi &&
+       git tag -f frotz &&
+       git push testrepo frotz &&
+       check_push_result $the_commit tags/frotz &&
+       check_push_result $the_first_commit heads/frotz
+
+'
+
+test_expect_success 'push with colon-less refspec (3)' '
+
+       mk_test &&
+       if git show-ref --verify -q refs/tags/frotz
+       then
+               git tag -d frotz
+       fi &&
+       git branch -f frotz master &&
+       git push testrepo frotz &&
+       check_push_result $the_commit heads/frotz &&
+       test 1 = $( cd testrepo && git show-ref | wc -l )
+'
+
+test_expect_success 'push with colon-less refspec (4)' '
+
+       mk_test &&
+       if git show-ref --verify -q refs/heads/frotz
+       then
+               git branch -D frotz
+       fi &&
+       git tag -f frotz &&
+       git push testrepo frotz &&
+       check_push_result $the_commit tags/frotz &&
+       test 1 = $( cd testrepo && git show-ref | wc -l )
+
+'
+
+test_done
index 243212d3dac03c19db18e9c8fcfc3b9049137cdd..93eaf2c1544b5374dbe8043c66478a0a80b0bb82 100755 (executable)
@@ -54,4 +54,3 @@ test_expect_success 'the default remote . should not break explicit pull' '
 '
 
 test_done
-
index 6d432525934980050f5c0d1683a1bc5e220b6e18..4e93aaab02e7b84b4bcf6ac70515e6cf52f0dabc 100755 (executable)
@@ -51,7 +51,7 @@ diff expected current'
 cd "$base_dir"
 
 test_expect_success 'cloning with reference (no -l -s)' \
-'git clone --reference B A D'
+'git clone --reference B file://`pwd`/A D'
 
 cd "$base_dir"
 
index b0933274db4ee73341bcc55e7d98fcddc3bae920..56f9d8ae734b130f3d6b978f00eeeaf8df2a2873 100755 (executable)
@@ -43,4 +43,21 @@ test_expect_success 'local clone from x.git that does not exist' '
        fi
 '
 
+test_expect_success 'With -no-hardlinks, local will make a copy' '
+       cd "$D" &&
+       git clone --bare --no-hardlinks x w &&
+       cd w &&
+       linked=$(find objects -type f ! -links 1 | wc -l) &&
+       test 0 = $linked
+'
+
+test_expect_success 'Even without -l, local will make a hardlink' '
+       cd "$D" &&
+       rm -fr w &&
+       git clone -l --bare x w &&
+       cd w &&
+       copied=$(find objects -type f -links 1 | wc -l) &&
+       test 0 = $copied
+'
+
 test_done
index 2f8e97cb7ea4b8bdf56d5795d512534d504d25c0..699df6ebd8b6e76f95b255783c892de23610e504 100755 (executable)
@@ -104,4 +104,3 @@ test_valid_repo'
 cd "$base_dir"
 
 test_done
-
index d40262159bce36e78dd56d931eaf1d007ef77da8..180633e1e0e37c59eeaa571ad7f7b292f35415ec 100755 (executable)
@@ -17,17 +17,17 @@ unique_commit()
        _text=$1
         _tree=$2
        shift 2
-       echo $_text | git-commit-tree $(tag $_tree) "$@"
+       echo $_text | git commit-tree $(tag $_tree) "$@"
 }
 
 # Save the output of a command into the tag specified. Prepend
 # a substitution script for the tag onto the front of sed.script
 save_tag()
 {
-       _tag=$1 
+       _tag=$1
        [ -n "$_tag" ] || error "usage: save_tag tag commit-args ..."
        shift 1
-       "$@" >.git/refs/tags/$_tag
+       "$@" >.git/refs/tags/$_tag
 
         echo "s/$(tag $_tag)/$_tag/g" > sed.script.tmp
        cat sed.script >> sed.script.tmp
@@ -35,7 +35,7 @@ save_tag()
        mv sed.script.tmp sed.script
 }
 
-# Replace unhelpful sha1 hashses with their symbolic equivalents 
+# Replace unhelpful sha1 hashses with their symbolic equivalents
 entag()
 {
        sed -f sed.script
@@ -62,7 +62,7 @@ as_author()
 commit_date()
 {
         _commit=$1
-       git-cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p" 
+       git cat-file commit $_commit | sed -n "s/^committer .*> \([0-9]*\) .*/\1/p"
 }
 
 on_committer_date()
@@ -103,14 +103,14 @@ name_from_description()
 
 # Execute the test described by the first argument, by eval'ing
 # command line specified in the 2nd argument. Check the status code
-# is zero and that the output matches the stream read from 
+# is zero and that the output matches the stream read from
 # stdin.
 test_output_expect_success()
-{      
+{
        _description=$1
         _test=$2
         [ $# -eq 2 ] || error "usage: test_output_expect_success description test <<EOF ... EOF"
         _name=$(echo $_description | name_from_description)
        cat > $_name.expected
-       test_expect_success "$_description" "check_output $_name \"$_test\"" 
+       test_expect_success "$_description" "check_output $_name \"$_test\""
 }
index fcb33027648b8449cba5869cbe808a4e26ff7f2d..8f5de097ecd703ae5f6f889ecb735f7277f361be 100755 (executable)
@@ -2,7 +2,7 @@
 #
 # Copyright (c) 2005 Jon Seymour
 #
-test_description='Tests git-rev-list --bisect functionality'
+test_description='Tests git rev-list --bisect functionality'
 
 . ./test-lib.sh
 . ../t6000lib.sh # t6xxx specific functions
@@ -16,17 +16,17 @@ test_bisection_diff()
        _max_diff=$1
        _bisect_option=$2
        shift 2
-       _bisection=$(git-rev-list $_bisect_option "$@")
-       _list_size=$(git-rev-list "$@" | wc -l)
+       _bisection=$(git rev-list $_bisect_option "$@")
+       _list_size=$(git rev-list "$@" | wc -l)
         _head=$1
        shift 1
-       _bisection_size=$(git-rev-list $_bisection "$@" | wc -l)
+       _bisection_size=$(git rev-list $_bisection "$@" | wc -l)
        [ -n "$_list_size" -a -n "$_bisection_size" ] ||
        error "test_bisection_diff failed"
 
        # Test if bisection size is close to half of list size within
        # tolerance.
-       # 
+       #
        _bisect_err=`expr $_list_size - $_bisection_size \* 2`
        test "$_bisect_err" -lt 0 && _bisect_err=`expr 0 - $_bisect_err`
        _bisect_err=`expr $_bisect_err / 2` ; # floor
@@ -37,8 +37,8 @@ test_bisection_diff()
 }
 
 date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
+git update-index --add path0
+save_tag tree git write-tree
 on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
 on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
 on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
@@ -58,7 +58,7 @@ on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3
 on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
 on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
 on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-git-update-ref HEAD $(tag l5)
+git update-ref HEAD $(tag l5)
 
 
 #     E
@@ -116,8 +116,8 @@ on_committer_date "1971-08-16 00:00:06" save_tag V unique_commit V tree -p u1 -p
 
 test_sequence()
 {
-       _bisect_option=$1       
-       
+       _bisect_option=$1
+
        test_bisection_diff 0 $_bisect_option l0 ^root
        test_bisection_diff 0 $_bisect_option l1 ^root
        test_bisection_diff 0 $_bisect_option l2 ^root
@@ -152,7 +152,7 @@ test_sequence()
        test_bisection_diff 0 $_bisect_option u3 ^U
        test_bisection_diff 0 $_bisect_option u4 ^U
        test_bisection_diff 0 $_bisect_option u5 ^U
-       
+
 #
 # the following illustrates Linus' binary bug blatt idea.
 #
@@ -163,23 +163,23 @@ test_sequence()
 # the bisection point is the head - this is the bad point.
 #
 
-test_output_expect_success "$_bisect_option l5 ^root" 'git-rev-list $_bisect_option l5 ^root' <<EOF
+test_output_expect_success "$_bisect_option l5 ^root" 'git rev-list $_bisect_option l5 ^root' <<EOF
 c3
 EOF
 
-test_output_expect_success "$_bisect_option l5 ^root ^c3" 'git-rev-list $_bisect_option l5 ^root ^c3' <<EOF
+test_output_expect_success "$_bisect_option l5 ^root ^c3" 'git rev-list $_bisect_option l5 ^root ^c3' <<EOF
 b4
 EOF
 
-test_output_expect_success "$_bisect_option l5 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l5 ^c3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option l5 ^root ^c3 ^b4" 'git rev-list $_bisect_option l5 ^c3 ^b4' <<EOF
 l3
 EOF
 
-test_output_expect_success "$_bisect_option l3 ^root ^c3 ^b4" 'git-rev-list $_bisect_option l3 ^root ^c3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option l3 ^root ^c3 ^b4" 'git rev-list $_bisect_option l3 ^root ^c3 ^b4' <<EOF
 a4
 EOF
 
-test_output_expect_success "$_bisect_option l5 ^b3 ^a3 ^b4 ^a4" 'git-rev-list $_bisect_option l3 ^b3 ^a3 ^a4' <<EOF
+test_output_expect_success "$_bisect_option l5 ^b3 ^a3 ^b4 ^a4" 'git rev-list $_bisect_option l3 ^b3 ^a3 ^a4' <<EOF
 l3
 EOF
 
@@ -187,11 +187,11 @@ EOF
 # if l3 is bad, then l4 is bad too - so advance the bad pointer by making b4 the known bad head
 #
 
-test_output_expect_success "$_bisect_option l4 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l4 ^a2 ^a3 ^a4' <<EOF
+test_output_expect_success "$_bisect_option l4 ^a2 ^a3 ^b ^a4" 'git rev-list $_bisect_option l4 ^a2 ^a3 ^a4' <<EOF
 l3
 EOF
 
-test_output_expect_success "$_bisect_option l3 ^a2 ^a3 ^b ^a4" 'git-rev-list $_bisect_option l3 ^a2 ^a3 ^a4' <<EOF
+test_output_expect_success "$_bisect_option l3 ^a2 ^a3 ^b ^a4" 'git rev-list $_bisect_option l3 ^a2 ^a3 ^a4' <<EOF
 l3
 EOF
 
@@ -201,15 +201,15 @@ EOF
 # as another example, let's consider a4 to be the bad head, in which case
 #
 
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
 c2
 EOF
 
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2' <<EOF
 c3
 EOF
 
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4 ^c2 ^c3' <<EOF
 a4
 EOF
 
@@ -219,11 +219,11 @@ EOF
 # or consider c3 to be the bad head
 #
 
-test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git-rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
+test_output_expect_success "$_bisect_option a4 ^a2 ^a3 ^b4" 'git rev-list $_bisect_option a4 ^a2 ^a3 ^b4' <<EOF
 c2
 EOF
 
-test_output_expect_success "$_bisect_option c3 ^a2 ^a3 ^b4 ^c2" 'git-rev-list $_bisect_option c3 ^a2 ^a3 ^b4 ^c2' <<EOF
+test_output_expect_success "$_bisect_option c3 ^a2 ^a3 ^b4 ^c2" 'git rev-list $_bisect_option c3 ^a2 ^a3 ^b4 ^c2' <<EOF
 c3
 EOF
 
index d99a9ad39e45c0020d385799c28dcd3cdc4e1eda..5daa0be8cc856bff513905bc6583854e0b5ae53a 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Jon Seymour
 #
 
-test_description='Tests git-rev-list --topo-order functionality'
+test_description='Tests git rev-list --topo-order functionality'
 
 . ./test-lib.sh
 . ../t6000lib.sh # t6xxx specific functions
@@ -14,8 +14,8 @@ list_duplicates()
 }
 
 date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
+git update-index --add path0
+save_tag tree git write-tree
 on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
 on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
 on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
@@ -77,13 +77,13 @@ save_tag h2 unique_commit g4 tree -p g2
 save_tag g3 unique_commit g5 tree -p g2
 save_tag g4 unique_commit g6 tree -p g3 -p h2
 
-git-update-ref HEAD $(tag l5)
+git update-ref HEAD $(tag l5)
 
-test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF
+test_output_expect_success 'rev-list has correct number of entries' 'git rev-list HEAD | wc -l | tr -d \" \"' <<EOF
 19
 EOF
 
-test_output_expect_success 'simple topo order' 'git-rev-list --topo-order  HEAD' <<EOF
+test_output_expect_success 'simple topo order' 'git rev-list --topo-order  HEAD' <<EOF
 l5
 l4
 l3
@@ -105,7 +105,7 @@ l0
 root
 EOF
 
-test_output_expect_success 'two diamonds topo order (g6)' 'git-rev-list --topo-order  g4' <<EOF
+test_output_expect_success 'two diamonds topo order (g6)' 'git rev-list --topo-order  g4' <<EOF
 g4
 h2
 g3
@@ -115,7 +115,7 @@ g1
 g0
 EOF
 
-test_output_expect_success 'multiple heads' 'git-rev-list --topo-order a3 b3 c3' <<EOF
+test_output_expect_success 'multiple heads' 'git rev-list --topo-order a3 b3 c3' <<EOF
 a3
 a2
 a1
@@ -132,7 +132,7 @@ l0
 root
 EOF
 
-test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --topo-order a3 b3 c3 ^a1' <<EOF
+test_output_expect_success 'multiple heads, prune at a1' 'git rev-list --topo-order a3 b3 c3 ^a1' <<EOF
 a3
 a2
 c3
@@ -143,7 +143,7 @@ b2
 b1
 EOF
 
-test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --topo-order a3 b3 c3 ^l1' <<EOF
+test_output_expect_success 'multiple heads, prune at l1' 'git rev-list --topo-order a3 b3 c3 ^l1' <<EOF
 a3
 a2
 a1
@@ -157,7 +157,7 @@ a0
 l2
 EOF
 
-test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --topo-order l5 ^l1' <<EOF
+test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git rev-list --topo-order l5 ^l1' <<EOF
 l5
 l4
 l3
@@ -176,7 +176,7 @@ a0
 l2
 EOF
 
-test_output_expect_success 'duplicated head arguments' 'git-rev-list --topo-order l5 l5 ^l1' <<EOF
+test_output_expect_success 'duplicated head arguments' 'git rev-list --topo-order l5 l5 ^l1' <<EOF
 l5
 l4
 l3
@@ -195,7 +195,7 @@ a0
 l2
 EOF
 
-test_output_expect_success 'prune near topo' 'git-rev-list --topo-order a4 ^c3' <<EOF
+test_output_expect_success 'prune near topo' 'git rev-list --topo-order a4 ^c3' <<EOF
 a4
 b4
 a3
@@ -204,52 +204,52 @@ a1
 b3
 EOF
 
-test_output_expect_success "head has no parent" 'git-rev-list --topo-order  root' <<EOF
+test_output_expect_success "head has no parent" 'git rev-list --topo-order  root' <<EOF
 root
 EOF
 
-test_output_expect_success "two nodes - one head, one base" 'git-rev-list --topo-order  l0' <<EOF
+test_output_expect_success "two nodes - one head, one base" 'git rev-list --topo-order  l0' <<EOF
 l0
 root
 EOF
 
-test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --topo-order  l1' <<EOF
+test_output_expect_success "three nodes one head, one internal, one base" 'git rev-list --topo-order  l1' <<EOF
 l1
 l0
 root
 EOF
 
-test_output_expect_success "linear prune l2 ^root" 'git-rev-list --topo-order  l2 ^root' <<EOF
+test_output_expect_success "linear prune l2 ^root" 'git rev-list --topo-order  l2 ^root' <<EOF
 l2
 l1
 l0
 EOF
 
-test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --topo-order  l2 ^l0' <<EOF
+test_output_expect_success "linear prune l2 ^l0" 'git rev-list --topo-order  l2 ^l0' <<EOF
 l2
 l1
 EOF
 
-test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --topo-order  l2 ^l1' <<EOF
+test_output_expect_success "linear prune l2 ^l1" 'git rev-list --topo-order  l2 ^l1' <<EOF
 l2
 EOF
 
-test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --topo-order  l5 ^a4' <<EOF
+test_output_expect_success "linear prune l5 ^a4" 'git rev-list --topo-order  l5 ^a4' <<EOF
 l5
 l4
 l3
 EOF
 
-test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --topo-order  l5 ^l3' <<EOF
+test_output_expect_success "linear prune l5 ^l3" 'git rev-list --topo-order  l5 ^l3' <<EOF
 l5
 l4
 EOF
 
-test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --topo-order  l5 ^l4' <<EOF
+test_output_expect_success "linear prune l5 ^l4" 'git rev-list --topo-order  l5 ^l4' <<EOF
 l5
 EOF
 
-test_output_expect_success "max-count 10 - topo order" 'git-rev-list --topo-order  --max-count=10 l5' <<EOF
+test_output_expect_success "max-count 10 - topo order" 'git rev-list --topo-order  --max-count=10 l5' <<EOF
 l5
 l4
 l3
@@ -262,7 +262,7 @@ a3
 a2
 EOF
 
-test_output_expect_success "max-count 10 - non topo order" 'git-rev-list --max-count=10 l5' <<EOF
+test_output_expect_success "max-count 10 - non topo order" 'git rev-list --max-count=10 l5' <<EOF
 l5
 l4
 l3
@@ -275,7 +275,7 @@ c2
 b3
 EOF
 
-test_output_expect_success '--max-age=c3, no --topo-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
+test_output_expect_success '--max-age=c3, no --topo-order' "git rev-list --max-age=$(commit_date c3) l5" <<EOF
 l5
 l4
 l3
@@ -289,7 +289,7 @@ EOF
 #
 # this test fails on --topo-order - a fix is required
 #
-#test_output_expect_success '--max-age=c3, --topo-order' "git-rev-list --topo-order --max-age=$(commit_date c3) l5" <<EOF
+#test_output_expect_success '--max-age=c3, --topo-order' "git rev-list --topo-order --max-age=$(commit_date c3) l5" <<EOF
 #l5
 #l4
 #l3
@@ -300,31 +300,31 @@ EOF
 #a2
 #EOF
 
-test_output_expect_success 'one specified head reachable from another a4, c3, --topo-order' "list_duplicates git-rev-list --topo-order a4 c3" <<EOF
+test_output_expect_success 'one specified head reachable from another a4, c3, --topo-order' "list_duplicates git rev-list --topo-order a4 c3" <<EOF
 EOF
 
-test_output_expect_success 'one specified head reachable from another c3, a4, --topo-order' "list_duplicates git-rev-list --topo-order c3 a4" <<EOF
+test_output_expect_success 'one specified head reachable from another c3, a4, --topo-order' "list_duplicates git rev-list --topo-order c3 a4" <<EOF
 EOF
 
-test_output_expect_success 'one specified head reachable from another a4, c3, no --topo-order' "list_duplicates git-rev-list a4 c3" <<EOF
+test_output_expect_success 'one specified head reachable from another a4, c3, no --topo-order' "list_duplicates git rev-list a4 c3" <<EOF
 EOF
 
-test_output_expect_success 'one specified head reachable from another c3, a4, no --topo-order' "list_duplicates git-rev-list c3 a4" <<EOF
+test_output_expect_success 'one specified head reachable from another c3, a4, no --topo-order' "list_duplicates git rev-list c3 a4" <<EOF
 EOF
 
-test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
+test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git rev-list m1" <<EOF
 EOF
 
-test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
+test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git rev-list m2" <<EOF
 EOF
 
-test_expect_success "head ^head --topo-order" 'git-rev-list --topo-order  a3 ^a3' <<EOF
+test_expect_success "head ^head --topo-order" 'git rev-list --topo-order  a3 ^a3' <<EOF
 EOF
 
-test_expect_success "head ^head no --topo-order" 'git-rev-list a3 ^a3' <<EOF
+test_expect_success "head ^head no --topo-order" 'git rev-list a3 ^a3' <<EOF
 EOF
 
-test_output_expect_success 'simple topo order (l5r1)' 'git-rev-list --topo-order  l5r1' <<EOF
+test_output_expect_success 'simple topo order (l5r1)' 'git rev-list --topo-order  l5r1' <<EOF
 l5r1
 r1
 r0
@@ -350,7 +350,7 @@ l0
 root
 EOF
 
-test_output_expect_success 'simple topo order (r1l5)' 'git-rev-list --topo-order  r1l5' <<EOF
+test_output_expect_success 'simple topo order (r1l5)' 'git rev-list --topo-order  r1l5' <<EOF
 r1l5
 l5
 l4
@@ -376,13 +376,13 @@ r0
 alt_root
 EOF
 
-test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --topo-order" <<EOF
+test_output_expect_success "don't print things unreachable from one branch" "git rev-list a3 ^b3 --topo-order" <<EOF
 a3
 a2
 a1
 EOF
 
-test_output_expect_success "--topo-order a4 l3" "git-rev-list --topo-order a4 l3" <<EOF
+test_output_expect_success "--topo-order a4 l3" "git rev-list --topo-order a4 l3" <<EOF
 l3
 a4
 c3
index 761f09b1e537ebf9c24171c646e8578d99ce95fa..80d71988b8464b6abe2b13e3052b54a4bb84604f 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-rev-list trivial path optimization test'
+test_description='git rev-list trivial path optimization test'
 
 . ./test-lib.sh
 
@@ -12,9 +12,9 @@ initial=$(git rev-parse --verify HEAD)
 '
 
 test_expect_success path-optimization '
-    commit=$(echo "Unchanged tree" | git-commit-tree "HEAD^{tree}" -p HEAD) &&
-    test $(git-rev-list $commit | wc -l) = 2 &&
-    test $(git-rev-list $commit -- . | wc -l) = 1
+    commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
+    test $(git rev-list $commit | wc -l) = 2 &&
+    test $(git rev-list $commit -- . | wc -l) = 1
 '
 
 test_expect_success 'further setup' '
index 334fccf58ca01d391d553f51711a448948fe86fa..0b64822bf621dee5c9544f76013c0342412eaee6 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-rev-list --max-count and --skip test'
+test_description='git rev-list --max-count and --skip test'
 
 . ./test-lib.sh
 
@@ -13,39 +13,39 @@ test_expect_success 'setup' '
 '
 
 test_expect_success 'no options' '
-    test $(git-rev-list HEAD | wc -l) = 5
+    test $(git rev-list HEAD | wc -l) = 5
 '
 
 test_expect_success '--max-count' '
-    test $(git-rev-list HEAD --max-count=0 | wc -l) = 0 &&
-    test $(git-rev-list HEAD --max-count=3 | wc -l) = 3 &&
-    test $(git-rev-list HEAD --max-count=5 | wc -l) = 5 &&
-    test $(git-rev-list HEAD --max-count=10 | wc -l) = 5
+    test $(git rev-list HEAD --max-count=0 | wc -l) = 0 &&
+    test $(git rev-list HEAD --max-count=3 | wc -l) = 3 &&
+    test $(git rev-list HEAD --max-count=5 | wc -l) = 5 &&
+    test $(git rev-list HEAD --max-count=10 | wc -l) = 5
 '
 
 test_expect_success '--max-count all forms' '
-    test $(git-rev-list HEAD --max-count=1 | wc -l) = 1 &&
-    test $(git-rev-list HEAD -1 | wc -l) = 1 &&
-    test $(git-rev-list HEAD -n1 | wc -l) = 1 &&
-    test $(git-rev-list HEAD -n 1 | wc -l) = 1
+    test $(git rev-list HEAD --max-count=1 | wc -l) = 1 &&
+    test $(git rev-list HEAD -1 | wc -l) = 1 &&
+    test $(git rev-list HEAD -n1 | wc -l) = 1 &&
+    test $(git rev-list HEAD -n 1 | wc -l) = 1
 '
 
 test_expect_success '--skip' '
-    test $(git-rev-list HEAD --skip=0 | wc -l) = 5 &&
-    test $(git-rev-list HEAD --skip=3 | wc -l) = 2 &&
-    test $(git-rev-list HEAD --skip=5 | wc -l) = 0 &&
-    test $(git-rev-list HEAD --skip=10 | wc -l) = 0
+    test $(git rev-list HEAD --skip=0 | wc -l) = 5 &&
+    test $(git rev-list HEAD --skip=3 | wc -l) = 2 &&
+    test $(git rev-list HEAD --skip=5 | wc -l) = 0 &&
+    test $(git rev-list HEAD --skip=10 | wc -l) = 0
 '
 
 test_expect_success '--skip --max-count' '
-    test $(git-rev-list HEAD --skip=0 --max-count=0 | wc -l) = 0 &&
-    test $(git-rev-list HEAD --skip=0 --max-count=10 | wc -l) = 5 &&
-    test $(git-rev-list HEAD --skip=3 --max-count=0 | wc -l) = 0 &&
-    test $(git-rev-list HEAD --skip=3 --max-count=1 | wc -l) = 1 &&
-    test $(git-rev-list HEAD --skip=3 --max-count=2 | wc -l) = 2 &&
-    test $(git-rev-list HEAD --skip=3 --max-count=10 | wc -l) = 2 &&
-    test $(git-rev-list HEAD --skip=5 --max-count=10 | wc -l) = 0 &&
-    test $(git-rev-list HEAD --skip=10 --max-count=10 | wc -l) = 0
+    test $(git rev-list HEAD --skip=0 --max-count=0 | wc -l) = 0 &&
+    test $(git rev-list HEAD --skip=0 --max-count=10 | wc -l) = 5 &&
+    test $(git rev-list HEAD --skip=3 --max-count=0 | wc -l) = 0 &&
+    test $(git rev-list HEAD --skip=3 --max-count=1 | wc -l) = 1 &&
+    test $(git rev-list HEAD --skip=3 --max-count=2 | wc -l) = 2 &&
+    test $(git rev-list HEAD --skip=3 --max-count=10 | wc -l) = 2 &&
+    test $(git rev-list HEAD --skip=5 --max-count=10 | wc -l) = 0 &&
+    test $(git rev-list HEAD --skip=10 --max-count=10 | wc -l) = 0
 '
 
 test_done
index aab17face8c0b326359c250bb83684ed3b894160..ad6d0b8c9da56e22b22d4fd97898f20101964e1f 100755 (executable)
@@ -1,12 +1,12 @@
 #!/bin/sh
 
-test_description='git-rev-list --pretty=format test'
+test_description='git rev-list --pretty=format test'
 
 . ./test-lib.sh
 
 test_tick
 test_expect_success 'setup' '
-touch foo && git-add foo && git-commit -m "added foo" &&
+touch foo && git add foo && git-commit -m "added foo" &&
   echo changed >foo && git-commit -a -m "changed foo"
 '
 
@@ -14,8 +14,8 @@ 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-diff expect.$1 output.$1
+git rev-list --pretty=format:$2 master >output.$1 &&
+git diff expect.$1 output.$1
 "
 }
 
@@ -113,7 +113,7 @@ and it will be encoded in iso8859-1. We should therefore
 include an iso8859 character: ¡bueno!
 EOF
 test_expect_success 'setup complex body' '
-git-config i18n.commitencoding iso8859-1 &&
+git config i18n.commitencoding iso8859-1 &&
   echo change2 >foo && git-commit -a -F commit-msg
 '
 
diff --git a/t/t6007-rev-list-cherry-pick-file.sh b/t/t6007-rev-list-cherry-pick-file.sh
new file mode 100755 (executable)
index 0000000..3faeae6
--- /dev/null
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='test git rev-list --cherry-pick -- file'
+
+. ./test-lib.sh
+
+# A---B
+#  \
+#   \
+#    C
+#
+# B changes a file foo.c, adding a line of text.  C changes foo.c as
+# well as bar.c, but the change in foo.c was identical to change B.
+
+test_expect_success setup '
+       echo Hallo > foo &&
+       git add foo &&
+       test_tick &&
+       git commit -m "A" &&
+       git tag A &&
+       git checkout -b branch &&
+       echo Bello > foo &&
+       echo Cello > bar &&
+       git add foo bar &&
+       test_tick &&
+       git commit -m "C" &&
+       git tag C &&
+       git checkout master &&
+       git checkout branch foo &&
+       test_tick &&
+       git commit -m "B" &&
+       git tag B
+'
+
+test_expect_success '--cherry-pick foo comes up empty' '
+       test -z "$(git rev-list --left-right --cherry-pick B...C -- foo)"
+'
+
+test_expect_success '--cherry-pick bar does not come up empty' '
+       ! test -z "$(git rev-list --left-right --cherry-pick B...C -- bar)"
+'
+
+test_done
index 22e0893056a27b4153be3eb533887b09a0396693..96f3d355301cf9e2daf58d219c3cf6cbdd118d82 100755 (executable)
@@ -8,7 +8,7 @@ test_description='Merge base computation.
 
 . ./test-lib.sh
 
-T=$(git-write-tree)
+T=$(git write-tree)
 
 M=1130000000
 Z=+0000
@@ -29,7 +29,7 @@ doit() {
        GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z"
        GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE
        export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
-       commit=$(echo $NAME | git-commit-tree $T $PARENTS)
+       commit=$(echo $NAME | git commit-tree $T $PARENTS)
        echo $commit >.git/refs/tags/$NAME
        echo $commit
 }
@@ -51,16 +51,16 @@ G=$(doit 7 G $B $E)
 H=$(doit 8 H $A $F)
 
 test_expect_success 'compute merge-base (single)' \
-    'MB=$(git-merge-base G H) &&
-     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+    'MB=$(git merge-base G H) &&
+     expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
 
 test_expect_success 'compute merge-base (all)' \
-    'MB=$(git-merge-base --all G H) &&
-     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+    'MB=$(git merge-base --all G H) &&
+     expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
 
 test_expect_success 'compute merge-base with show-branch' \
-    'MB=$(git-show-branch --merge-base G H) &&
-     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/B"'
+    'MB=$(git show-branch --merge-base G H) &&
+     expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/B"'
 
 # Setup for second test to demonstrate that relying on timestamps in a
 # distributed SCM to provide a _consistent_ partial ordering of commits
@@ -100,11 +100,11 @@ PL=$(doit  4 PL $L2 $C2)
 PR=$(doit  4 PR $C2 $R2)
 
 test_expect_success 'compute merge-base (single)' \
-    'MB=$(git-merge-base PL PR) &&
-     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+    'MB=$(git merge-base PL PR) &&
+     expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
 
 test_expect_success 'compute merge-base (all)' \
-    'MB=$(git-merge-base --all PL PR) &&
-     expr "$(git-name-rev "$MB")" : "[0-9a-f]* tags/C2"'
+    'MB=$(git merge-base --all PL PR) &&
+     expr "$(git name-rev "$MB")" : "[0-9a-f]* tags/C2"'
 
 test_done
index 499cafb882b83b780171c8601b9f4c54b294ed43..0ab14a6e81302db6a1239d68270ab909cb2fd216 100755 (executable)
@@ -20,7 +20,7 @@ test_expect_success 'prepare repository' \
 7
 8
 9" > file &&
-git add file && 
+git add file &&
 git commit -m "Initial commit" file &&
 git branch A &&
 git branch B &&
index c76fccfb5a0811aca3bb5af67f8558745935148f..ae3b6f28315d54349601a5c4e162a25949b626ec 100755 (executable)
@@ -56,18 +56,18 @@ printf "propter nomen suum." >> new4.txt
 
 cp new1.txt test.txt
 test_expect_success "merge without conflict" \
-       "git-merge-file test.txt orig.txt new2.txt"
+       "git merge-file test.txt orig.txt new2.txt"
 
 cp new1.txt test2.txt
 test_expect_success "merge without conflict (missing LF at EOF)" \
-       "git-merge-file test2.txt orig.txt new2.txt"
+       "git merge-file test2.txt orig.txt new2.txt"
 
 test_expect_success "merge result added missing LF" \
        "git diff test.txt test2.txt"
 
 cp test.txt backup.txt
 test_expect_failure "merge with conflicts" \
-       "git-merge-file test.txt orig.txt new3.txt"
+       "git merge-file test.txt orig.txt new3.txt"
 
 cat > expect.txt << EOF
 <<<<<<< test.txt
@@ -90,7 +90,7 @@ test_expect_success "expected conflict markers" "git diff test.txt expect.txt"
 
 cp backup.txt test.txt
 test_expect_failure "merge with conflicts, using -L" \
-       "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
+       "git merge-file -L 1 -L 2 test.txt orig.txt new3.txt"
 
 cat > expect.txt << EOF
 <<<<<<< 1
@@ -114,7 +114,7 @@ test_expect_success "expected conflict markers, with -L" \
 
 sed "s/ tu / TU /" < new1.txt > new5.txt
 test_expect_failure "conflict in removed tail" \
-       "git-merge-file -p orig.txt new1.txt new5.txt > out"
+       "git merge-file -p orig.txt new1.txt new5.txt > out"
 
 cat > expect << EOF
 Dominus regit me,
@@ -134,5 +134,9 @@ EOF
 
 test_expect_success "expected conflict markers" "git diff expect out"
 
-test_done
+test_expect_success 'binary files cannot be merged' '
+       ! git merge-file -p orig.txt ../test4012.png new1.txt 2> merge.err &&
+       grep "Cannot merge binary files" merge.err
+'
 
+test_done
index 058db9cc52521a9ba9e408c9455645d8adc4ba79..c154f03cf5f80198b9b8d19f6b0c04db11c79965 100755 (executable)
@@ -28,7 +28,7 @@ echo B > a1 &&
 GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 &&
 
 git checkout -b D A &&
-git-rev-parse B > .git/MERGE_HEAD &&
+git rev-parse B > .git/MERGE_HEAD &&
 echo D > a1 &&
 git update-index a1 &&
 GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D &&
@@ -42,19 +42,19 @@ echo C > a1 &&
 GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 &&
 
 git checkout -b E C &&
-git-rev-parse B > .git/MERGE_HEAD &&
+git rev-parse B > .git/MERGE_HEAD &&
 echo E > a1 &&
 git update-index a1 &&
 GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E &&
 
 git checkout -b G E &&
-git-rev-parse A > .git/MERGE_HEAD &&
+git rev-parse A > .git/MERGE_HEAD &&
 echo G > a1 &&
 git update-index a1 &&
 GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G &&
 
 git checkout -b F D &&
-git-rev-parse C > .git/MERGE_HEAD &&
+git rev-parse C > .git/MERGE_HEAD &&
 echo F > a1 &&
 git update-index a1 &&
 GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
index 3c1a6972bd05608d9ed1a244996e7eb72056ed6d..950c2e9b632f59a9405ba2100eb077836223291d 100755 (executable)
@@ -12,22 +12,22 @@ if core.symlinks is false.'
 
 test_expect_success \
 'setup' '
-git-config core.symlinks false &&
+git config core.symlinks false &&
 > file &&
-git-add file &&
+git add file &&
 git-commit -m initial &&
-git-branch b-symlink &&
-git-branch b-file &&
+git branch b-symlink &&
+git branch b-file &&
 l=$(echo -n file | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l        symlink" | git-update-index --index-info &&
+echo "120000 $l        symlink" | git update-index --index-info &&
 git-commit -m master &&
 git-checkout b-symlink &&
 l=$(echo -n file-different | git-hash-object -t blob -w --stdin) &&
-echo "120000 $l        symlink" | git-update-index --index-info &&
+echo "120000 $l        symlink" | git update-index --index-info &&
 git-commit -m b-symlink &&
 git-checkout b-file &&
 echo plain-file > symlink &&
-git-add symlink &&
+git add symlink &&
 git-commit -m b-file'
 
 test_expect_failure \
diff --git a/t/t6027-merge-binary.sh b/t/t6027-merge-binary.sh
new file mode 100755 (executable)
index 0000000..a7358f7
--- /dev/null
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+test_description='ask merge-recursive to merge binary files'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       cat ../test4012.png >m &&
+       git add m &&
+       git ls-files -s | sed -e "s/ 0  / 1     /" >E1 &&
+       test_tick &&
+       git commit -m "initial" &&
+
+       git branch side &&
+       echo frotz >a &&
+       git add a &&
+       echo nitfol >>m &&
+       git add a m &&
+       git ls-files -s a >E0 &&
+       git ls-files -s m | sed -e "s/ 0        / 3     /" >E3 &&
+       test_tick &&
+       git commit -m "master adds some" &&
+
+       git checkout side &&
+       echo rezrov >>m &&
+       git add m &&
+       git ls-files -s m | sed -e "s/ 0        / 2     /" >E2 &&
+       test_tick &&
+       git commit -m "side modifies" &&
+
+       git tag anchor &&
+
+       cat E0 E1 E2 E3 >expect
+'
+
+test_expect_success resolve '
+
+       rm -f a* m* &&
+       git reset --hard anchor &&
+
+       if git merge -s resolve master
+       then
+               echo Oops, should not have succeeded
+               false
+       else
+               git ls-files -s >current
+               diff -u current expect
+       fi
+'
+
+test_expect_success recursive '
+
+       rm -f a* m* &&
+       git reset --hard anchor &&
+
+       if git merge -s recursive master
+       then
+               echo Oops, should not have succeeded
+               false
+       else
+               git ls-files -s >current
+               diff -u current expect
+       fi
+'
+
+test_done
index 30f6ade13f088df1ce6c7e3649c2afb343649d65..03cdba5808aef6fbec2d95f771e6551396ff94cf 100755 (executable)
@@ -102,4 +102,3 @@ test_expect_success \
 #
 #
 test_done
-
index 7d354a1fae774ca0e77e554dad827f1a9b781818..0724864e562a53e7079c021f8b331c5b8213ac98 100755 (executable)
@@ -3,31 +3,40 @@
 # Copyright (c) 2005 Johannes Schindelin
 #
 
-test_description='Test git-rev-parse with different parent options'
+test_description='Test git rev-parse with different parent options'
 
 . ./test-lib.sh
 . ../t6000lib.sh # t6xxx specific functions
 
 date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
+git update-index --add path0
+save_tag tree git write-tree
 hide_error save_tag start unique_commit "start" tree
 save_tag second unique_commit "second" tree -p start
 hide_error save_tag start2 unique_commit "start2" tree
 save_tag two_parents unique_commit "next" tree -p second -p start2
 save_tag final unique_commit "final" tree -p two_parents
 
-test_expect_success 'start is valid' 'git-rev-parse start | grep "^[0-9a-f]\{40\}$"'
-test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git-rev-parse start^0)"
-test_expect_success 'start^1 not valid' "if git-rev-parse --verify start^1; then false; else :; fi"
-test_expect_success 'second^1 = second^' "test $(git-rev-parse second^1) = $(git-rev-parse second^)"
-test_expect_success 'final^1^1^1' "test $(git-rev-parse start) = $(git-rev-parse final^1^1^1)"
-test_expect_success 'final^1^1^1 = final^^^' "test $(git-rev-parse final^1^1^1) = $(git-rev-parse final^^^)"
-test_expect_success 'final^1^2' "test $(git-rev-parse start2) = $(git-rev-parse final^1^2)"
-test_expect_success 'final^1^2 != final^1^1' "test $(git-rev-parse final^1^2) != $(git-rev-parse final^1^1)"
-test_expect_success 'final^1^3 not valid' "if git-rev-parse --verify final^1^3; then false; else :; fi"
-test_expect_failure '--verify start2^1' 'git-rev-parse --verify start2^1'
-test_expect_success '--verify start2^0' 'git-rev-parse --verify start2^0'
+test_expect_success 'start is valid' 'git rev-parse start | grep "^[0-9a-f]\{40\}$"'
+test_expect_success 'start^0' "test $(cat .git/refs/tags/start) = $(git rev-parse start^0)"
+test_expect_success 'start^1 not valid' "if git rev-parse --verify start^1; then false; else :; fi"
+test_expect_success 'second^1 = second^' "test $(git rev-parse second^1) = $(git rev-parse second^)"
+test_expect_success 'final^1^1^1' "test $(git rev-parse start) = $(git rev-parse final^1^1^1)"
+test_expect_success 'final^1^1^1 = final^^^' "test $(git rev-parse final^1^1^1) = $(git rev-parse final^^^)"
+test_expect_success 'final^1^2' "test $(git rev-parse start2) = $(git rev-parse final^1^2)"
+test_expect_success 'final^1^2 != final^1^1' "test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)"
+test_expect_success 'final^1^3 not valid' "if git rev-parse --verify final^1^3; then false; else :; fi"
+test_expect_failure '--verify start2^1' 'git rev-parse --verify start2^1'
+test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0'
 
-test_done
+test_expect_success 'repack for next test' 'git repack -a -d'
+test_expect_success 'short SHA-1 works' '
+       start=`git rev-parse --verify start` &&
+       echo $start &&
+       abbrv=`echo $start | sed s/.\$//` &&
+       echo $abbrv &&
+       abbrv=`git rev-parse --verify $abbrv` &&
+       echo $abbrv &&
+       test $start = $abbrv'
 
+test_done
index 3e9edda1ca6a0c6c8e3022c1f639f7ec7728f90f..ae8ee11183833fd4610adb1f83763eba381f7850 100755 (executable)
@@ -28,39 +28,39 @@ check_describe () {
 test_expect_success setup '
 
        test_tick &&
-       echo one >file && git-add file && git-commit -m initial &&
-       one=$(git-rev-parse HEAD) &&
+       echo one >file && git add file && git-commit -m initial &&
+       one=$(git rev-parse HEAD) &&
 
        test_tick &&
-       echo two >file && git-add file && git-commit -m second &&
-       two=$(git-rev-parse HEAD) &&
+       echo two >file && git add file && git-commit -m second &&
+       two=$(git rev-parse HEAD) &&
 
        test_tick &&
-       echo three >file && git-add file && git-commit -m third &&
+       echo three >file && git add file && git-commit -m third &&
 
        test_tick &&
-       echo A >file && git-add file && git-commit -m A &&
+       echo A >file && git add file && git-commit -m A &&
        test_tick &&
        git-tag -a -m A A &&
 
        test_tick &&
-       echo c >file && git-add file && git-commit -m c &&
+       echo c >file && git add file && git-commit -m c &&
        test_tick &&
        git-tag c &&
 
        git reset --hard $two &&
        test_tick &&
-       echo B >side && git-add side && git-commit -m B &&
+       echo B >side && git add side && git-commit -m B &&
        test_tick &&
        git-tag -a -m B B &&
 
        test_tick &&
        git-merge -m Merged c &&
-       merged=$(git-rev-parse HEAD) &&
+       merged=$(git rev-parse HEAD) &&
 
        git reset --hard $two &&
        test_tick &&
-       echo D >another && git-add another && git-commit -m D &&
+       echo D >another && git add another && git-commit -m D &&
        test_tick &&
        git-tag -a -m D D &&
 
@@ -77,7 +77,7 @@ test_expect_success setup '
        git-merge -m Merged $merged &&
 
        test_tick &&
-       echo X >file && echo X >side && git-add file side &&
+       echo X >file && echo X >side && git add file side &&
        git-commit -m x
 
 '
index 344033249cc1ea3f7066d4d6007ade6cc1a2c5de..8b43fb5a27af181e30a4fd125260174b7b49e3c0 100755 (executable)
@@ -1,18 +1,18 @@
 #!/bin/sh
 
-test_description='git-mv in subdirs'
+test_description='git mv in subdirs'
 . ./test-lib.sh
 
 test_expect_success \
     'prepare reference tree' \
     'mkdir path0 path1 &&
      cp ../../COPYING path0/COPYING &&
-     git-add path0/COPYING &&
+     git add path0/COPYING &&
      git-commit -m add -a'
 
 test_expect_success \
     'moving the file out of subdirectory' \
-    'cd path0 && git-mv COPYING ../path1/COPYING'
+    'cd path0 && git mv COPYING ../path1/COPYING'
 
 # in path0 currently
 test_expect_success \
@@ -21,12 +21,12 @@ test_expect_success \
 
 test_expect_success \
     'checking the commit' \
-    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+    'git diff-tree -r -M --name-status  HEAD^ HEAD | \
     grep -E "^R100.+path0/COPYING.+path1/COPYING"'
 
 test_expect_success \
     'moving the file back into subdirectory' \
-    'cd path0 && git-mv ../path1/COPYING COPYING'
+    'cd path0 && git mv ../path1/COPYING COPYING'
 
 # in path0 currently
 test_expect_success \
@@ -35,18 +35,18 @@ test_expect_success \
 
 test_expect_success \
     'checking the commit' \
-    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+    'git diff-tree -r -M --name-status  HEAD^ HEAD | \
     grep -E "^R100.+path1/COPYING.+path0/COPYING"'
 
 test_expect_success \
     'adding another file' \
     'cp ../../README path0/README &&
-     git-add path0/README &&
+     git add path0/README &&
      git-commit -m add2 -a'
 
 test_expect_success \
     'moving whole subdirectory' \
-    'git-mv path0 path2'
+    'git mv path0 path2'
 
 test_expect_success \
     'commiting the change' \
@@ -54,18 +54,18 @@ test_expect_success \
 
 test_expect_success \
     'checking the commit' \
-    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+    'git diff-tree -r -M --name-status  HEAD^ HEAD | \
      grep -E "^R100.+path0/COPYING.+path2/COPYING" &&
-     git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+     git diff-tree -r -M --name-status  HEAD^ HEAD | \
      grep -E "^R100.+path0/README.+path2/README"'
 
 test_expect_success \
     'succeed when source is a prefix of destination' \
-    'git-mv path2/COPYING path2/COPYING-renamed'
+    'git mv path2/COPYING path2/COPYING-renamed'
 
 test_expect_success \
     'moving whole subdirectory into subdirectory' \
-    'git-mv path2 path1'
+    'git mv path2 path1'
 
 test_expect_success \
     'commiting the change' \
@@ -73,18 +73,18 @@ test_expect_success \
 
 test_expect_success \
     'checking the commit' \
-    'git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+    'git diff-tree -r -M --name-status  HEAD^ HEAD | \
      grep -E "^R100.+path2/COPYING.+path1/path2/COPYING" &&
-     git-diff-tree -r -M --name-status  HEAD^ HEAD | \
+     git diff-tree -r -M --name-status  HEAD^ HEAD | \
      grep -E "^R100.+path2/README.+path1/path2/README"'
 
 test_expect_failure \
     'do not move directory over existing directory' \
-    'mkdir path0 && mkdir path0/path2 && git-mv path2 path0'
+    'mkdir path0 && mkdir path0/path2 && git mv path2 path0'
 
 test_expect_success \
     'move into "."' \
-    'git-mv path1/path2/ .'
+    'git mv path1/path2/ .'
 
 test_expect_success "Michael Cassar's test case" '
        rm -fr .git papers partA &&
diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh
new file mode 100755 (executable)
index 0000000..bc6e2dd
--- /dev/null
@@ -0,0 +1,162 @@
+#!/bin/sh
+
+test_description='git-filter-branch'
+. ./test-lib.sh
+
+make_commit () {
+       lower=$(echo $1 | tr A-Z a-z)
+       echo $lower > $lower
+       git add $lower
+       test_tick
+       git commit -m $1
+       git tag $1
+}
+
+test_expect_success 'setup' '
+       make_commit A
+       make_commit B
+       git checkout -b branch B
+       make_commit D
+       make_commit E
+       git checkout master
+       make_commit C
+       git checkout branch
+       git merge C
+       git tag F
+       make_commit G
+       make_commit H
+'
+
+H=$(git rev-parse H)
+
+test_expect_success 'rewrite identically' '
+       git-filter-branch branch
+'
+test_expect_success 'result is really identical' '
+       test $H = $(git rev-parse HEAD)
+'
+
+test_expect_success 'rewrite, renaming a specific file' '
+       git-filter-branch -f --tree-filter "mv d doh || :" HEAD
+'
+
+test_expect_success 'test that the file was renamed' '
+       test d = $(git show HEAD:doh)
+'
+
+git tag oldD HEAD~4
+test_expect_success 'rewrite one branch, keeping a side branch' '
+       git branch modD oldD &&
+       git-filter-branch -f --tree-filter "mv b boh || :" D..modD
+'
+
+test_expect_success 'common ancestor is still common (unchanged)' '
+       test "$(git merge-base modD D)" = "$(git rev-parse B)"
+'
+
+test_expect_success 'filter subdirectory only' '
+       mkdir subdir &&
+       touch subdir/new &&
+       git add subdir/new &&
+       test_tick &&
+       git commit -m "subdir" &&
+       echo H > a &&
+       test_tick &&
+       git commit -m "not subdir" a &&
+       echo A > subdir/new &&
+       test_tick &&
+       git commit -m "again subdir" subdir/new &&
+       git rm a &&
+       test_tick &&
+       git commit -m "again not subdir" &&
+       git branch sub &&
+       git-filter-branch -f --subdirectory-filter subdir refs/heads/sub
+'
+
+test_expect_success 'subdirectory filter result looks okay' '
+       test 2 = $(git rev-list sub | wc -l) &&
+       git show sub:new &&
+       ! git show sub:subdir
+'
+
+test_expect_success 'setup and filter history that requires --full-history' '
+       git checkout master &&
+       mkdir subdir &&
+       echo A > subdir/new &&
+       git add subdir/new &&
+       test_tick &&
+       git commit -m "subdir on master" subdir/new &&
+       git rm a &&
+       test_tick &&
+       git commit -m "again subdir on master" &&
+       git merge branch &&
+       git branch sub-master &&
+       git-filter-branch -f --subdirectory-filter subdir sub-master
+'
+
+test_expect_success 'subdirectory filter result looks okay' '
+       test 3 = $(git rev-list -1 --parents sub-master | wc -w) &&
+       git show sub-master^:new &&
+       git show sub-master^2:new &&
+       ! git show sub:subdir
+'
+
+test_expect_success 'use index-filter to move into a subdirectory' '
+       git branch directorymoved &&
+       git-filter-branch -f --index-filter \
+                "git ls-files -s | sed \"s-\\t-&newsubdir/-\" |
+                 GIT_INDEX_FILE=\$GIT_INDEX_FILE.new \
+                       git update-index --index-info &&
+                 mv \$GIT_INDEX_FILE.new \$GIT_INDEX_FILE" directorymoved &&
+       test -z "$(git diff HEAD directorymoved:newsubdir)"'
+
+test_expect_success 'stops when msg filter fails' '
+       old=$(git rev-parse HEAD) &&
+       ! git-filter-branch -f --msg-filter false &&
+       test $old = $(git rev-parse HEAD) &&
+       rm -rf .git-rewrite
+'
+
+test_expect_success 'author information is preserved' '
+       : > i &&
+       git add i &&
+       test_tick &&
+       GIT_AUTHOR_NAME="B V Uips" git commit -m bvuips &&
+       git branch preserved-author &&
+       git-filter-branch -f --msg-filter "cat; \
+                       test \$GIT_COMMIT != $(git rev-parse master) || \
+                       echo Hallo" \
+               preserved-author &&
+       test 1 = $(git rev-list --author="B V Uips" preserved-author | wc -l)
+'
+
+test_expect_success "remove a certain author's commits" '
+       echo i > i &&
+       test_tick &&
+       git commit -m i i &&
+       git branch removed-author &&
+       git-filter-branch -f --commit-filter "\
+               if [ \"\$GIT_AUTHOR_NAME\" = \"B V Uips\" ];\
+               then\
+                       shift;\
+                       while [ -n \"\$1\" ];\
+                       do\
+                               shift;\
+                               echo \"\$1\";\
+                               shift;\
+                       done;\
+               else\
+                       git commit-tree \"\$@\";\
+               fi" removed-author &&
+       cnt1=$(git rev-list master | wc -l) &&
+       cnt2=$(git rev-list removed-author | wc -l) &&
+       test $cnt1 -eq $(($cnt2 + 1)) &&
+       test 0 = $(git rev-list --author="B V Uips" removed-author | wc -l)
+'
+
+test_expect_success 'barf on invalid name' '
+       ! git filter-branch -f master xy-problem &&
+       ! git filter-branch -f HEAD^
+'
+
+test_done
diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh
new file mode 100755 (executable)
index 0000000..c4fa446
--- /dev/null
@@ -0,0 +1,1002 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Carlos Rica
+#
+
+test_description='git-tag
+
+Tests for operations with tags.'
+
+. ./test-lib.sh
+
+# creating and listing lightweight tags:
+
+tag_exists () {
+       git show-ref --quiet --verify refs/tags/"$1"
+}
+
+# todo: git tag -l now returns always zero, when fixed, change this test
+test_expect_success 'listing all tags in an empty tree should succeed' '
+       git tag -l &&
+       git tag
+'
+
+test_expect_success 'listing all tags in an empty tree should output nothing' '
+       test `git-tag -l | wc -l` -eq 0 &&
+       test `git-tag | wc -l` -eq 0
+'
+
+test_expect_failure 'looking for a tag in an empty tree should fail' \
+       'tag_exists mytag'
+
+test_expect_success 'creating a tag in an empty tree should fail' '
+       ! git-tag mynotag &&
+       ! tag_exists mynotag
+'
+
+test_expect_success 'creating a tag for HEAD in an empty tree should fail' '
+       ! git-tag mytaghead HEAD &&
+       ! tag_exists mytaghead
+'
+
+test_expect_success 'creating a tag for an unknown revision should fail' '
+       ! git-tag mytagnorev aaaaaaaaaaa &&
+       ! tag_exists mytagnorev
+'
+
+# commit used in the tests, test_tick is also called here to freeze the date:
+test_expect_success 'creating a tag using default HEAD should succeed' '
+       test_tick &&
+       echo foo >foo &&
+       git add foo &&
+       git commit -m Foo &&
+       git tag mytag
+'
+
+test_expect_success 'listing all tags if one exists should succeed' '
+       git-tag -l &&
+       git-tag
+'
+
+test_expect_success 'listing all tags if one exists should output that tag' '
+       test `git-tag -l` = mytag &&
+       test `git-tag` = mytag
+'
+
+# pattern matching:
+
+test_expect_success 'listing a tag using a matching pattern should succeed' \
+       'git-tag -l mytag'
+
+test_expect_success \
+       'listing a tag using a matching pattern should output that tag' \
+       'test `git-tag -l mytag` = mytag'
+
+# todo: git tag -l now returns always zero, when fixed, change this test
+test_expect_success \
+       'listing tags using a non-matching pattern should suceed' \
+       'git-tag -l xxx'
+
+test_expect_success \
+       'listing tags using a non-matching pattern should output nothing' \
+       'test `git-tag -l xxx | wc -l` -eq 0'
+
+# special cases for creating tags:
+
+test_expect_failure \
+       'trying to create a tag with the name of one existing should fail' \
+       'git tag mytag'
+
+test_expect_success \
+       'trying to create a tag with a non-valid name should fail' '
+       test `git-tag -l | wc -l` -eq 1 &&
+       ! git tag "" &&
+       ! git tag .othertag &&
+       ! git tag "other tag" &&
+       ! git tag "othertag^" &&
+       ! git tag "other~tag" &&
+       test `git-tag -l | wc -l` -eq 1
+'
+
+test_expect_success 'creating a tag using HEAD directly should succeed' '
+       git tag myhead HEAD &&
+       tag_exists myhead
+'
+
+# deleting tags:
+
+test_expect_success 'trying to delete an unknown tag should fail' '
+       ! tag_exists unknown-tag &&
+       ! git-tag -d unknown-tag
+'
+
+cat >expect <<EOF
+myhead
+mytag
+EOF
+test_expect_success \
+       'trying to delete tags without params should succeed and do nothing' '
+       git tag -l > actual && git diff expect actual &&
+       git-tag -d &&
+       git tag -l > actual && git diff expect actual
+'
+
+test_expect_success \
+       'deleting two existing tags in one command should succeed' '
+       tag_exists mytag &&
+       tag_exists myhead &&
+       git-tag -d mytag myhead &&
+       ! tag_exists mytag &&
+       ! tag_exists myhead
+'
+
+test_expect_success \
+       'creating a tag with the name of another deleted one should succeed' '
+       ! tag_exists mytag &&
+       git-tag mytag &&
+       tag_exists mytag
+'
+
+test_expect_success \
+       'trying to delete two tags, existing and not, should fail in the 2nd' '
+       tag_exists mytag &&
+       ! tag_exists myhead &&
+       ! git-tag -d mytag anothertag &&
+       ! tag_exists mytag &&
+       ! tag_exists myhead
+'
+
+test_expect_failure 'trying to delete an already deleted tag should fail' \
+       'git-tag -d mytag'
+
+# listing various tags with pattern matching:
+
+cat >expect <<EOF
+a1
+aa1
+cba
+t210
+t211
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+EOF
+test_expect_success 'listing all tags should print them ordered' '
+       git tag v1.0.1 &&
+       git tag t211 &&
+       git tag aa1 &&
+       git tag v0.2.1 &&
+       git tag v1.1.3 &&
+       git tag cba &&
+       git tag a1 &&
+       git tag v1.0 &&
+       git tag t210 &&
+       git tag -l > actual &&
+       git diff expect actual &&
+       git tag > actual &&
+       git diff expect actual
+'
+
+cat >expect <<EOF
+a1
+aa1
+cba
+EOF
+test_expect_success \
+       'listing tags with substring as pattern must print those matching' '
+       git-tag -l a > actual &&
+       git diff expect actual
+'
+
+cat >expect <<EOF
+v0.2.1
+v1.0.1
+v1.1.3
+EOF
+test_expect_success \
+       'listing tags with substring as pattern must print those matching' '
+       git-tag -l .1 > actual &&
+       git diff expect actual
+'
+
+cat >expect <<EOF
+t210
+t211
+EOF
+test_expect_success \
+       'listing tags with substring as pattern must print those matching' '
+       git-tag -l t21 > actual &&
+       git diff expect actual
+'
+
+cat >expect <<EOF
+a1
+aa1
+EOF
+test_expect_success \
+       'listing tags using a name as pattern must print those matching' '
+       git-tag -l a1 > actual &&
+       git diff expect actual
+'
+
+cat >expect <<EOF
+v1.0
+v1.0.1
+EOF
+test_expect_success \
+       'listing tags using a name as pattern must print those matching' '
+       git-tag -l v1.0 > actual &&
+       git diff expect actual
+'
+
+cat >expect <<EOF
+v1.1.3
+EOF
+test_expect_success \
+       'listing tags with ? in the pattern should print those matching' '
+       git-tag -l "1.1?" > actual &&
+       git diff expect actual
+'
+
+>expect
+test_expect_success \
+       'listing tags using v.* should print nothing because none have v.' '
+       git-tag -l "v.*" > actual &&
+       git diff expect actual
+'
+
+cat >expect <<EOF
+v0.2.1
+v1.0
+v1.0.1
+v1.1.3
+EOF
+test_expect_success \
+       'listing tags using v* should print only those having v' '
+       git-tag -l "v*" > actual &&
+       git diff expect actual
+'
+
+# creating and verifying lightweight tags:
+
+test_expect_success \
+       'a non-annotated tag created without parameters should point to HEAD' '
+       git-tag non-annotated-tag &&
+       test $(git cat-file -t non-annotated-tag) = commit &&
+       test $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)
+'
+
+test_expect_failure 'trying to verify an unknown tag should fail' \
+       'git-tag -v unknown-tag'
+
+test_expect_failure \
+       'trying to verify a non-annotated and non-signed tag should fail' \
+       'git-tag -v non-annotated-tag'
+
+test_expect_failure \
+       'trying to verify many non-annotated or unknown tags, should fail' \
+       'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'
+
+# creating annotated tags:
+
+get_tag_msg () {
+       git cat-file tag "$1" | sed -e "/BEGIN PGP/q"
+}
+
+# run test_tick before committing always gives the time in that timezone
+get_tag_header () {
+cat <<EOF
+object $2
+type $3
+tag $1
+tagger C O Mitter <committer@example.com> $4 -0700
+
+EOF
+}
+
+commit=$(git rev-parse HEAD)
+time=$test_tick
+
+get_tag_header annotated-tag $commit commit $time >expect
+echo "A message" >>expect
+test_expect_success \
+       'creating an annotated tag with -m message should succeed' '
+       git-tag -m "A message" annotated-tag &&
+       get_tag_msg annotated-tag >actual &&
+       git diff expect actual
+'
+
+cat >msgfile <<EOF
+Another message
+in a file.
+EOF
+get_tag_header file-annotated-tag $commit commit $time >expect
+cat msgfile >>expect
+test_expect_success \
+       'creating an annotated tag with -F messagefile should succeed' '
+       git-tag -F msgfile file-annotated-tag &&
+       get_tag_msg file-annotated-tag >actual &&
+       git diff expect actual
+'
+
+cat >inputmsg <<EOF
+A message from the
+standard input
+EOF
+get_tag_header stdin-annotated-tag $commit commit $time >expect
+cat inputmsg >>expect
+test_expect_success 'creating an annotated tag with -F - should succeed' '
+       git-tag -F - stdin-annotated-tag <inputmsg &&
+       get_tag_msg stdin-annotated-tag >actual &&
+       git diff expect actual
+'
+
+test_expect_success \
+       'trying to create a tag with a non-existing -F file should fail' '
+       ! test -f nonexistingfile &&
+       ! tag_exists notag &&
+       ! git-tag -F nonexistingfile notag &&
+       ! tag_exists notag
+'
+
+test_expect_success \
+       'trying to create tags giving many -m or -F options should fail' '
+       echo "message file 1" >msgfile1 &&
+       echo "message file 2" >msgfile2 &&
+       ! tag_exists msgtag &&
+       ! git-tag -m "message 1" -m "message 2" msgtag &&
+       ! tag_exists msgtag &&
+       ! git-tag -F msgfile1 -F msgfile2 msgtag &&
+       ! tag_exists msgtag &&
+       ! git-tag -m "message 1" -F msgfile1 msgtag &&
+       ! tag_exists msgtag &&
+       ! git-tag -F msgfile1 -m "message 1" msgtag &&
+       ! tag_exists msgtag &&
+       ! git-tag -F msgfile1 -m "message 1" -F msgfile2 msgtag &&
+       ! tag_exists msgtag &&
+       ! git-tag -m "message 1" -F msgfile1 -m "message 2" msgtag &&
+       ! tag_exists msgtag
+'
+
+# blank and empty messages:
+
+get_tag_header empty-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with an empty -m message should succeed' '
+       git-tag -m "" empty-annotated-tag &&
+       get_tag_msg empty-annotated-tag >actual &&
+       git diff expect actual
+'
+
+>emptyfile
+get_tag_header emptyfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with an empty -F messagefile should succeed' '
+       git-tag -F emptyfile emptyfile-annotated-tag &&
+       get_tag_msg emptyfile-annotated-tag >actual &&
+       git diff expect actual
+'
+
+printf '\n\n  \n\t\nLeading blank lines\n' >blanksfile
+printf '\n\t \t  \nRepeated blank lines\n' >>blanksfile
+printf '\n\n\nTrailing spaces      \t  \n' >>blanksfile
+printf '\nTrailing blank lines\n\n\t \n\n' >>blanksfile
+get_tag_header blanks-annotated-tag $commit commit $time >expect
+cat >>expect <<EOF
+Leading blank lines
+
+Repeated blank lines
+
+Trailing spaces
+
+Trailing blank lines
+EOF
+test_expect_success \
+       'extra blanks in the message for an annotated tag should be removed' '
+       git-tag -F blanksfile blanks-annotated-tag &&
+       get_tag_msg blanks-annotated-tag >actual &&
+       git diff expect actual
+'
+
+get_tag_header blank-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with blank -m message with spaces should succeed' '
+       git-tag -m "     " blank-annotated-tag &&
+       get_tag_msg blank-annotated-tag >actual &&
+       git diff expect actual
+'
+
+echo '     ' >blankfile
+echo ''      >>blankfile
+echo '  '    >>blankfile
+get_tag_header blankfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with blank -F messagefile with spaces should succeed' '
+       git-tag -F blankfile blankfile-annotated-tag &&
+       get_tag_msg blankfile-annotated-tag >actual &&
+       git diff expect actual
+'
+
+printf '      ' >blanknonlfile
+get_tag_header blanknonlfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with -F file of spaces and no newline should succeed' '
+       git-tag -F blanknonlfile blanknonlfile-annotated-tag &&
+       get_tag_msg blanknonlfile-annotated-tag >actual &&
+       git diff expect actual
+'
+
+# messages with commented lines:
+
+cat >commentsfile <<EOF
+# A comment
+
+############
+The message.
+############
+One line.
+
+
+# commented lines
+# commented lines
+
+Another line.
+# comments
+
+Last line.
+EOF
+get_tag_header comments-annotated-tag $commit commit $time >expect
+cat >>expect <<EOF
+The message.
+One line.
+
+Another line.
+
+Last line.
+EOF
+test_expect_success \
+       'creating a tag using a -F messagefile with #comments should succeed' '
+       git-tag -F commentsfile comments-annotated-tag &&
+       get_tag_msg comments-annotated-tag >actual &&
+       git diff expect actual
+'
+
+get_tag_header comment-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with a #comment in the -m message should succeed' '
+       git-tag -m "#comment" comment-annotated-tag &&
+       get_tag_msg comment-annotated-tag >actual &&
+       git diff expect actual
+'
+
+echo '#comment' >commentfile
+echo ''         >>commentfile
+echo '####'     >>commentfile
+get_tag_header commentfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with #comments in the -F messagefile should succeed' '
+       git-tag -F commentfile commentfile-annotated-tag &&
+       get_tag_msg commentfile-annotated-tag >actual &&
+       git diff expect actual
+'
+
+printf '#comment' >commentnonlfile
+get_tag_header commentnonlfile-annotated-tag $commit commit $time >expect
+test_expect_success \
+       'creating a tag with a file of #comment and no newline should succeed' '
+       git-tag -F commentnonlfile commentnonlfile-annotated-tag &&
+       get_tag_msg commentnonlfile-annotated-tag >actual &&
+       git diff expect actual
+'
+
+# listing messages for annotated non-signed tags:
+
+test_expect_success \
+       'listing the one-line message of a non-signed tag should succeed' '
+       git-tag -m "A msg" tag-one-line &&
+
+       echo "tag-one-line" >expect &&
+       git-tag -l | grep "^tag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l | grep "^tag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l tag-one-line >actual &&
+       git diff expect actual &&
+
+       echo "tag-one-line    A msg" >expect &&
+       git-tag -n xxx -l | grep "^tag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n "" -l | grep "^tag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l | grep "^tag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n -l | grep "^tag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l tag-one-line >actual &&
+       git diff expect actual &&
+       git-tag -n 2 -l tag-one-line >actual &&
+       git diff expect actual &&
+       git-tag -n 999 -l tag-one-line >actual &&
+       git diff expect actual
+'
+
+test_expect_success \
+       'listing the zero-lines message of a non-signed tag should succeed' '
+       git-tag -m "" tag-zero-lines &&
+
+       echo "tag-zero-lines" >expect &&
+       git-tag -l | grep "^tag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l | grep "^tag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l tag-zero-lines >actual &&
+       git diff expect actual &&
+
+       echo "tag-zero-lines  " >expect &&
+       git-tag -n 1 -l | grep "^tag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n -l | grep "^tag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l tag-zero-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 2 -l tag-zero-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 999 -l tag-zero-lines >actual &&
+       git diff expect actual
+'
+
+echo 'tag line one' >annotagmsg
+echo 'tag line two' >>annotagmsg
+echo 'tag line three' >>annotagmsg
+test_expect_success \
+       'listing many message lines of a non-signed tag should succeed' '
+       git-tag -F annotagmsg tag-lines &&
+
+       echo "tag-lines" >expect &&
+       git-tag -l | grep "^tag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l | grep "^tag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l tag-lines >actual &&
+       git diff expect actual &&
+
+       echo "tag-lines       tag line one" >expect &&
+       git-tag -n 1 -l | grep "^tag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n -l | grep "^tag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l tag-lines >actual &&
+       git diff expect actual &&
+
+       echo "    tag line two" >>expect &&
+       git-tag -n 2 -l | grep "^ *tag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 2 -l tag-lines >actual &&
+       git diff expect actual &&
+
+       echo "    tag line three" >>expect &&
+       git-tag -n 3 -l | grep "^ *tag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 3 -l tag-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 4 -l | grep "^ *tag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 4 -l tag-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 99 -l | grep "^ *tag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 99 -l tag-lines >actual &&
+       git diff expect actual
+'
+
+# trying to verify annotated non-signed tags:
+
+test_expect_success \
+       'trying to verify an annotated non-signed tag should fail' '
+       tag_exists annotated-tag &&
+       ! git-tag -v annotated-tag
+'
+
+test_expect_success \
+       'trying to verify a file-annotated non-signed tag should fail' '
+       tag_exists file-annotated-tag &&
+       ! git-tag -v file-annotated-tag
+'
+
+test_expect_success \
+       'trying to verify two annotated non-signed tags should fail' '
+       tag_exists annotated-tag file-annotated-tag &&
+       ! git-tag -v annotated-tag file-annotated-tag
+'
+
+# creating and verifying signed tags:
+
+gpg --version >/dev/null
+if [ $? -eq 127 ]; then
+       echo "Skipping signed tags tests, because gpg was not found"
+       test_done
+       exit
+fi
+
+# 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>
+# No password given, to enable non-interactive operation.
+
+cp -R ../t7004 ./gpghome
+chmod 0700 gpghome
+export GNUPGHOME="$(pwd)/gpghome"
+
+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' '
+       git-tag -s -m "A signed tag message" signed-tag &&
+       get_tag_msg signed-tag >actual &&
+       git diff expect actual
+'
+
+cat >sigmsgfile <<EOF
+Another signed tag
+message in a file.
+EOF
+get_tag_header file-signed-tag $commit commit $time >expect
+cat sigmsgfile >>expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       'creating a signed tag with -F messagefile should succeed' '
+       git-tag -s -F sigmsgfile file-signed-tag &&
+       get_tag_msg file-signed-tag >actual &&
+       git diff expect actual
+'
+
+cat >siginputmsg <<EOF
+A signed tag message from
+the standard input
+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' '
+       git-tag -s -F - stdin-signed-tag <siginputmsg &&
+       get_tag_msg stdin-signed-tag >actual &&
+       git diff expect actual
+'
+
+test_expect_success \
+       'trying to create a signed tag with non-existing -F file should fail' '
+       ! test -f nonexistingfile &&
+       ! tag_exists nosigtag &&
+       ! git-tag -s -F nonexistingfile nosigtag &&
+       ! tag_exists nosigtag
+'
+
+test_expect_success 'verifying a signed tag should succeed' \
+       'git-tag -v signed-tag'
+
+test_expect_success 'verifying two signed tags in one command should succeed' \
+       'git-tag -v signed-tag file-signed-tag'
+
+test_expect_success \
+       'verifying many signed and non-signed tags should fail' '
+       ! git-tag -v signed-tag annotated-tag &&
+       ! git-tag -v file-annotated-tag file-signed-tag &&
+       ! git-tag -v annotated-tag file-signed-tag file-annotated-tag &&
+       ! git-tag -v signed-tag annotated-tag file-signed-tag
+'
+
+test_expect_success 'verifying a forged tag should fail' '
+       forged=$(git cat-file tag signed-tag |
+               sed -e "s/signed-tag/forged-tag/" |
+               git mktag) &&
+       git tag forged-tag $forged &&
+       ! git-tag -v forged-tag
+'
+
+# blank and empty messages for signed tags:
+
+get_tag_header empty-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v empty-signed-tag
+'
+
+>sigemptyfile
+get_tag_header emptyfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v emptyfile-signed-tag
+'
+
+printf '\n\n  \n\t\nLeading blank lines\n' > sigblanksfile
+printf '\n\t \t  \nRepeated blank lines\n' >>sigblanksfile
+printf '\n\n\nTrailing spaces      \t  \n' >>sigblanksfile
+printf '\nTrailing blank lines\n\n\t \n\n' >>sigblanksfile
+get_tag_header blanks-signed-tag $commit commit $time >expect
+cat >>expect <<EOF
+Leading blank lines
+
+Repeated blank lines
+
+Trailing spaces
+
+Trailing blank lines
+EOF
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v blanks-signed-tag
+'
+
+get_tag_header blank-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v blank-signed-tag
+'
+
+echo '     ' >sigblankfile
+echo ''      >>sigblankfile
+echo '  '    >>sigblankfile
+get_tag_header blankfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v blankfile-signed-tag
+'
+
+printf '      ' >sigblanknonlfile
+get_tag_header blanknonlfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v signed-tag
+'
+
+# messages with commented lines for signed tags:
+
+cat >sigcommentsfile <<EOF
+# A comment
+
+############
+The message.
+############
+One line.
+
+
+# commented lines
+# commented lines
+
+Another line.
+# comments
+
+Last line.
+EOF
+get_tag_header comments-signed-tag $commit commit $time >expect
+cat >>expect <<EOF
+The message.
+One line.
+
+Another line.
+
+Last line.
+EOF
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v comments-signed-tag
+'
+
+get_tag_header comment-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v comment-signed-tag
+'
+
+echo '#comment' >sigcommentfile
+echo ''         >>sigcommentfile
+echo '####'     >>sigcommentfile
+get_tag_header commentfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v commentfile-signed-tag
+'
+
+printf '#comment' >sigcommentnonlfile
+get_tag_header commentnonlfile-signed-tag $commit commit $time >expect
+echo '-----BEGIN PGP SIGNATURE-----' >>expect
+test_expect_success \
+       '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 &&
+       git diff expect actual &&
+       git-tag -v commentnonlfile-signed-tag
+'
+
+# listing messages for signed tags:
+
+test_expect_success \
+       'listing the one-line message of a signed tag should succeed' '
+       git-tag -s -m "A message line signed" stag-one-line &&
+
+       echo "stag-one-line" >expect &&
+       git-tag -l | grep "^stag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l | grep "^stag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l stag-one-line >actual &&
+       git diff expect actual &&
+
+       echo "stag-one-line   A message line signed" >expect &&
+       git-tag -n xxx -l | grep "^stag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n "" -l | grep "^stag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l | grep "^stag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n -l | grep "^stag-one-line" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l stag-one-line >actual &&
+       git diff expect actual &&
+       git-tag -n 2 -l stag-one-line >actual &&
+       git diff expect actual &&
+       git-tag -n 999 -l stag-one-line >actual &&
+       git diff expect actual
+'
+
+test_expect_success \
+       'listing the zero-lines message of a signed tag should succeed' '
+       git-tag -s -m "" stag-zero-lines &&
+
+       echo "stag-zero-lines" >expect &&
+       git-tag -l | grep "^stag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l | grep "^stag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l stag-zero-lines >actual &&
+       git diff expect actual &&
+
+       echo "stag-zero-lines " >expect &&
+       git-tag -n 1 -l | grep "^stag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n -l | grep "^stag-zero-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l stag-zero-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 2 -l stag-zero-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 999 -l stag-zero-lines >actual &&
+       git diff expect actual
+'
+
+echo 'stag line one' >sigtagmsg
+echo 'stag line two' >>sigtagmsg
+echo 'stag line three' >>sigtagmsg
+test_expect_success \
+       'listing many message lines of a signed tag should succeed' '
+       git-tag -s -F sigtagmsg stag-lines &&
+
+       echo "stag-lines" >expect &&
+       git-tag -l | grep "^stag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l | grep "^stag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 0 -l stag-lines >actual &&
+       git diff expect actual &&
+
+       echo "stag-lines      stag line one" >expect &&
+       git-tag -n 1 -l | grep "^stag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n -l | grep "^stag-lines" >actual &&
+       git diff expect actual &&
+       git-tag -n 1 -l stag-lines >actual &&
+       git diff expect actual &&
+
+       echo "    stag line two" >>expect &&
+       git-tag -n 2 -l | grep "^ *stag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 2 -l stag-lines >actual &&
+       git diff expect actual &&
+
+       echo "    stag line three" >>expect &&
+       git-tag -n 3 -l | grep "^ *stag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 3 -l stag-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 4 -l | grep "^ *stag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 4 -l stag-lines >actual &&
+       git diff expect actual &&
+       git-tag -n 99 -l | grep "^ *stag.line" >actual &&
+       git diff expect actual &&
+       git-tag -n 99 -l stag-lines >actual &&
+       git diff expect actual
+'
+
+# tags pointing to objects different from commits:
+
+tree=$(git rev-parse HEAD^{tree})
+blob=$(git rev-parse HEAD:foo)
+tag=$(git rev-parse signed-tag)
+
+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 \
+       '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 &&
+       git diff expect actual
+'
+
+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 \
+       '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 &&
+       git diff expect actual
+'
+
+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 \
+       '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 &&
+       git diff expect actual
+'
+
+# try to verify without gpg:
+
+rm -rf gpghome
+test_expect_failure \
+       'verify signed tag fails when public key is not present' \
+       'git-tag -v signed-tag'
+
+test_done
diff --git a/t/t7004/pubring.gpg b/t/t7004/pubring.gpg
new file mode 100644 (file)
index 0000000..83855fa
Binary files /dev/null and b/t/t7004/pubring.gpg differ
diff --git a/t/t7004/random_seed b/t/t7004/random_seed
new file mode 100644 (file)
index 0000000..8fed133
Binary files /dev/null and b/t/t7004/random_seed differ
diff --git a/t/t7004/secring.gpg b/t/t7004/secring.gpg
new file mode 100644 (file)
index 0000000..d831cd9
Binary files /dev/null and b/t/t7004/secring.gpg differ
diff --git a/t/t7004/trustdb.gpg b/t/t7004/trustdb.gpg
new file mode 100644 (file)
index 0000000..abace96
Binary files /dev/null and b/t/t7004/trustdb.gpg differ
diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh
new file mode 100755 (executable)
index 0000000..28643b0
--- /dev/null
@@ -0,0 +1,91 @@
+#!/bin/sh
+
+test_description='GIT_EDITOR, core.editor, and stuff'
+
+. ./test-lib.sh
+
+for i in GIT_EDITOR core_editor EDITOR VISUAL vi
+do
+       cat >e-$i.sh <<-EOF
+       echo "Edited by $i" >"\$1"
+       EOF
+       chmod +x e-$i.sh
+done
+unset vi
+mv e-vi.sh vi
+PATH=".:$PATH"
+unset EDITOR VISUAL GIT_EDITOR
+
+test_expect_success setup '
+
+       msg="Hand edited" &&
+       echo "$msg" >expect &&
+       git add vi &&
+       test_tick &&
+       git commit -m "$msg" &&
+       git show -s --pretty=oneline |
+       sed -e "s/^[0-9a-f]* //" >actual &&
+       diff actual expect
+
+'
+
+TERM=dumb
+export TERM
+test_expect_success 'dumb should error out when falling back on vi' '
+
+       if git commit --amend
+       then
+               echo "Oops?"
+               exit 1
+       else
+               : happy
+       fi
+'
+
+TERM=vt100
+export TERM
+for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+do
+       echo "Edited by $i" >expect
+       unset EDITOR VISUAL GIT_EDITOR
+       git config --unset-all core.editor
+       case "$i" in
+       core_editor)
+               git config core.editor ./e-core_editor.sh
+               ;;
+       [A-Z]*)
+               eval "$i=./e-$i.sh"
+               export $i
+               ;;
+       esac
+       test_expect_success "Using $i" '
+               git commit --amend &&
+               git show -s --pretty=oneline |
+               sed -e "s/^[0-9a-f]* //" >actual &&
+               diff actual expect
+       '
+done
+
+unset EDITOR VISUAL GIT_EDITOR
+git config --unset-all core.editor
+for i in vi EDITOR VISUAL core_editor GIT_EDITOR
+do
+       echo "Edited by $i" >expect
+       case "$i" in
+       core_editor)
+               git config core.editor ./e-core_editor.sh
+               ;;
+       [A-Z]*)
+               eval "$i=./e-$i.sh"
+               export $i
+               ;;
+       esac
+       test_expect_success "Using $i (override)" '
+               git commit --amend &&
+               git show -s --pretty=oneline |
+               sed -e "s/^[0-9a-f]* //" >actual &&
+               diff actual expect
+       '
+done
+
+test_done
index a9191407f21c748f4c00bf909f670fc2b5124ec3..66d40430b293b2c1f8c7bc72416730e502c41f58 100755 (executable)
@@ -10,7 +10,7 @@ test_expect_success \
     'creating initial files' \
     'mkdir path0 &&
      cp ../../COPYING path0/COPYING &&
-     git-add path0/COPYING &&
+     git add path0/COPYING &&
      git-commit -m add -a'
 
 test_expect_success \
@@ -21,10 +21,10 @@ test_expect_success \
      cp ../../COPYING path1/COPYING &&
      cp ../../COPYING COPYING &&
      cp ../../COPYING path0/COPYING-TOO &&
-     git-add path1/path2/COPYING &&
-     git-add path1/COPYING &&
-     git-add COPYING &&
-     git-add path0/COPYING-TOO &&
+     git add path1/path2/COPYING &&
+     git add path1/COPYING &&
+     git add COPYING &&
+     git add path0/COPYING-TOO &&
      git-commit -m change -a'
 
 test_expect_success \
index de70b38d1cc2f8a16bd1fd68fc24d0bef82def97..eb0847afe9825f4af46065c2ee38282c44789bfb 100755 (executable)
@@ -13,10 +13,10 @@ test_expect_success 'setup' '
        touch src/part1.c Makefile &&
        echo build >.gitignore &&
        echo \*.o >>.gitignore &&
-       git-add . &&
+       git add . &&
        git-commit -m setup &&
        touch src/part2.c README &&
-       git-add .
+       git add .
 
 '
 
@@ -141,7 +141,7 @@ test_expect_success 'git-clean -d -X' '
 
 test_expect_success 'clean.requireForce' '
 
-       git-config clean.requireForce true &&
+       git config clean.requireForce true &&
        ! git-clean
 
 '
diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh
new file mode 100755 (executable)
index 0000000..e8ce7cd
--- /dev/null
@@ -0,0 +1,178 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Lars Hjemli
+#
+
+test_description='Basic porcelain support for submodules
+
+This test tries to verify basic sanity of the init, update and status
+subcommands of git-submodule.
+'
+
+. ./test-lib.sh
+
+#
+# Test setup:
+#  -create a repository in directory lib
+#  -add a couple of files
+#  -add directory lib to 'superproject', this creates a DIRLINK entry
+#  -add a couple of regular files to enable testing of submodule filtering
+#  -mv lib subrepo
+#  -add an entry to .gitmodules for submodule 'example'
+#
+test_expect_success 'Prepare submodule testing' '
+       : > t &&
+       git-add t &&
+       git-commit -m "initial commit" &&
+       git branch initial HEAD &&
+       mkdir lib &&
+       cd lib &&
+       git init &&
+       echo a >a &&
+       git add a &&
+       git-commit -m "submodule commit 1" &&
+       git-tag -a -m "rev-1" rev-1 &&
+       rev1=$(git rev-parse HEAD) &&
+       if test -z "$rev1"
+       then
+               echo "[OOPS] submodule git rev-parse returned nothing"
+               false
+       fi &&
+       cd .. &&
+       echo a >a &&
+       echo z >z &&
+       git add a lib z &&
+       git-commit -m "super commit 1" &&
+       mv lib .subrepo &&
+       GIT_CONFIG=.gitmodules git config submodule.example.url git://example.com/lib.git
+'
+
+test_expect_success 'status should fail for unmapped paths' '
+       if git-submodule status
+       then
+               echo "[OOPS] submodule status succeeded"
+               false
+       elif ! GIT_CONFIG=.gitmodules git config submodule.example.path lib
+       then
+               echo "[OOPS] git config failed to update .gitmodules"
+               false
+       fi
+'
+
+test_expect_success 'status should only print one line' '
+       lines=$(git-submodule status | wc -l) &&
+       test $lines = 1
+'
+
+test_expect_success 'status should initially be "missing"' '
+       git-submodule status | grep "^-$rev1"
+'
+
+test_expect_success 'init should register submodule url in .git/config' '
+       git-submodule init &&
+       url=$(git config submodule.example.url) &&
+       if test "$url" != "git://example.com/lib.git"
+       then
+               echo "[OOPS] init succeeded but submodule url is wrong"
+               false
+       elif ! git config submodule.example.url ./.subrepo
+       then
+               echo "[OOPS] init succeeded but update of url failed"
+               false
+       fi
+'
+
+test_expect_success 'update should fail when path is used by a file' '
+       echo "hello" >lib &&
+       if git-submodule update
+       then
+               echo "[OOPS] update should have failed"
+               false
+       elif test "$(cat lib)" != "hello"
+       then
+               echo "[OOPS] update failed but lib file was molested"
+               false
+       else
+               rm lib
+       fi
+'
+
+test_expect_success 'update should fail when path is used by a nonempty directory' '
+       mkdir lib &&
+       echo "hello" >lib/a &&
+       if git-submodule update
+       then
+               echo "[OOPS] update should have failed"
+               false
+       elif test "$(cat lib/a)" != "hello"
+       then
+               echo "[OOPS] update failed but lib/a was molested"
+               false
+       else
+               rm lib/a
+       fi
+'
+
+test_expect_success 'update should work when path is an empty dir' '
+       rm -rf lib &&
+       mkdir lib &&
+       git-submodule update &&
+       head=$(cd lib && git rev-parse HEAD) &&
+       if test -z "$head"
+       then
+               echo "[OOPS] Failed to obtain submodule head"
+               false
+       elif test "$head" != "$rev1"
+       then
+               echo "[OOPS] Submodule head is $head but should have been $rev1"
+               false
+       fi
+'
+
+test_expect_success 'status should be "up-to-date" after update' '
+       git-submodule status | grep "^ $rev1"
+'
+
+test_expect_success 'status should be "modified" after submodule commit' '
+       cd lib &&
+       echo b >b &&
+       git add b &&
+       git-commit -m "submodule commit 2" &&
+       rev2=$(git rev-parse HEAD) &&
+       cd .. &&
+       if test -z "$rev2"
+       then
+               echo "[OOPS] submodule git rev-parse returned nothing"
+               false
+       fi &&
+       git-submodule status | grep "^+$rev2"
+'
+
+test_expect_success 'the --cached sha1 should be rev1' '
+       git-submodule --cached status | grep "^+$rev1"
+'
+
+test_expect_success 'update should checkout rev1' '
+       git-submodule update &&
+       head=$(cd lib && git rev-parse HEAD) &&
+       if test -z "$head"
+       then
+               echo "[OOPS] submodule git rev-parse returned nothing"
+               false
+       elif test "$head" != "$rev1"
+       then
+               echo "[OOPS] init did not checkout correct head"
+               false
+       fi
+'
+
+test_expect_success 'status should be "up-to-date" after update' '
+       git-submodule status | grep "^ $rev1"
+'
+
+test_expect_success 'checkout superproject with subproject already present' '
+       git-checkout initial &&
+       git-checkout master
+'
+
+test_done
diff --git a/t/t7500-commit.sh b/t/t7500-commit.sh
new file mode 100755 (executable)
index 0000000..f11ada8
--- /dev/null
@@ -0,0 +1,96 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Steven Grimm
+#
+
+test_description='git-commit
+
+Tests for selected commit options.'
+
+. ./test-lib.sh
+
+commit_msg_is () {
+       test "`git log --pretty=format:%s%b -1`" = "$1"
+}
+
+# A sanity check to see if commit is working at all.
+test_expect_success 'a basic commit in an empty tree should succeed' '
+       echo content > foo &&
+       git add foo &&
+       git commit -m "initial commit"
+'
+
+test_expect_success 'nonexistent template file should return error' '
+       echo changes >> foo &&
+       git add foo &&
+       ! git commit --template "$PWD"/notexist
+'
+
+test_expect_success 'nonexistent template file in config should return error' '
+       git config commit.template "$PWD"/notexist &&
+       ! git commit &&
+       git config --unset commit.template
+'
+
+# From now on we'll use a template file that exists.
+TEMPLATE="$PWD"/template
+
+test_expect_success 'unedited template should not commit' '
+       echo "template line" > "$TEMPLATE" &&
+       ! git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'unedited template with comments should not commit' '
+       echo "# comment in template" >> "$TEMPLATE" &&
+       ! git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'a Signed-off-by line by itself should not commit' '
+       ! GIT_EDITOR=../t7500/add-signed-off git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'adding comments to a template should not commit' '
+       ! GIT_EDITOR=../t7500/add-comments git commit --template "$TEMPLATE"
+'
+
+test_expect_success 'adding real content to a template should commit' '
+       GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" &&
+       commit_msg_is "template linecommit message"
+'
+
+test_expect_success '-t option should be short for --template' '
+       echo "short template" > "$TEMPLATE" &&
+       echo "new content" >> foo &&
+       git add foo &&
+       GIT_EDITOR=../t7500/add-content git commit -t "$TEMPLATE" &&
+       commit_msg_is "short templatecommit message"
+'
+
+test_expect_success 'config-specified template should commit' '
+       echo "new template" > "$TEMPLATE" &&
+       git config commit.template "$TEMPLATE" &&
+       echo "more content" >> foo &&
+       git add foo &&
+       GIT_EDITOR=../t7500/add-content git commit &&
+       git config --unset commit.template &&
+       commit_msg_is "new templatecommit message"
+'
+
+test_expect_success 'explicit commit message should override template' '
+       echo "still more content" >> foo &&
+       git add foo &&
+       GIT_EDITOR=../t7500/add-content git commit --template "$TEMPLATE" \
+               -m "command line msg" &&
+       commit_msg_is "command line msg<unknown>"
+'
+
+test_expect_success 'commit message from file should override template' '
+       echo "content galore" >> foo &&
+       git add foo &&
+       echo "standard input msg" |
+               GIT_EDITOR=../t7500/add-content git commit \
+                       --template "$TEMPLATE" --file - &&
+       commit_msg_is "standard input msg<unknown>"
+'
+
+test_done
diff --git a/t/t7500/add-comments b/t/t7500/add-comments
new file mode 100755 (executable)
index 0000000..a72e65c
--- /dev/null
@@ -0,0 +1,4 @@
+#!/bin/sh
+echo "# this is a new comment" >> "$1"
+echo "# and so is this" >> "$1"
+exit 0
diff --git a/t/t7500/add-content b/t/t7500/add-content
new file mode 100755 (executable)
index 0000000..2fa3d86
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo "commit message" >> "$1"
+exit 0
diff --git a/t/t7500/add-signed-off b/t/t7500/add-signed-off
new file mode 100755 (executable)
index 0000000..e1d856a
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+echo "Signed-off-by: foo <bar@frotz>" >> "$1"
+exit 0
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
new file mode 100644 (file)
index 0000000..6bd3c9e
--- /dev/null
@@ -0,0 +1,134 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>
+#
+
+# FIXME: Test the various index usages, -i and -o, test reflog,
+# signoff, hooks
+
+test_description='git-commit'
+. ./test-lib.sh
+
+test_tick
+
+test_expect_success \
+       "initial status" \
+       "echo 'bongo bongo' >file &&
+        git-add file && \
+        git-status | grep 'Initial commit'"
+
+test_expect_failure \
+       "fail initial amend" \
+       "git-commit --amend"
+
+test_expect_success \
+       "initial commit" \
+       "git-commit -m initial"
+
+test_expect_failure \
+       "invalid options 1" \
+       "git-commit -m foo -m bar -F file"
+
+test_expect_failure \
+       "invalid options 2" \
+       "git-commit -C HEAD -m illegal"
+
+test_expect_failure \
+       "using invalid commit with -C" \
+       "git-commit -C bogus"
+
+test_expect_failure \
+       "testing nothing to commit" \
+       "git-commit -m initial"
+
+test_expect_success \
+       "next commit" \
+       "echo 'bongo bongo bongo' >file \
+        git-commit -m next -a"
+
+test_expect_failure \
+       "commit message from non-existing file" \
+       "echo 'more bongo: bongo bongo bongo bongo' >file && \
+        git-commit -F gah -a"
+
+# Empty except stray tabs and spaces on a few lines.
+sed -e 's/@$//' >msg <<EOF
+               @
+
+  @
+Signed-off-by: hula
+EOF
+test_expect_failure \
+       "empty commit message" \
+       "git-commit -F msg -a"
+
+test_expect_success \
+       "commit message from file" \
+       "echo 'this is the commit message, coming from a file' >msg && \
+        git-commit -F msg -a"
+
+cat >editor <<\EOF
+#!/bin/sh
+sed -i -e "s/a file/an amend commit/g" $1
+EOF
+chmod 755 editor
+
+test_expect_success \
+       "amend commit" \
+       "VISUAL=./editor git-commit --amend"
+
+test_expect_failure \
+       "passing -m and -F" \
+       "echo 'enough with the bongos' >file && \
+        git-commit -F msg -m amending ."
+
+test_expect_success \
+       "using message from other commit" \
+       "git-commit -C HEAD^ ."
+
+cat >editor <<\EOF
+#!/bin/sh
+sed -i -e "s/amend/older/g" $1
+EOF
+chmod 755 editor
+
+test_expect_success \
+       "editing message from other commit" \
+       "echo 'hula hula' >file && \
+        VISUAL=./editor git-commit -c HEAD^ -a"
+
+test_expect_success \
+       "message from stdin" \
+       "echo 'silly new contents' >file && \
+        echo commit message from stdin | git-commit -F - -a"
+
+test_expect_success \
+       "overriding author from command line" \
+       "echo 'gak' >file && \
+        git-commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
+
+test_expect_success \
+       "interactive add" \
+       "echo 7 | git-commit --interactive | grep 'What now'"
+
+test_expect_success \
+       "showing committed revisions" \
+       "git-rev-list HEAD >current"
+
+# We could just check the head sha1, but checking each commit makes it
+# easier to isolate bugs.
+
+cat >expected <<\EOF
+72c0dc9855b0c9dadcbfd5a31cab072e0cb774ca
+9b88fc14ce6b32e3d9ee021531a54f18a5cf38a2
+3536bbb352c3a1ef9a420f5b4242d48578b92aa7
+d381ac431806e53f3dd7ac2f1ae0534f36d738b9
+4fd44095ad6334f3ef72e4c5ec8ddf108174b54a
+402702b49136e7587daa9280e91e4bb7cb2179f7
+EOF
+
+test_expect_success \
+    'validate git-rev-list output.' \
+    'diff current expected'
+
+test_done
index 3a6490e8f864b3b3193a7b86d54a0e8d81d0dd20..eabec2e06e2f97fc1790cd4ce30a80e402d4a205 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-annotate'
+test_description='git annotate'
 . ./test-lib.sh
 
 PROG='git annotate'
index 97773939962187b4a27176e9bcb5104652ba3853..92ece30fa94784bdad8ae50fc370487e60bbcb5c 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-test_description='git-blame'
+test_description='git blame'
 . ./test-lib.sh
 
 PROG='git blame -c'
index 70c3669ee81f4c6d5dd6ce75e4c7c18e33e93553..614cf50d195bb3b055fd8166d425eeffa7106509 100755 (executable)
@@ -86,10 +86,10 @@ test_expect_failure "$name" "
        rm -f '$GIT_DIR'/index &&
        git checkout -f -b mybranch3 remotes/git-svn &&
        rm bar/zzz &&
-       git-update-index --remove bar/zzz &&
+       git update-index --remove bar/zzz &&
        mkdir bar/zzz &&
        echo yyy > bar/zzz/yyy &&
-       git-update-index --add bar/zzz/yyy &&
+       git update-index --add bar/zzz/yyy &&
        git commit -m '$name' &&
        git-svn set-tree --find-copies-harder --rmdir \
                remotes/git-svn..mybranch3" || true
@@ -191,8 +191,8 @@ GIT_SVN_ID=alt
 export GIT_SVN_ID
 test_expect_success "$name" \
     "git-svn init $svnrepo && git-svn fetch &&
-     git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
-     git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
+     git rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
+     git rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
      git diff a b"
 
 name='check imported tree checksums expected tree checksums'
@@ -214,7 +214,7 @@ EOF
 test_expect_success "$name" "git diff a expected"
 
 test_expect_failure 'exit if remote refs are ambigious' "
-        git-config --add svn-remote.svn.fetch \
+        git config --add svn-remote.svn.fetch \
                               bar:refs/remotes/git-svn &&
         git-svn migrate
         "
@@ -222,7 +222,7 @@ test_expect_failure 'exit if remote refs are ambigious' "
 test_expect_failure 'exit if init-ing a would clobber a URL' "
         svnadmin create ${PWD}/svnrepo2 &&
         svn mkdir -m 'mkdir bar' ${svnrepo}2/bar &&
-        git-config --unset svn-remote.svn.fetch \
+        git config --unset svn-remote.svn.fetch \
                                 '^bar:refs/remotes/git-svn$' &&
         git-svn init ${svnrepo}2/bar
         "
index 35aa45cb9addd862ca01f2f7eb39a863bb508364..d8f9cab35dcff89469f1974dcc7130e3d38f471e 100755 (executable)
@@ -30,31 +30,31 @@ test_expect_success 'initialize repo' "
 test_expect_success 'init and fetch a moved directory' "
        git-svn init --minimize-url -i thunk $svnrepo/thunk &&
        git-svn fetch -i thunk &&
-       test \"\`git-rev-parse --verify refs/remotes/thunk@2\`\" \
-           = \"\`git-rev-parse --verify refs/remotes/thunk~1\`\" &&
-        test \"\`git-cat-file blob refs/remotes/thunk:readme |\
+       test \"\`git rev-parse --verify refs/remotes/thunk@2\`\" \
+           = \"\`git rev-parse --verify refs/remotes/thunk~1\`\" &&
+        test \"\`git cat-file blob refs/remotes/thunk:readme |\
                  sed -n -e '3p'\`\" = goodbye &&
-       test -z \"\`git-config --get svn-remote.svn.fetch \
+       test -z \"\`git config --get svn-remote.svn.fetch \
                 '^trunk:refs/remotes/thunk@2$'\`\"
        "
 
 test_expect_success 'init and fetch from one svn-remote' "
-        git-config svn-remote.svn.url $svnrepo &&
-        git-config --add svn-remote.svn.fetch \
+        git config svn-remote.svn.url $svnrepo &&
+        git config --add svn-remote.svn.fetch \
           trunk:refs/remotes/svn/trunk &&
-        git-config --add svn-remote.svn.fetch \
+        git config --add svn-remote.svn.fetch \
           thunk:refs/remotes/svn/thunk &&
         git-svn fetch -i svn/thunk &&
-       test \"\`git-rev-parse --verify refs/remotes/svn/trunk\`\" \
-           = \"\`git-rev-parse --verify refs/remotes/svn/thunk~1\`\" &&
-        test \"\`git-cat-file blob refs/remotes/svn/thunk:readme |\
+       test \"\`git rev-parse --verify refs/remotes/svn/trunk\`\" \
+           = \"\`git rev-parse --verify refs/remotes/svn/thunk~1\`\" &&
+        test \"\`git cat-file blob refs/remotes/svn/thunk:readme |\
                  sed -n -e '3p'\`\" = goodbye
         "
 
 test_expect_success 'follow deleted parent' "
         svn cp -m 'resurrecting trunk as junk' \
                -r2 $svnrepo/trunk $svnrepo/junk &&
-        git-config --add svn-remote.svn.fetch \
+        git config --add svn-remote.svn.fetch \
           junk:refs/remotes/svn/junk &&
         git-svn fetch -i svn/thunk &&
         git-svn fetch -i svn/junk &&
@@ -71,13 +71,13 @@ test_expect_success 'follow larger parent' "
         git-svn init --minimize-url -i larger \
           $svnrepo/another-larger/trunk/thunk/bump/thud &&
         git-svn fetch -i larger &&
-        git-rev-parse --verify refs/remotes/larger &&
-        git-rev-parse --verify \
+        git rev-parse --verify refs/remotes/larger &&
+        git rev-parse --verify \
            refs/remotes/larger-parent/trunk/thunk/bump/thud &&
-        test \"\`git-merge-base \
+        test \"\`git merge-base \
                  refs/remotes/larger-parent/trunk/thunk/bump/thud \
                  refs/remotes/larger\`\" = \
-             \"\`git-rev-parse refs/remotes/larger\`\"
+             \"\`git rev-parse refs/remotes/larger\`\"
         true
         "
 
index dc2afdaa4561ce037239907297718dba1fa33ce6..67fdf7023f3aed9273eeaf5664cbd62950e32616 100755 (executable)
@@ -19,9 +19,9 @@ test_expect_success 'setup old-looking metadata' "
        mv $GIT_DIR/svn/* $GIT_DIR/ &&
        mv $GIT_DIR/svn/.metadata $GIT_DIR/ &&
        rmdir $GIT_DIR/svn &&
-       git-update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn &&
-       git-update-ref refs/heads/svn-HEAD refs/remotes/git-svn &&
-       git-update-ref -d refs/remotes/git-svn refs/remotes/git-svn
+       git update-ref refs/heads/git-svn-HEAD refs/remotes/git-svn &&
+       git update-ref refs/heads/svn-HEAD refs/remotes/git-svn &&
+       git update-ref -d refs/remotes/git-svn refs/remotes/git-svn
        "
 
 head=`git rev-parse --verify refs/heads/git-svn-HEAD^0`
@@ -33,8 +33,8 @@ test_expect_success 'initialize old-style (v0) git-svn layout' "
        echo $svnrepo > $GIT_DIR/svn/info/url &&
        git-svn migrate &&
        ! test -d $GIT_DIR/git-svn &&
-       git-rev-parse --verify refs/remotes/git-svn^0 &&
-       git-rev-parse --verify refs/remotes/svn^0 &&
+       git rev-parse --verify refs/remotes/git-svn^0 &&
+       git rev-parse --verify refs/remotes/svn^0 &&
        test \`git config --get svn-remote.svn.url\` = '$svnrepo' &&
        test \`git config --get svn-remote.svn.fetch\` = \
              ':refs/remotes/git-svn'
@@ -42,20 +42,20 @@ test_expect_success 'initialize old-style (v0) git-svn layout' "
 
 test_expect_success 'initialize a multi-repository repo' "
        git-svn init $svnrepo -T trunk -t tags -b branches &&
-       git-config --get-all svn-remote.svn.fetch > fetch.out &&
+       git config --get-all svn-remote.svn.fetch > fetch.out &&
        grep '^trunk:refs/remotes/trunk$' fetch.out &&
-       test -n \"\`git-config --get svn-remote.svn.branches \
+       test -n \"\`git config --get svn-remote.svn.branches \
                    '^branches/\*:refs/remotes/\*$'\`\" &&
-       test -n \"\`git-config --get svn-remote.svn.tags \
+       test -n \"\`git config --get svn-remote.svn.tags \
                    '^tags/\*:refs/remotes/tags/\*$'\`\" &&
        git config --unset svn-remote.svn.branches \
                                '^branches/\*:refs/remotes/\*$' &&
        git config --unset svn-remote.svn.tags \
                                '^tags/\*:refs/remotes/tags/\*$' &&
-       git-config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' &&
-       git-config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' &&
+       git config --add svn-remote.svn.fetch 'branches/a:refs/remotes/a' &&
+       git config --add svn-remote.svn.fetch 'branches/b:refs/remotes/b' &&
        for i in tags/0.1 tags/0.2 tags/0.3; do
-               git-config --add svn-remote.svn.fetch \
+               git config --add svn-remote.svn.fetch \
                                 \$i:refs/remotes/\$i || exit 1; done
        "
 
@@ -86,8 +86,8 @@ test_expect_success 'migrate --minimize on old inited layout' "
                echo $svnrepo\$path > $GIT_DIR/svn/\$ref/info/url ) || exit 1;
        done &&
        git-svn migrate --minimize &&
-       test -z \"\`git-config -l |grep -v '^svn-remote\.git-svn\.'\`\" &&
-       git-config --get-all svn-remote.svn.fetch > fetch.out &&
+       test -z \"\`git config -l |grep -v '^svn-remote\.git-svn\.'\`\" &&
+       git config --get-all svn-remote.svn.fetch > fetch.out &&
        grep '^trunk:refs/remotes/trunk$' fetch.out &&
        grep '^branches/a:refs/remotes/a$' fetch.out &&
        grep '^branches/b:refs/remotes/b$' fetch.out &&
@@ -109,4 +109,3 @@ test_expect_success  ".rev_db auto-converted to .rev_db.UUID" "
        "
 
 test_done
-
index 59e17f266374c837f0c1e0bd0a4a40fe22a773a6..6235af4db8b0b744d40156f90a0afed469952441 100755 (executable)
@@ -13,7 +13,7 @@ test_expect_success 'load svm repo' "
        git-svn init --minimize-url -R argh -i dir $svnrepo/mirror/argh &&
        git-svn init --minimize-url -R argh -i e \
          $svnrepo/mirror/argh/a/b/c/d/e &&
-       git-config svn.useSvmProps true &&
+       git config svn.useSvmProps true &&
        git-svn fetch --all
        "
 
@@ -21,31 +21,31 @@ uuid=161ce429-a9dd-4828-af4a-52023f968c89
 
 bar_url=http://mayonaise/svnrepo/bar
 test_expect_success 'verify metadata for /bar' "
-       git-cat-file commit refs/remotes/bar | \
+       git cat-file commit refs/remotes/bar | \
           grep '^git-svn-id: $bar_url@12 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~1 | \
+       git cat-file commit refs/remotes/bar~1 | \
           grep '^git-svn-id: $bar_url@11 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~2 | \
+       git cat-file commit refs/remotes/bar~2 | \
           grep '^git-svn-id: $bar_url@10 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~3 | \
+       git cat-file commit refs/remotes/bar~3 | \
           grep '^git-svn-id: $bar_url@9 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~4 | \
+       git cat-file commit refs/remotes/bar~4 | \
           grep '^git-svn-id: $bar_url@6 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~5 | \
+       git cat-file commit refs/remotes/bar~5 | \
           grep '^git-svn-id: $bar_url@1 $uuid$'
        "
 
 e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
 test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
-       git-cat-file commit refs/remotes/e | \
+       git cat-file commit refs/remotes/e | \
           grep '^git-svn-id: $e_url@1 $uuid$'
        "
 
 dir_url=http://mayonaise/svnrepo/dir
 test_expect_success 'verify metadata for /dir' "
-       git-cat-file commit refs/remotes/dir | \
+       git cat-file commit refs/remotes/dir | \
           grep '^git-svn-id: $dir_url@2 $uuid$' &&
-       git-cat-file commit refs/remotes/dir~1 | \
+       git cat-file commit refs/remotes/dir~1 | \
           grep '^git-svn-id: $dir_url@1 $uuid$'
        "
 
index e52321471a3f49098e61c5a2f9257d354a628d3b..ec7dedd48b0e45278b33e3c5d4f97d8df5f85ddf 100755 (executable)
@@ -12,7 +12,7 @@ test_expect_success 'load svnsync repo' "
        git-svn init --minimize-url -R arr -i bar $svnrepo/bar &&
        git-svn init --minimize-url -R argh -i dir $svnrepo/dir &&
        git-svn init --minimize-url -R argh -i e $svnrepo/dir/a/b/c/d/e &&
-       git-config svn.useSvnsyncProps true &&
+       git config svn.useSvnsyncProps true &&
        git-svn fetch --all
        "
 
@@ -20,31 +20,31 @@ uuid=161ce429-a9dd-4828-af4a-52023f968c89
 
 bar_url=http://mayonaise/svnrepo/bar
 test_expect_success 'verify metadata for /bar' "
-       git-cat-file commit refs/remotes/bar | \
+       git cat-file commit refs/remotes/bar | \
           grep '^git-svn-id: $bar_url@12 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~1 | \
+       git cat-file commit refs/remotes/bar~1 | \
           grep '^git-svn-id: $bar_url@11 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~2 | \
+       git cat-file commit refs/remotes/bar~2 | \
           grep '^git-svn-id: $bar_url@10 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~3 | \
+       git cat-file commit refs/remotes/bar~3 | \
           grep '^git-svn-id: $bar_url@9 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~4 | \
+       git cat-file commit refs/remotes/bar~4 | \
           grep '^git-svn-id: $bar_url@6 $uuid$' &&
-       git-cat-file commit refs/remotes/bar~5 | \
+       git cat-file commit refs/remotes/bar~5 | \
           grep '^git-svn-id: $bar_url@1 $uuid$'
        "
 
 e_url=http://mayonaise/svnrepo/dir/a/b/c/d/e
 test_expect_success 'verify metadata for /dir/a/b/c/d/e' "
-       git-cat-file commit refs/remotes/e | \
+       git cat-file commit refs/remotes/e | \
           grep '^git-svn-id: $e_url@1 $uuid$'
        "
 
 dir_url=http://mayonaise/svnrepo/dir
 test_expect_success 'verify metadata for /dir' "
-       git-cat-file commit refs/remotes/dir | \
+       git cat-file commit refs/remotes/dir | \
           grep '^git-svn-id: $dir_url@2 $uuid$' &&
-       git-cat-file commit refs/remotes/dir~1 | \
+       git cat-file commit refs/remotes/dir~1 | \
           grep '^git-svn-id: $dir_url@1 $uuid$'
        "
 
index a9a46eeb2978071e44db85405b62c515e0b6c278..499fa9594fafe5d45a32005c189634b6a3048777 100644 (file)
@@ -558,5 +558,3 @@ Text-content-md5: 7abb78de7f2756ca8b511cbc879fd5e7
 Content-length: 4
 
 cba
-
-
diff --git a/t/t9113-git-svn-dcommit-new-file.sh b/t/t9113-git-svn-dcommit-new-file.sh
new file mode 100755 (executable)
index 0000000..9ef0db9
--- /dev/null
@@ -0,0 +1,40 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+# Don't run this test by default unless the user really wants it
+# I don't like the idea of taking a port and possibly leaving a
+# daemon running on a users system if the test fails.
+# Not all git users will need to interact with SVN.
+test -z "$SVNSERVE_PORT" && exit 0
+
+test_description='git-svn dcommit new files over svn:// test'
+
+. ./lib-git-svn.sh
+
+start_svnserve () {
+       svnserve --listen-port $SVNSERVE_PORT \
+                --root $rawsvnrepo \
+                --listen-once \
+                --listen-host 127.0.0.1 &
+}
+
+test_expect_success 'start tracking an empty repo' "
+       svn mkdir -m 'empty dir' $svnrepo/empty-dir &&
+       echo anon-access = write >> $rawsvnrepo/conf/svnserve.conf &&
+       start_svnserve &&
+       git svn init svn://127.0.0.1:$SVNSERVE_PORT &&
+       git svn fetch
+       "
+
+test_expect_success 'create files in new directory with dcommit' "
+       mkdir git-new-dir &&
+       echo hello > git-new-dir/world &&
+       git update-index --add git-new-dir/world &&
+       git commit -m hello &&
+       start_svnserve &&
+       git svn dcommit
+       "
+
+test_done
diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh
new file mode 100755 (executable)
index 0000000..d6ca955
--- /dev/null
@@ -0,0 +1,89 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+# Based on a script by Joakim Tjernlund <joakim.tjernlund@transmode.se>
+
+test_description='git-svn dcommit handles merges'
+
+. ./lib-git-svn.sh
+
+big_text_block () {
+cat << EOF
+#
+# (C) Copyright 2000 - 2005
+# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
+#
+# See file CREDITS for list of people who contributed to this
+# project.
+#
+# 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
+#
+EOF
+}
+
+test_expect_success 'setup svn repository' "
+       svn co $svnrepo mysvnwork &&
+       mkdir -p mysvnwork/trunk &&
+       cd mysvnwork &&
+               big_text_block >> trunk/README &&
+               svn add trunk &&
+               svn ci -m 'first commit' trunk &&
+               cd ..
+       "
+
+test_expect_success 'setup git mirror and merge' "
+       git svn init $svnrepo -t tags -T trunk -b branches &&
+       git svn fetch &&
+       git checkout --track -b svn remotes/trunk &&
+       git checkout -b merge &&
+       echo new file > new_file &&
+       git add new_file &&
+       git commit -a -m 'New file' &&
+       echo hello >> README &&
+       git commit -a -m 'hello' &&
+       echo add some stuff >> new_file &&
+       git commit -a -m 'add some stuff' &&
+       git checkout svn &&
+       mv -f README tmp &&
+       echo friend > README &&
+       cat tmp >> README &&
+       git commit -a -m 'friend' &&
+       git pull . merge
+       "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'verify pre-merge ancestry' "
+       test x\`git rev-parse --verify refs/heads/svn^2\` = \
+            x\`git rev-parse --verify refs/heads/merge\` &&
+       git cat-file commit refs/heads/svn^ | grep '^friend$'
+       "
+
+test_expect_success 'git svn dcommit merges' "
+       git svn dcommit
+       "
+
+test_debug 'gitk --all & sleep 1'
+
+test_expect_success 'verify post-merge ancestry' "
+       test x\`git rev-parse --verify refs/heads/svn\` = \
+            x\`git rev-parse --verify refs/remotes/trunk \` &&
+       test x\`git rev-parse --verify refs/heads/svn^2\` = \
+            x\`git rev-parse --verify refs/heads/merge\` &&
+       git cat-file commit refs/heads/svn^ | grep '^friend$'
+       "
+
+test_done
diff --git a/t/t9115-git-svn-dcommit-funky-renames.sh b/t/t9115-git-svn-dcommit-funky-renames.sh
new file mode 100755 (executable)
index 0000000..182299c
--- /dev/null
@@ -0,0 +1,54 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+
+
+test_description='git-svn dcommit can commit renames of files with ugly names'
+
+. ./lib-git-svn.sh
+
+test_expect_success 'load repository with strange names' "
+       svnadmin load -q $rawsvnrepo < ../t9115/funky-names.dump &&
+       start_httpd
+       "
+
+test_expect_success 'init and fetch repository' "
+       git svn init $svnrepo &&
+       git svn fetch &&
+       git reset --hard git-svn
+       "
+
+test_expect_success 'create file in existing ugly and empty dir' '
+       mkdir "#{bad_directory_name}" &&
+       echo hi > "#{bad_directory_name}/ foo" &&
+       git update-index --add "#{bad_directory_name}/ foo" &&
+       git commit -m "new file in ugly parent" &&
+       git svn dcommit
+       '
+
+test_expect_success 'rename ugly file' '
+       git mv "#{bad_directory_name}/ foo" "file name with feces" &&
+       git commit -m "rename ugly file" &&
+       git svn dcommit
+       '
+
+test_expect_success 'rename pretty file' '
+       echo :x > pretty &&
+       git update-index --add pretty &&
+       git commit -m "pretty :x" &&
+       git svn dcommit &&
+       mkdir regular_dir_name &&
+       git mv pretty regular_dir_name/pretty &&
+       git commit -m "moved pretty file" &&
+       git svn dcommit
+       '
+
+test_expect_success 'rename pretty file into ugly one' '
+       git mv regular_dir_name/pretty "#{bad_directory_name}/ booboo" &&
+       git commit -m booboo &&
+       git svn dcommit
+       '
+
+stop_httpd
+
+test_done
diff --git a/t/t9115/funky-names.dump b/t/t9115/funky-names.dump
new file mode 100644 (file)
index 0000000..42422f7
--- /dev/null
@@ -0,0 +1,103 @@
+SVN-fs-dump-format-version: 2
+
+UUID: 819c44fe-2bcc-4066-88e4-985e2bc0b418
+
+Revision-number: 0
+Prop-content-length: 56
+Content-length: 56
+
+K 8
+svn:date
+V 27
+2007-07-12T07:54:26.062914Z
+PROPS-END
+
+Revision-number: 1
+Prop-content-length: 152
+Content-length: 152
+
+K 7
+svn:log
+V 44
+what will those wacky people think of next?
+
+K 10
+svn:author
+V 12
+normalperson
+K 8
+svn:date
+V 27
+2007-07-12T08:00:05.011573Z
+PROPS-END
+
+Node-path:  leading space
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path:  leading space file
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 5
+Text-content-md5: e4fa20c67542cdc21271e08d329397ab
+Content-length: 15
+
+PROPS-END
+ugly
+
+
+Node-path: #{bad_directory_name}
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: #{cool_name}
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 18
+Text-content-md5: 87dac40ca337dfa3dcc8911388c3ddda
+Content-length: 28
+
+PROPS-END
+strange name here
+
+
+Node-path: dir name with spaces
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
+
+
+Node-path: file name with spaces
+Node-kind: file
+Node-action: add
+Prop-content-length: 10
+Text-content-length: 7
+Text-content-md5: c1f10cfd640618484a2a475c11410fd3
+Content-length: 17
+
+PROPS-END
+spaces
+
+
+Node-path: regular_dir_name
+Node-kind: dir
+Node-action: add
+Prop-content-length: 10
+Content-length: 10
+
+PROPS-END
diff --git a/t/t9116-git-svn-log.sh b/t/t9116-git-svn-log.sh
new file mode 100755 (executable)
index 0000000..0d4e6b3
--- /dev/null
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Eric Wong
+#
+
+test_description='git-svn log tests'
+. ./lib-git-svn.sh
+
+test_expect_success 'setup repository and import' "
+       mkdir import &&
+       cd import &&
+               for i in trunk branches/a branches/b \
+                        tags/0.1 tags/0.2 tags/0.3; do
+                       mkdir -p \$i && \
+                       echo hello >> \$i/README || exit 1
+               done && \
+               svn import -m test . $svnrepo
+               cd .. &&
+       git-svn init $svnrepo -T trunk -b branches -t tags &&
+       git-svn fetch &&
+       git reset --hard trunk &&
+       echo bye >> README &&
+       git commit -a -m bye &&
+       git svn dcommit &&
+       git reset --hard a &&
+       echo why >> FEEDME &&
+       git update-index --add FEEDME &&
+       git commit -m feedme &&
+       git svn dcommit &&
+       git reset --hard trunk &&
+       echo aye >> README &&
+       git commit -a -m aye &&
+       git svn dcommit
+       "
+
+test_expect_success 'run log' "
+       git reset --hard a &&
+       git svn log -r2 trunk | grep ^r2 &&
+       git svn log -r4 trunk | grep ^r4 &&
+       git svn log -r3 | grep ^r3
+       "
+
+test_expect_success 'run log against a from trunk' "
+       git reset --hard trunk &&
+       git svn log -r3 a | grep ^r3
+       "
+
+test_done
index 4efa0c926c45befa92be90f746f03807a591a893..910c584f242c6b29fc6d398d2433703765abbcc5 100755 (executable)
@@ -28,6 +28,18 @@ git add empty &&
 git commit -q -a -m "Initial" 2>/dev/null ||
 exit 1
 
+check_entries () {
+       # $1 == directory, $2 == expected
+       grep '^/' "$1/CVS/Entries" | sort | cut -d/ -f2,3,5 >actual
+       if test -z "$2"
+       then
+               >expected
+       else
+               printf '%s\n' "$2" | tr '|' '\012' >expected
+       fi
+       diff -u expected actual
+}
+
 test_expect_success \
     'New file' \
     'mkdir A B C D E F &&
@@ -43,10 +55,10 @@ test_expect_success \
      id=$(git rev-list --max-count=1 HEAD) &&
      (cd "$CVSWORK" &&
      git cvsexportcommit -c $id &&
-     test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.1/" &&
-     test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "newfile2.txt/1.1/" &&
-     test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "newfile3.png/1.1/-kb" &&
-     test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.1/-kb" &&
+     check_entries A "newfile1.txt/1.1/" &&
+     check_entries B "newfile2.txt/1.1/" &&
+     check_entries C "newfile3.png/1.1/-kb" &&
+     check_entries D "newfile4.png/1.1/-kb" &&
      diff A/newfile1.txt ../A/newfile1.txt &&
      diff B/newfile2.txt ../B/newfile2.txt &&
      diff C/newfile3.png ../C/newfile3.png &&
@@ -67,12 +79,12 @@ test_expect_success \
      id=$(git rev-list --max-count=1 HEAD) &&
      (cd "$CVSWORK" &&
      git cvsexportcommit -c $id &&
-     test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" &&
-     test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "newfile4.png/1.2/-kb" &&
-     test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
-     test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+     check_entries A "newfile1.txt/1.2/" &&
+     check_entries B "" &&
+     check_entries C "" &&
+     check_entries D "newfile4.png/1.2/-kb" &&
+     check_entries E "newfile5.txt/1.1/" &&
+     check_entries F "newfile6.png/1.1/-kb" &&
      diff A/newfile1.txt ../A/newfile1.txt &&
      diff D/newfile4.png ../D/newfile4.png &&
      diff E/newfile5.txt ../E/newfile5.txt &&
@@ -115,12 +127,12 @@ test_expect_success \
      id=$(git rev-list --max-count=1 HEAD) &&
      (cd "$CVSWORK" &&
      git cvsexportcommit -c $id &&
-     test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "newfile1.txt/1.2/" &&
-     test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
-     test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+     check_entries A "newfile1.txt/1.2/" &&
+     check_entries B "" &&
+     check_entries C "" &&
+     check_entries D "" &&
+     check_entries E "newfile5.txt/1.1/" &&
+     check_entries F "newfile6.png/1.1/-kb" &&
      diff A/newfile1.txt ../A/newfile1.txt &&
      diff E/newfile5.txt ../E/newfile5.txt &&
      diff F/newfile6.png ../F/newfile6.png
@@ -133,12 +145,12 @@ test_expect_success \
      id=$(git rev-list --max-count=1 HEAD) &&
      (cd "$CVSWORK" &&
      git cvsexportcommit -c $id &&
-     test "$(echo $(sort A/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort B/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort C/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort D/CVS/Entries|cut -d/ -f2,3,5))" = "" &&
-     test "$(echo $(sort E/CVS/Entries|cut -d/ -f2,3,5))" = "newfile5.txt/1.1/" &&
-     test "$(echo $(sort F/CVS/Entries|cut -d/ -f2,3,5))" = "newfile6.png/1.1/-kb" &&
+     check_entries A "" &&
+     check_entries B "" &&
+     check_entries C "" &&
+     check_entries D "" &&
+     check_entries E "newfile5.txt/1.1/" &&
+     check_entries F "newfile6.png/1.1/-kb" &&
      diff E/newfile5.txt ../E/newfile5.txt &&
      diff F/newfile6.png ../F/newfile6.png
      )'
@@ -154,7 +166,7 @@ test_expect_success \
       id=$(git rev-list --max-count=1 HEAD) &&
       (cd "$CVSWORK" &&
       git-cvsexportcommit -c $id &&
-      test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.1/-kb with spaces.txt/1.1/"
+      check_entries "G g" "with spaces.png/1.1/-kb|with spaces.txt/1.1/"
       )'
 
 test_expect_success \
@@ -166,7 +178,7 @@ test_expect_success \
       id=$(git rev-list --max-count=1 HEAD) &&
       (cd "$CVSWORK" &&
       git-cvsexportcommit -c $id
-      test "$(echo $(sort "G g/CVS/Entries"|cut -d/ -f2,3,5))" = "with spaces.png/1.2/-kb with spaces.txt/1.2/"
+      check_entries "G g" "with spaces.png/1.2/-kb|with spaces.txt/1.2/"
       )'
 
 # Some filesystems mangle pathnames with UTF-8 characters --
@@ -191,7 +203,9 @@ test_expect_success \
       id=$(git rev-list --max-count=1 HEAD) &&
       (cd "$CVSWORK" &&
       git-cvsexportcommit -v -c $id &&
-      test "$(echo $(sort Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/CVS/Entries|cut -d/ -f2,3,5))" = "gårdetsågårdet.png/1.1/-kb gårdetsågårdet.txt/1.1/"
+      check_entries \
+      "Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö" \
+      "gårdetsågårdet.png/1.1/-kb|gårdetsågårdet.txt/1.1/"
       )'
 
 fi
index 72e49f5d3bebcf6509536c578cc934879ee1aa55..6f95305bf461c2659392d3ee6a77a5360c833c37 100755 (executable)
@@ -60,10 +60,10 @@ INPUT_END
 test_expect_success \
     'A: create pack from stdin' \
     'git-fast-import --export-marks=marks.out <input &&
-        git-whatchanged master'
+        git whatchanged master'
 test_expect_success \
        'A: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 
 cat >expect <<EOF
 author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
@@ -73,7 +73,7 @@ initial
 EOF
 test_expect_success \
        'A: verify commit' \
-       'git-cat-file commit master | sed 1d >actual &&
+       'git cat-file commit master | sed 1d >actual &&
        git diff expect actual'
 
 cat >expect <<EOF
@@ -83,29 +83,29 @@ cat >expect <<EOF
 EOF
 test_expect_success \
        'A: verify tree' \
-       'git-cat-file -p master^{tree} | sed "s/ [0-9a-f]*      / /" >actual &&
+       'git cat-file -p master^{tree} | sed "s/ [0-9a-f]*      / /" >actual &&
         git diff expect actual'
 
 echo "$file2_data" >expect
 test_expect_success \
        'A: verify file2' \
-       'git-cat-file blob master:file2 >actual && git diff expect actual'
+       'git cat-file blob master:file2 >actual && git diff expect actual'
 
 echo "$file3_data" >expect
 test_expect_success \
        'A: verify file3' \
-       'git-cat-file blob master:file3 >actual && git diff expect actual'
+       'git cat-file blob master:file3 >actual && git diff expect actual'
 
 printf "$file4_data" >expect
 test_expect_success \
        'A: verify file4' \
-       'git-cat-file blob master:file4 >actual && git diff expect actual'
+       'git cat-file blob master:file4 >actual && git diff expect actual'
 
 cat >expect <<EOF
-:2 `git-rev-parse --verify master:file2`
-:3 `git-rev-parse --verify master:file3`
-:4 `git-rev-parse --verify master:file4`
-:5 `git-rev-parse --verify master^0`
+:2 `git rev-parse --verify master:file2`
+:3 `git rev-parse --verify master:file3`
+:4 `git rev-parse --verify master:file4`
+:5 `git rev-parse --verify master^0`
 EOF
 test_expect_success \
        'A: verify marks output' \
@@ -134,19 +134,19 @@ INPUT_END
 test_expect_success \
        'A: verify marks import does not crash' \
        'git-fast-import --import-marks=marks.out <input &&
-        git-whatchanged verify--import-marks'
+        git whatchanged verify--import-marks'
 test_expect_success \
        'A: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 cat >expect <<EOF
 :000000 100755 0000000000000000000000000000000000000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 A     copy-of-file2
 EOF
-git-diff-tree -M -r master verify--import-marks >actual
+git diff-tree -M -r master verify--import-marks >actual
 test_expect_success \
        'A: verify diff' \
        'compare_diff_raw expect actual &&
-        test `git-rev-parse --verify master:file2` \
-           = `git-rev-parse --verify verify--import-marks:copy-of-file2`'
+        test `git rev-parse --verify master:file2` \
+           = `git rev-parse --verify verify--import-marks:copy-of-file2`'
 
 ###
 ### series B
@@ -175,7 +175,7 @@ rm -f .git/objects/pack_* .git/objects/index_*
 ###
 
 newf=`echo hi newf | git-hash-object -w --stdin`
-oldf=`git-rev-parse --verify master:file2`
+oldf=`git rev-parse --verify master:file2`
 test_tick
 cat >input <<INPUT_END
 commit refs/heads/branch
@@ -193,17 +193,17 @@ INPUT_END
 test_expect_success \
     'C: incremental import create pack from stdin' \
     'git-fast-import <input &&
-        git-whatchanged branch'
+        git whatchanged branch'
 test_expect_success \
        'C: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 test_expect_success \
        'C: validate reuse existing blob' \
-       'test $newf = `git-rev-parse --verify branch:file2/newf`
-        test $oldf = `git-rev-parse --verify branch:file2/oldf`'
+       'test $newf = `git rev-parse --verify branch:file2/newf`
+        test $oldf = `git rev-parse --verify branch:file2/oldf`'
 
 cat >expect <<EOF
-parent `git-rev-parse --verify master^0`
+parent `git rev-parse --verify master^0`
 author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 
@@ -211,7 +211,7 @@ second
 EOF
 test_expect_success \
        'C: verify commit' \
-       'git-cat-file commit branch | sed 1d >actual &&
+       'git cat-file commit branch | sed 1d >actual &&
         git diff expect actual'
 
 cat >expect <<EOF
@@ -219,7 +219,7 @@ cat >expect <<EOF
 :100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100  file2   file2/oldf
 :100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D     file3
 EOF
-git-diff-tree -M -r master branch >actual
+git diff-tree -M -r master branch >actual
 test_expect_success \
        'C: validate rename result' \
        'compare_diff_raw expect actual'
@@ -251,16 +251,16 @@ INPUT_END
 test_expect_success \
     'D: inline data in commit' \
     'git-fast-import <input &&
-        git-whatchanged branch'
+        git whatchanged branch'
 test_expect_success \
        'D: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 
 cat >expect <<EOF
 :000000 100755 0000000000000000000000000000000000000000 35a59026a33beac1569b1c7f66f3090ce9c09afc A     newdir/exec.sh
 :000000 100644 0000000000000000000000000000000000000000 046d0371e9220107917db0d0e030628de8a1de9b A     newdir/interesting
 EOF
-git-diff-tree -M -r branch^ branch >actual
+git diff-tree -M -r branch^ branch >actual
 test_expect_success \
        'D: validate new files added' \
        'compare_diff_raw expect actual'
@@ -268,13 +268,13 @@ test_expect_success \
 echo "$file5_data" >expect
 test_expect_success \
        'D: verify file5' \
-       'git-cat-file blob branch:newdir/interesting >actual &&
+       'git cat-file blob branch:newdir/interesting >actual &&
         git diff expect actual'
 
 echo "$file6_data" >expect
 test_expect_success \
        'D: verify file6' \
-       'git-cat-file blob branch:newdir/exec.sh >actual &&
+       'git cat-file blob branch:newdir/exec.sh >actual &&
         git diff expect actual'
 
 ###
@@ -300,7 +300,7 @@ test_expect_success \
     'git-fast-import --date-format=rfc2822 <input'
 test_expect_success \
        'E: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 
 cat >expect <<EOF
 author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500
@@ -310,14 +310,14 @@ RFC 2822 type date
 EOF
 test_expect_success \
        'E: verify commit' \
-       'git-cat-file commit branch | sed 1,2d >actual &&
+       'git cat-file commit branch | sed 1,2d >actual &&
        git diff expect actual'
 
 ###
 ### series F
 ###
 
-old_branch=`git-rev-parse --verify branch^0`
+old_branch=`git rev-parse --verify branch^0`
 test_tick
 cat >input <<INPUT_END
 commit refs/heads/branch
@@ -339,7 +339,7 @@ test_expect_success \
                echo BAD gfi did not fail
                return 1
         else
-               if test $old_branch = `git-rev-parse --verify branch^0`
+               if test $old_branch = `git rev-parse --verify branch^0`
                then
                        : branch unaffected and failure returned
                        return 0
@@ -351,11 +351,11 @@ test_expect_success \
        '
 test_expect_success \
        'F: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 
 cat >expect <<EOF
-tree `git-rev-parse branch~1^{tree}`
-parent `git-rev-parse branch~1`
+tree `git rev-parse branch~1^{tree}`
+parent `git rev-parse branch~1`
 author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 
@@ -363,14 +363,14 @@ losing things already?
 EOF
 test_expect_success \
        'F: verify other commit' \
-       'git-cat-file commit other >actual &&
+       'git cat-file commit other >actual &&
        git diff expect actual'
 
 ###
 ### series G
 ###
 
-old_branch=`git-rev-parse --verify branch^0`
+old_branch=`git rev-parse --verify branch^0`
 test_tick
 cat >input <<INPUT_END
 commit refs/heads/branch
@@ -387,11 +387,11 @@ test_expect_success \
     'git-fast-import --force <input'
 test_expect_success \
        'G: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 test_expect_success \
        'G: branch changed, but logged' \
-       'test $old_branch != `git-rev-parse --verify branch^0` &&
-        test $old_branch = `git-rev-parse --verify branch@{1}`'
+       'test $old_branch != `git rev-parse --verify branch^0` &&
+        test $old_branch = `git rev-parse --verify branch@{1}`'
 
 ###
 ### series H
@@ -421,10 +421,10 @@ INPUT_END
 test_expect_success \
     'H: deletall, add 1' \
     'git-fast-import <input &&
-        git-whatchanged H'
+        git whatchanged H'
 test_expect_success \
        'H: verify pack' \
-       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+       'for p in .git/objects/pack/*.pack;do git verify-pack $p||exit;done'
 
 cat >expect <<EOF
 :100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D     file2/newf
@@ -433,7 +433,7 @@ cat >expect <<EOF
 :100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 R100  newdir/interesting      h/e/l/lo
 :100755 000000 e74b7d465e52746be2b4bae983670711e6e66657 0000000000000000000000000000000000000000 D     newdir/exec.sh
 EOF
-git-diff-tree -M -r H^ H >actual
+git diff-tree -M -r H^ H >actual
 test_expect_success \
        'H: validate old files removed, new files added' \
        'compare_diff_raw expect actual'
@@ -441,7 +441,7 @@ test_expect_success \
 echo "$file5_data" >expect
 test_expect_success \
        'H: verify file' \
-       'git-cat-file blob H:h/e/l/lo >actual &&
+       'git cat-file blob H:h/e/l/lo >actual &&
         git diff expect actual'
 
 ###
@@ -463,7 +463,7 @@ test_expect_success \
     'git-fast-import --export-pack-edges=edges.list <input'
 
 cat >expect <<EOF
-.git/objects/pack/pack-.pack: `git-rev-parse --verify export-boundary`
+.git/objects/pack/pack-.pack: `git rev-parse --verify export-boundary`
 EOF
 test_expect_success \
        'I: verify edge list' \
@@ -497,7 +497,7 @@ test_expect_success \
     'git-fast-import <input'
 test_expect_success \
        'J: branch has 1 commit, empty tree' \
-       'test 1 = `git-rev-list J | wc -l` &&
+       'test 1 = `git rev-list J | wc -l` &&
         test 0 = `git ls-tree J | wc -l`'
 
 ###
@@ -527,8 +527,8 @@ test_expect_success \
     'git-fast-import <input'
 test_expect_success \
     'K: verify K^1 = branch^1' \
-    'test `git-rev-parse --verify branch^1` \
-               = `git-rev-parse --verify K^1`'
+    'test `git rev-parse --verify branch^1` \
+               = `git rev-parse --verify K^1`'
 
 ###
 ### series L
@@ -577,7 +577,158 @@ EXPECT_END
 test_expect_success \
     'L: verify internal tree sorting' \
        'git-fast-import <input &&
-        git-diff --raw L^ L >output &&
+        git diff-tree --abbrev --raw L^ L >output &&
         git diff expect output'
 
+###
+### series M
+###
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/M1
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file rename
+COMMIT
+
+from refs/heads/branch^0
+R file2/newf file2/n.e.w.f
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100  file2/newf      file2/n.e.w.f
+EOF
+test_expect_success \
+       'M: rename file in same subdirectory' \
+       'git-fast-import <input &&
+        git diff-tree -M -r M1^ M1 >actual &&
+        compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/M2
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file rename
+COMMIT
+
+from refs/heads/branch^0
+R file2/newf i/am/new/to/you
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100  file2/newf      i/am/new/to/you
+EOF
+test_expect_success \
+       'M: rename file to new subdirectory' \
+       'git-fast-import <input &&
+        git diff-tree -M -r M2^ M2 >actual &&
+        compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/M3
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file rename
+COMMIT
+
+from refs/heads/M2^0
+R i other/sub
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc R100  i/am/new/to/you other/sub/am/new/to/you
+EOF
+test_expect_success \
+       'M: rename subdirectory to new subdirectory' \
+       'git-fast-import <input &&
+        git diff-tree -M -r M3^ M3 >actual &&
+        compare_diff_raw expect actual'
+
+###
+### series N
+###
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/N1
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+file copy
+COMMIT
+
+from refs/heads/branch^0
+C file2/newf file2/n.e.w.f
+
+INPUT_END
+
+cat >expect <<EOF
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100  file2/newf      file2/n.e.w.f
+EOF
+test_expect_success \
+       'N: copy file in same subdirectory' \
+       'git-fast-import <input &&
+        git diff-tree -C --find-copies-harder -r N1^ N1 >actual &&
+        compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/N2
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+clean directory copy
+COMMIT
+
+from refs/heads/branch^0
+C file2 file3
+
+commit refs/heads/N2
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+modify directory copy
+COMMIT
+
+M 644 inline file3/file5
+data <<EOF
+$file5_data
+EOF
+
+INPUT_END
+
+cat >expect <<EOF
+:100644 100644 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 fcf778cda181eaa1cbc9e9ce3a2e15ee9f9fe791 C100  newdir/interesting      file3/file5
+:100755 100755 f1fb5da718392694d0076d677d6d0e364c79b0bc f1fb5da718392694d0076d677d6d0e364c79b0bc C100  file2/newf      file3/newf
+:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 C100  file2/oldf      file3/oldf
+EOF
+test_expect_success \
+       'N: copy then modify subdirectory' \
+       'git-fast-import <input &&
+        git diff-tree -C --find-copies-harder -r N2^^ N2 >actual &&
+        compare_diff_raw expect actual'
+
+cat >input <<INPUT_END
+commit refs/heads/N3
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+dirty directory copy
+COMMIT
+
+from refs/heads/branch^0
+M 644 inline file2/file5
+data <<EOF
+$file5_data
+EOF
+
+C file2 file3
+D file2/file5
+
+INPUT_END
+
+test_expect_success \
+       'N: copy dirty subdirectory' \
+       'git-fast-import <input &&
+        test `git-rev-parse N2^{tree}` = `git-rev-parse N3^{tree}`'
+
 test_done
index d406a8824a382433cbc8c6f499ef3bc1f00301b2..641303e0a18c1c9958e4dae86d1fcdee644dbcff 100755 (executable)
@@ -38,7 +38,7 @@ echo >empty &&
   git commit -q -m "First Commit" &&
   git clone -q --local --bare "$WORKDIR/.git" "$SERVERDIR" >/dev/null 2>&1 &&
   GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
-  GIT_DIR="$SERVERDIR" git config --bool gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
+  GIT_DIR="$SERVERDIR" git config gitcvs.logfile "$SERVERDIR/gitcvs.log" ||
   exit 1
 
 # note that cvs doesn't accept absolute pathnames
@@ -47,6 +47,217 @@ test_expect_success 'basic checkout' \
   'GIT_CONFIG="$git_config" cvs -Q co -d cvswork master &&
    test "$(echo $(grep -v ^D cvswork/CVS/Entries|cut -d/ -f2,3,5))" = "empty/1.1/"'
 
+#------------------------
+# PSERVER AUTHENTICATION
+#------------------------
+
+cat >request-anonymous  <<EOF
+BEGIN AUTH REQUEST
+$SERVERDIR
+anonymous
+
+END AUTH REQUEST
+EOF
+
+cat >request-git  <<EOF
+BEGIN AUTH REQUEST
+$SERVERDIR
+git
+
+END AUTH REQUEST
+EOF
+
+cat >login-anonymous <<EOF
+BEGIN VERIFICATION REQUEST
+$SERVERDIR
+anonymous
+
+END VERIFICATION REQUEST
+EOF
+
+cat >login-git <<EOF
+BEGIN VERIFICATION REQUEST
+$SERVERDIR
+git
+
+END VERIFICATION REQUEST
+EOF
+
+test_expect_success 'pserver authentication' \
+  'cat request-anonymous | git-cvsserver pserver >log 2>&1 &&
+   tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_success 'pserver authentication failure (non-anonymous user)' \
+  'if cat request-git | git-cvsserver pserver >log 2>&1
+   then
+       false
+   else
+       true
+   fi &&
+   tail -n1 log | grep -q "^I HATE YOU$"'
+
+test_expect_success 'pserver authentication (login)' \
+  'cat login-anonymous | git-cvsserver pserver >log 2>&1 &&
+   tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_success 'pserver authentication failure (login/non-anonymous user)' \
+  'if cat login-git | git-cvsserver pserver >log 2>&1
+   then
+       false
+   else
+       true
+   fi &&
+   tail -n1 log | grep -q "^I HATE YOU$"'
+
+
+# misuse pserver authentication for testing of req_Root
+
+cat >request-relative  <<EOF
+BEGIN AUTH REQUEST
+gitcvs.git
+anonymous
+
+END AUTH REQUEST
+EOF
+
+cat >request-conflict  <<EOF
+BEGIN AUTH REQUEST
+$SERVERDIR
+anonymous
+
+END AUTH REQUEST
+Root $WORKDIR
+EOF
+
+test_expect_success 'req_Root failure (relative pathname)' \
+  'if cat request-relative | git-cvsserver pserver >log 2>&1
+   then
+       echo unexpected success
+       false
+   else
+       true
+   fi &&
+   tail log | grep -q "^error 1 Root must be an absolute pathname$"'
+
+test_expect_success 'req_Root failure (conflicting roots)' \
+  'cat request-conflict | git-cvsserver pserver >log 2>&1 &&
+   tail log | grep -q "^error 1 Conflicting roots specified$"'
+
+test_expect_success 'req_Root (strict paths)' \
+  'cat request-anonymous | git-cvsserver --strict-paths pserver $SERVERDIR >log 2>&1 &&
+   tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (strict-paths)' \
+  'cat request-anonymous | git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1'
+
+test_expect_success 'req_Root (w/o strict-paths)' \
+  'cat request-anonymous | git-cvsserver pserver $WORKDIR/ >log 2>&1 &&
+   tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (w/o strict-paths)' \
+  'cat request-anonymous | git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1'
+
+cat >request-base  <<EOF
+BEGIN AUTH REQUEST
+/gitcvs.git
+anonymous
+
+END AUTH REQUEST
+Root /gitcvs.git
+EOF
+
+test_expect_success 'req_Root (base-path)' \
+  'cat request-base | git-cvsserver --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
+   tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (base-path)' \
+  'cat request-anonymous | git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1'
+
+GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1
+
+test_expect_success 'req_Root (export-all)' \
+  'cat request-anonymous | git-cvsserver --export-all pserver $WORKDIR >log 2>&1 &&
+   tail -n1 log | grep -q "^I LOVE YOU$"'
+
+test_expect_failure 'req_Root failure (export-all w/o whitelist)' \
+  'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 ||
+   false'
+
+test_expect_success 'req_Root (everything together)' \
+  'cat request-base | git-cvsserver --export-all --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&
+   tail -n1 log | grep -q "^I LOVE YOU$"'
+
+GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true || exit 1
+
+#--------------
+# CONFIG TESTS
+#--------------
+
+test_expect_success 'gitcvs.enabled = false' \
+  'GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false &&
+   if GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1
+   then
+     echo unexpected cvs success
+     false
+   else
+     true
+   fi &&
+   cat cvs.log | grep -q "GITCVS emulation disabled" &&
+   test ! -d cvswork2'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.ext.enabled = true' \
+  'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
+   GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false &&
+   GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+   diff -q cvswork cvswork2'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.ext.enabled = false' \
+  'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled false &&
+   GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true &&
+   if GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1
+   then
+     echo unexpected cvs success
+     false
+   else
+     true
+   fi &&
+   cat cvs.log | grep -q "GITCVS emulation disabled" &&
+   test ! -d cvswork2'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.dbname' \
+  'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
+   GIT_DIR="$SERVERDIR" git config gitcvs.dbname %Ggitcvs.%a.%m.sqlite &&
+   GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+   diff -q cvswork cvswork2 &&
+   test -f "$SERVERDIR/gitcvs.ext.master.sqlite" &&
+   cmp "$SERVERDIR/gitcvs.master.sqlite" "$SERVERDIR/gitcvs.ext.master.sqlite"'
+
+rm -fr cvswork2
+test_expect_success 'gitcvs.ext.dbname' \
+  'GIT_DIR="$SERVERDIR" git config --bool gitcvs.ext.enabled true &&
+   GIT_DIR="$SERVERDIR" git config gitcvs.ext.dbname %Ggitcvs1.%a.%m.sqlite &&
+   GIT_DIR="$SERVERDIR" git config gitcvs.dbname %Ggitcvs2.%a.%m.sqlite &&
+   GIT_CONFIG="$git_config" cvs -Q co -d cvswork2 master >cvs.log 2>&1 &&
+   diff -q cvswork cvswork2 &&
+   test -f "$SERVERDIR/gitcvs1.ext.master.sqlite" &&
+   test ! -f "$SERVERDIR/gitcvs2.ext.master.sqlite" &&
+   cmp "$SERVERDIR/gitcvs.master.sqlite" "$SERVERDIR/gitcvs1.ext.master.sqlite"'
+
+
+#------------
+# CVS UPDATE
+#------------
+
+rm -fr "$SERVERDIR"
+cd "$WORKDIR" &&
+git clone -q --local --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
+
 test_expect_success 'cvs update (create new file)' \
   'echo testfile1 >testfile1 &&
    git add testfile1 &&
@@ -123,4 +334,75 @@ test_expect_success 'cvs update (re-add deleted file)' \
    test "$(echo $(grep testfile1 CVS/Entries|cut -d/ -f2,3,5))" = "testfile1/1.4/" &&
    diff -q testfile1 ../testfile1'
 
+cd "$WORKDIR"
+test_expect_success 'cvs update (merge)' \
+  'echo Line 0 >expected &&
+   for i in 1 2 3 4 5 6 7
+   do
+     echo Line $i >>merge
+     echo Line $i >>expected
+   done &&
+   echo Line 8 >>expected &&
+   git add merge &&
+   git commit -q -m "Merge test (pre-merge)" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   test "$(echo $(grep merge CVS/Entries|cut -d/ -f2,3,5))" = "merge/1.1/" &&
+   diff -q merge ../merge &&
+   ( echo Line 0; cat merge ) >merge.tmp &&
+   mv merge.tmp merge &&
+   cd "$WORKDIR" &&
+   echo Line 8 >>merge &&
+   git add merge &&
+   git commit -q -m "Merge test (merge)" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   sleep 1 && touch merge &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   diff -q merge ../expected'
+
+cd "$WORKDIR"
+
+cat >expected.C <<EOF
+<<<<<<< merge.mine
+Line 0
+=======
+LINE 0
+>>>>>>> merge.3
+EOF
+
+for i in 1 2 3 4 5 6 7 8
+do
+  echo Line $i >>expected.C
+done
+
+test_expect_success 'cvs update (conflict merge)' \
+  '( echo LINE 0; cat merge ) >merge.tmp &&
+   mv merge.tmp merge &&
+   git add merge &&
+   git commit -q -m "Merge test (conflict)" &&
+   git push gitcvs.git >/dev/null &&
+   cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update &&
+   diff -q merge ../expected.C'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (-C)' \
+  'cd cvswork &&
+   GIT_CONFIG="$git_config" cvs -Q update -C &&
+   diff -q merge ../merge'
+
+cd "$WORKDIR"
+test_expect_success 'cvs update (merge no-op)' \
+   'echo Line 9 >>merge &&
+    cp merge cvswork/merge &&
+    git add merge &&
+    git commit -q -m "Merge test (no-op)" &&
+    git push gitcvs.git >/dev/null &&
+    cd cvswork &&
+    sleep 1 && touch merge &&
+    GIT_CONFIG="$git_config" cvs -Q update &&
+    diff -q merge ../merge'
+
 test_done
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
new file mode 100755 (executable)
index 0000000..fa32598
--- /dev/null
@@ -0,0 +1,552 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Jakub Narebski
+#
+
+test_description='gitweb as standalone script (basic tests).
+
+This test runs gitweb (git web interface) as CGI script from
+commandline, and checks that it would not write any errors
+or warnings to log.'
+
+gitweb_init () {
+       cat >gitweb_config.perl <<EOF
+#!/usr/bin/perl
+
+# gitweb configuration for tests
+
+our \$version = "current";
+our \$GIT = "git";
+our \$projectroot = "$(pwd)";
+our \$home_link_str = "projects";
+our \$site_name = "[localhost]";
+our \$site_header = "";
+our \$site_footer = "";
+our \$home_text = "indextext.html";
+our @stylesheets = ("file:///$(pwd)/../../gitweb/gitweb.css");
+our \$logo = "file:///$(pwd)/../../gitweb/git-logo.png";
+our \$favicon = "file:///$(pwd)/../../gitweb/git-favicon.png";
+our \$projects_list = "";
+our \$export_ok = "";
+our \$strict_export = "";
+
+CGI::Carp::set_programname("gitweb/gitweb.cgi");
+EOF
+
+       cat >.git/description <<EOF
+$0 test repository
+EOF
+}
+
+gitweb_run () {
+       export GATEWAY_INTERFACE="CGI/1.1"
+       export HTTP_ACCEPT="*/*"
+       export REQUEST_METHOD="GET"
+       export QUERY_STRING=""$1""
+       export PATH_INFO=""$2""
+
+       export GITWEB_CONFIG=$(pwd)/gitweb_config.perl
+
+       # some of git commands write to STDERR on error, but this is not
+       # 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 -- $(pwd)/../../gitweb/gitweb.perl \
+               >/dev/null 2>gitweb.log &&
+       if grep -q -s "^[[]" gitweb.log >/dev/null; then false; else true; fi
+
+       # gitweb.log is left for debugging
+}
+
+. ./test-lib.sh
+
+perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
+    test_expect_success 'skipping gitweb tests, perl version is too old' :
+    test_done
+    exit
+}
+
+gitweb_init
+
+# ----------------------------------------------------------------------
+# no commits (empty, just initialized repository)
+
+test_expect_success \
+       'no commits: projects_list (implicit)' \
+       'gitweb_run'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'no commits: projects_index' \
+       'gitweb_run "a=project_index"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'no commits: .git summary (implicit)' \
+       'gitweb_run "p=.git"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'no commits: .git commit (implicit HEAD)' \
+       'gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'no commits: .git commitdiff (implicit HEAD)' \
+       'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'no commits: .git tree (implicit HEAD)' \
+       'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'no commits: .git heads' \
+       'gitweb_run "p=.git;a=heads"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'no commits: .git tags' \
+       'gitweb_run "p=.git;a=tags"'
+test_debug 'cat gitweb.log'
+
+
+# ----------------------------------------------------------------------
+# initial commit
+
+test_expect_success \
+       'Make initial commit' \
+       'echo "Not an empty file." > file &&
+        git add file &&
+        git commit -a -m "Initial commit." &&
+        git branch b'
+
+test_expect_success \
+       'projects_list (implicit)' \
+       'gitweb_run'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'projects_index' \
+       'gitweb_run "a=project_index"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git summary (implicit)' \
+       'gitweb_run "p=.git"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git commit (implicit HEAD)' \
+       'gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git commitdiff (implicit HEAD, root commit)' \
+       'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git commitdiff_plain (implicit HEAD, root commit)' \
+       'gitweb_run "p=.git;a=commitdiff_plain"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git commit (HEAD)' \
+       'gitweb_run "p=.git;a=commit;h=HEAD"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git tree (implicit HEAD)' \
+       'gitweb_run "p=.git;a=tree"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git blob (file)' \
+       'gitweb_run "p=.git;a=blob;f=file"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git blob_plain (file)' \
+       'gitweb_run "p=.git;a=blob_plain;f=file"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# nonexistent objects
+
+test_expect_success \
+       '.git commit (non-existent)' \
+       'gitweb_run "p=.git;a=commit;h=non-existent"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git commitdiff (non-existent)' \
+       'gitweb_run "p=.git;a=commitdiff;h=non-existent"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git commitdiff (non-existent vs HEAD)' \
+       'gitweb_run "p=.git;a=commitdiff;hp=non-existent;h=HEAD"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git tree (0000000000000000000000000000000000000000)' \
+       'gitweb_run "p=.git;a=tree;h=0000000000000000000000000000000000000000"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git tag (0000000000000000000000000000000000000000)' \
+       'gitweb_run "p=.git;a=tag;h=0000000000000000000000000000000000000000"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git blob (non-existent)' \
+       'gitweb_run "p=.git;a=blob;f=non-existent"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       '.git blob_plain (non-existent)' \
+       'gitweb_run "p=.git;a=blob_plain;f=non-existent"'
+test_debug 'cat gitweb.log'
+
+
+# ----------------------------------------------------------------------
+# commitdiff testing (implicit, one implicit tree-ish)
+
+test_expect_success \
+       'commitdiff(0): root' \
+       'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): file added' \
+       'echo "New file" > new_file &&
+        git add new_file &&
+        git commit -a -m "File added." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): mode change' \
+       'chmod a+x new_file &&
+        git commit -a -m "Mode changed." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): file renamed' \
+       'git mv new_file renamed_file &&
+        git commit -a -m "File renamed." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): file to symlink' \
+       'rm renamed_file &&
+        ln -s file renamed_file &&
+        git commit -a -m "File to symlink." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): file deleted' \
+       'git rm renamed_file &&
+        rm -f renamed_file &&
+        git commit -a -m "File removed." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): file copied / new file' \
+       'cp file file2 &&
+        git add file2 &&
+        git commit -a -m "File copied." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): mode change and modified' \
+       'echo "New line" >> file2 &&
+        chmod a+x file2 &&
+        git commit -a -m "Mode change and modification." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): renamed and modified' \
+       'cat >file2<<EOF &&
+Dominus regit me,
+et nihil mihi deerit.
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+EOF
+        git commit -a -m "File added." &&
+        git mv file2 file3 &&
+        echo "Propter nomen suum." >> file3 &&
+        git commit -a -m "File rename and modification." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): renamed, mode change and modified' \
+       'git mv file3 file2 &&
+        echo "Propter nomen suum." >> file2 &&
+        chmod a+x file2 &&
+        git commit -a -m "File rename, mode change and modification." &&
+        gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# commitdiff testing (taken from t4114-apply-typechange.sh)
+
+test_expect_success 'setup typechange commits' '
+       echo "hello world" > foo &&
+       echo "hi planet" > bar &&
+       git update-index --add foo bar &&
+       git commit -m initial &&
+       git branch initial &&
+       rm -f foo &&
+       ln -s bar foo &&
+       git update-index foo &&
+       git commit -m "foo symlinked to bar" &&
+       git branch foo-symlinked-to-bar &&
+       rm -f foo &&
+       echo "how far is the sun?" > foo &&
+       git update-index foo &&
+       git commit -m "foo back to file" &&
+       git branch foo-back-to-file &&
+       rm -f foo &&
+       git update-index --remove foo &&
+       mkdir foo &&
+       echo "if only I knew" > foo/baz &&
+       git update-index --add foo/baz &&
+       git commit -m "foo becomes a directory" &&
+       git branch "foo-becomes-a-directory" &&
+       echo "hello world" > foo/baz &&
+       git update-index foo/baz &&
+       git commit -m "foo/baz is the original foo" &&
+       git branch foo-baz-renamed-from-foo
+       '
+
+test_expect_success \
+       'commitdiff(2): file renamed from foo to foo/baz' \
+       'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-baz-renamed-from-foo"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(2): file renamed from foo/baz to foo' \
+       'gitweb_run "p=.git;a=commitdiff;hp=foo-baz-renamed-from-foo;h=initial"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(2): directory becomes file' \
+       'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=initial"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(2): file becomes directory' \
+       'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-becomes-a-directory"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(2): file becomes symlink' \
+       'gitweb_run "p=.git;a=commitdiff;hp=initial;h=foo-symlinked-to-bar"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(2): symlink becomes file' \
+       'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-back-to-file"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(2): symlink becomes directory' \
+       'gitweb_run "p=.git;a=commitdiff;hp=foo-symlinked-to-bar;h=foo-becomes-a-directory"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(2): directory becomes symlink' \
+       'gitweb_run "p=.git;a=commitdiff;hp=foo-becomes-a-directory;h=foo-symlinked-to-bar"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# commit, commitdiff: merge, large
+test_expect_success \
+       'Create a merge' \
+       'git checkout b &&
+        echo "Branch" >> b &&
+        git add b &&
+        git commit -a -m "On branch" &&
+        git checkout master &&
+        git pull . b'
+
+test_expect_success \
+       'commit(0): merge commit' \
+       'gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(0): merge commit' \
+       'gitweb_run "p=.git;a=commitdiff"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'Prepare large commit' \
+       'git checkout b &&
+        echo "To be changed" > 01-change &&
+        echo "To be renamed" > 02-pure-rename-from &&
+        echo "To be deleted" > 03-delete &&
+        echo "To be renamed and changed" > 04-rename-from &&
+        echo "To have mode changed" > 05-mode-change &&
+        echo "File to symlink" > 06-file-or-symlink &&
+        echo "To be changed and have mode changed" > 07-change-mode-change     &&
+        git add 0* &&
+        git commit -a -m "Prepare large commit" &&
+        echo "Changed" > 01-change &&
+        git mv 02-pure-rename-from 02-pure-rename-to &&
+        git rm 03-delete && rm -f 03-delete &&
+        echo "A new file" > 03-new &&
+        git add 03-new &&
+        git mv 04-rename-from 04-rename-to &&
+        echo "Changed" >> 04-rename-to &&
+        chmod a+x 05-mode-change &&
+        rm -f 06-file-or-symlink && ln -s 01-change 06-file-or-symlink &&
+        echo "Changed and have mode changed" > 07-change-mode-change   &&
+        chmod a+x 07-change-mode-change &&
+        git commit -a -m "Large commit" &&
+        git checkout master'
+
+test_expect_success \
+       'commit(1): large commit' \
+       'gitweb_run "p=.git;a=commit;h=b"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'commitdiff(1): large commit' \
+       'gitweb_run "p=.git;a=commitdiff;h=b"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# tags testing
+
+test_expect_success \
+       'tags: list of different types of tags' \
+       'git checkout master &&
+        git tag -a -m "Tag commit object" tag-commit HEAD &&
+        git tag -a -m "" tag-commit-nomessage HEAD &&
+        git tag -a -m "Tag tag object" tag-tag tag-commit &&
+        git tag -a -m "Tag tree object" tag-tree HEAD^{tree} &&
+        git tag -a -m "Tag blob object" tag-blob HEAD:file &&
+        git tag lightweight/tag-commit HEAD &&
+        git tag lightweight/tag-tag tag-commit &&
+        git tag lightweight/tag-tree HEAD^{tree} &&
+        git tag lightweight/tag-blob HEAD:file &&
+        gitweb_run "p=.git;a=tags"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'tag: Tag to commit object' \
+       'gitweb_run "p=.git;a=tag;h=tag-commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'tag: on lightweight tag (invalid)' \
+       'gitweb_run "p=.git;a=tag;h=lightweight/tag-commit"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# logs
+
+test_expect_success \
+       'logs: log (implicit HEAD)' \
+       'gitweb_run "p=.git;a=log"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'logs: shortlog (implicit HEAD)' \
+       'gitweb_run "p=.git;a=shortlog"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'logs: history (implicit HEAD, file)' \
+       'gitweb_run "p=.git;a=history;f=file"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# feed generation
+
+test_expect_success \
+       'feeds: OPML' \
+       'gitweb_run "a=opml"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'feed: RSS' \
+       'gitweb_run "p=.git;a=rss"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'feed: Atom' \
+       'gitweb_run "p=.git;a=atom"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# encoding/decoding
+
+test_expect_success \
+       'encode(commit): utf8' \
+       '. ../t3901-utf8.txt &&
+        echo "UTF-8" >> file &&
+        git add file &&
+        git commit -F ../t3900/1-UTF-8.txt &&
+        gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'encode(commit): iso-8859-1' \
+       '. ../t3901-8859-1.txt &&
+        echo "ISO-8859-1" >> file &&
+        git add file &&
+        git config i18n.commitencoding ISO-8859-1 &&
+        git commit -F ../t3900/ISO-8859-1.txt &&
+        git config --unset i18n.commitencoding &&
+        gitweb_run "p=.git;a=commit"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'encode(log): utf-8 and iso-8859-1' \
+       'gitweb_run "p=.git;a=log"'
+test_debug 'cat gitweb.log'
+
+# ----------------------------------------------------------------------
+# extra options
+
+test_expect_success \
+       'opt: log --no-merges' \
+       'gitweb_run "p=.git;a=log;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'opt: atom --no-merges' \
+       'gitweb_run "p=.git;a=log;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'opt: "file" history --no-merges' \
+       'gitweb_run "p=.git;a=history;f=file;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'opt: log --no-such-option (invalid option)' \
+       'gitweb_run "p=.git;a=log;opt=--no-such-option"'
+test_debug 'cat gitweb.log'
+
+test_expect_success \
+       'opt: tree --no-merges (invalid option for action)' \
+       'gitweb_run "p=.git;a=tree;opt=--no-merges"'
+test_debug 'cat gitweb.log'
+
+test_done
index dee3ad76212ea95c75c8287c8d8c0f312125bd06..cc1253ccabf6afe0a3903f9f15177f73e8e33183 100644 (file)
@@ -11,6 +11,7 @@ TZ=UTC
 export LANG LC_ALL PAGER TZ
 EDITOR=:
 VISUAL=:
+unset GIT_EDITOR
 unset AUTHOR_DATE
 unset AUTHOR_EMAIL
 unset AUTHOR_NAME
@@ -26,6 +27,7 @@ GIT_COMMITTER_EMAIL=committer@example.com
 GIT_COMMITTER_NAME='C O Mitter'
 unset GIT_DIFF_OPTS
 unset GIT_DIR
+unset GIT_WORK_TREE
 unset GIT_EXTERNAL_DIFF
 unset GIT_INDEX_FILE
 unset GIT_OBJECT_DIRECTORY
@@ -232,7 +234,7 @@ test_create_repo () {
        mv .git/hooks .git/hooks-disabled
        cd "$owd"
 }
-       
+
 test_done () {
        trap - exit
        case "$test_failure" in
index b8352e731bf4baffbda780a1420f52ca4670b828..6f4dbd362fffdd85570c2223ad7d98015e7970ae 100644 (file)
@@ -6,8 +6,9 @@ endif
 
 INSTALL ?= install
 TAR ?= tar
+RM ?= rm -f
 prefix ?= $(HOME)
-template_dir ?= $(prefix)/share/git-core/templates/
+template_dir ?= $(prefix)/share/git-core/templates
 # DESTDIR=
 
 # Shell quote (do not use $(call) to accommodate ancient setups);
@@ -42,7 +43,7 @@ custom:
        $(QUIET): no custom templates yet
 
 clean:
-       rm -rf blt boilerplates.made
+       $(RM) -r blt boilerplates.made
 
 install: all
        $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(template_dir_SQ)'
index 9b04f2d69c6dc054e8e5403062fb7ef5d2117328..c5cdb9d7eeebab7edf048e77eb92437de24751bf 100644 (file)
@@ -19,4 +19,3 @@ test "" = "$(grep '^Signed-off-by: ' "$1" |
        echo >&2 Duplicate Signed-off-by lines.
        exit 1
 }
-
index 190de2688cd2ef82525a0998e0c5133f8cd45a83..b70c8fd36430fb1e0b02119d7f802c2144fa2a41 100644 (file)
@@ -14,4 +14,3 @@
 
 
 #. /usr/share/doc/git-core/contrib/hooks/post-receive-email
-
index 5f56ce8053a40919cbf02010e29f185b6f9d8784..eeccc934cad8bb74624ed388988fe79c26e6900d 100644 (file)
@@ -12,4 +12,3 @@
 test -x "$GIT_DIR/hooks/pre-commit" &&
        exec "$GIT_DIR/hooks/pre-commit" ${1+"$@"}
 :
-
index 723a9ef210bb84c217026c2cdf9d30661f64447b..18b87309f65be23794b87260e08c547f5deeefce 100644 (file)
@@ -68,4 +68,3 @@ perl -e '
     }
     exit($found_bad);
 '
-
diff --git a/test-absolute-path.c b/test-absolute-path.c
new file mode 100644 (file)
index 0000000..c959ea2
--- /dev/null
@@ -0,0 +1,11 @@
+#include "cache.h"
+
+int main(int argc, char **argv)
+{
+       while (argc > 1) {
+               puts(make_absolute_path(argv[1]));
+               argc--;
+               argv++;
+       }
+       return 0;
+}
index 852498eb49fe0cba5e1cd21661288e8321a4b20e..26bdbdd2bfea5eab99b9f1c38936efe56f1ab45a 100644 (file)
@@ -3,6 +3,7 @@
  */
 #include "cache.h"
 #include "diff.h"
+#include "diffcore.h"
 #include "tree.h"
 
 static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
@@ -290,6 +291,78 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
        return 0;
 }
 
+/*
+ * Does it look like the resulting diff might be due to a rename?
+ *  - single entry
+ *  - not a valid previous file
+ */
+static inline int diff_might_be_rename(void)
+{
+       return diff_queued_diff.nr == 1 &&
+               !DIFF_FILE_VALID(diff_queued_diff.queue[0]->one);
+}
+
+static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
+{
+       struct diff_options diff_opts;
+       struct diff_queue_struct *q = &diff_queued_diff;
+       struct diff_filepair *choice;
+       const char *paths[1];
+       int i;
+
+       /* Remove the file creation entry from the diff queue, and remember it */
+       choice = q->queue[0];
+       q->nr = 0;
+
+       diff_setup(&diff_opts);
+       diff_opts.recursive = 1;
+       diff_opts.detect_rename = DIFF_DETECT_RENAME;
+       diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+       diff_opts.single_follow = opt->paths[0];
+       paths[0] = NULL;
+       diff_tree_setup_paths(paths, &diff_opts);
+       if (diff_setup_done(&diff_opts) < 0)
+               die("unable to set up diff options to follow renames");
+       diff_tree(t1, t2, base, &diff_opts);
+       diffcore_std(&diff_opts);
+
+       /* Go through the new set of filepairing, and see if we find a more interesting one */
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+
+               /*
+                * Found a source? Not only do we use that for the new
+                * diff_queued_diff, we will also use that as the path in
+                * the future!
+                */
+               if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
+                       /* Switch the file-pairs around */
+                       q->queue[i] = choice;
+                       choice = p;
+
+                       /* Update the path we use from now on.. */
+                       opt->paths[0] = xstrdup(p->one->path);
+                       diff_tree_setup_paths(opt->paths, opt);
+                       break;
+               }
+       }
+
+       /*
+        * Then, discard all the non-relevane file pairs...
+        */
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filepair *p = q->queue[i];
+               diff_free_filepair(p);
+       }
+
+       /*
+        * .. and re-instate the one we want (which might be either the
+        * original one, or the rename/copy we found)
+        */
+       q->queue[0] = choice;
+       q->nr = 1;
+}
+
 int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const char *base, struct diff_options *opt)
 {
        void *tree1, *tree2;
@@ -306,6 +379,11 @@ int diff_tree_sha1(const unsigned char *old, const unsigned char *new, const cha
        init_tree_desc(&t1, tree1, size1);
        init_tree_desc(&t2, tree2, size2);
        retval = diff_tree(&t1, &t2, base, opt);
+       if (opt->follow_renames && diff_might_be_rename()) {
+               init_tree_desc(&t1, tree1, size1);
+               init_tree_desc(&t2, tree2, size2);
+               try_to_follow_renames(&t1, &t2, base, opt);
+       }
        free(tree1);
        free(tree2);
        return retval;
index cbb24eb3f663cd0370f528d983a2266a55b91c4f..8d4b67317f21508a105175686c7ec98a090ed9b5 100644 (file)
@@ -206,4 +206,3 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
        free(tree);
        return retval;
 }
-
index ee747aba0bec914bd8f5c7a5285c8395ba5f489b..db0fbdc701f1ef63cdc1a8b7d5c5e72322f91426 100644 (file)
@@ -22,7 +22,7 @@ static inline const unsigned char *tree_entry_extract(struct tree_desc *desc, co
 
 static inline int tree_entry_len(const char *name, const unsigned char *sha1)
 {
-       return (char *)sha1 - (char *)name - 1;
+       return (const char *)sha1 - name - 1;
 }
 
 void update_tree_entry(struct tree_desc *);
diff --git a/tree.c b/tree.c
index e946dac06938b44ce13d7e30dd93e26eda75e5ef..8c0819fa721771f6dc2e727860345376edb3ea60 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "cache-tree.h"
 #include "tree.h"
 #include "blob.h"
 #include "commit.h"
@@ -7,7 +8,7 @@
 
 const char *tree_type = "tree";
 
-static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+static int read_one_entry_opt(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage, int opt)
 {
        int len;
        unsigned int size;
@@ -25,7 +26,23 @@ static int read_one_entry(const unsigned char *sha1, const char *base, int basel
        memcpy(ce->name, base, baselen);
        memcpy(ce->name + baselen, pathname, len+1);
        hashcpy(ce->sha1, sha1);
-       return add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+       return add_cache_entry(ce, opt);
+}
+
+static int read_one_entry(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+{
+       return read_one_entry_opt(sha1, base, baselen, pathname, mode, stage,
+                                 ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
+}
+
+/*
+ * This is used when the caller knows there is no existing entries at
+ * the stage that will conflict with the entry being added.
+ */
+static int read_one_entry_quick(const unsigned char *sha1, const char *base, int baselen, const char *pathname, unsigned mode, int stage)
+{
+       return read_one_entry_opt(sha1, base, baselen, pathname, mode, stage,
+                                 ADD_CACHE_JUST_APPEND);
 }
 
 static int match_tree_entry(const char *base, int baselen, const char *path, unsigned int mode, const char **paths)
@@ -119,9 +136,55 @@ int read_tree_recursive(struct tree *tree,
        return 0;
 }
 
+static int cmp_cache_name_compare(const void *a_, const void *b_)
+{
+       const struct cache_entry *ce1, *ce2;
+
+       ce1 = *((const struct cache_entry **)a_);
+       ce2 = *((const struct cache_entry **)b_);
+       return cache_name_compare(ce1->name, ntohs(ce1->ce_flags),
+                                 ce2->name, ntohs(ce2->ce_flags));
+}
+
 int read_tree(struct tree *tree, int stage, const char **match)
 {
-       return read_tree_recursive(tree, "", 0, stage, match, read_one_entry);
+       read_tree_fn_t fn = NULL;
+       int i, err;
+
+       /*
+        * Currently the only existing callers of this function all
+        * call it with stage=1 and after making sure there is nothing
+        * at that stage; we could always use read_one_entry_quick().
+        *
+        * But when we decide to straighten out git-read-tree not to
+        * use unpack_trees() in some cases, this will probably start
+        * to matter.
+        */
+
+       /*
+        * See if we have cache entry at the stage.  If so,
+        * do it the original slow way, otherwise, append and then
+        * sort at the end.
+        */
+       for (i = 0; !fn && i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce_stage(ce) == stage)
+                       fn = read_one_entry;
+       }
+
+       if (!fn)
+               fn = read_one_entry_quick;
+       err = read_tree_recursive(tree, "", 0, stage, match, fn);
+       if (fn == read_one_entry || err)
+               return err;
+
+       /*
+        * Sort the cache entry -- we need to nuke the cache tree, though.
+        */
+       cache_tree_free(&active_cache_tree);
+       qsort(active_cache, active_nr, sizeof(active_cache[0]),
+             cmp_cache_name_compare);
+       return 0;
 }
 
 struct tree *lookup_tree(const unsigned char *sha1)
@@ -157,7 +220,7 @@ static void track_tree_refs(struct tree *item)
        /* Count how many entries there are.. */
        init_tree_desc(&desc, item->buffer, item->size);
        while (tree_entry(&desc, &entry)) {
-               if (S_ISDIRLNK(entry.mode))
+               if (S_ISGITLINK(entry.mode))
                        continue;
                n_refs++;
        }
@@ -169,7 +232,7 @@ static void track_tree_refs(struct tree *item)
        while (tree_entry(&desc, &entry)) {
                struct object *obj;
 
-               if (S_ISDIRLNK(entry.mode))
+               if (S_ISGITLINK(entry.mode))
                        continue;
                if (S_ISDIR(entry.mode))
                        obj = &lookup_tree(entry.sha1)->object;
index 25c56b374ae01ba890ee243368077c1316d9f0ba..65c66eb0bf34efee6485db3dbf8af11788c394f4 100644 (file)
@@ -14,9 +14,7 @@ static char *create_temp_file(unsigned char *sha1)
                die("unable to read blob object %s", sha1_to_hex(sha1));
 
        strcpy(path, ".merge_file_XXXXXX");
-       fd = mkstemp(path);
-       if (fd < 0)
-               die("unable to create temp-file");
+       fd = xmkstemp(path);
        if (write_in_full(fd, buf, size) != size)
                die("unable to write temp-file");
        close(fd);
index 89dd279f89c7648ec333c59634b3169251eec7ab..ccfeb6e245f32d88170ae51f0367ee15aa950a37 100644 (file)
@@ -5,41 +5,30 @@
 #include "cache-tree.h"
 #include "unpack-trees.h"
 #include "progress.h"
+#include "refs.h"
 
 #define DBRT_DEBUG 1
 
 struct tree_entry_list {
        struct tree_entry_list *next;
-       unsigned directory : 1;
-       unsigned executable : 1;
-       unsigned symlink : 1;
        unsigned int mode;
        const char *name;
        const unsigned char *sha1;
 };
 
-static struct tree_entry_list *create_tree_entry_list(struct tree *tree)
+static struct tree_entry_list *create_tree_entry_list(struct tree_desc *desc)
 {
-       struct tree_desc desc;
        struct name_entry one;
        struct tree_entry_list *ret = NULL;
        struct tree_entry_list **list_p = &ret;
 
-       if (!tree->object.parsed)
-               parse_tree(tree);
-
-       init_tree_desc(&desc, tree->buffer, tree->size);
-
-       while (tree_entry(&desc, &one)) {
+       while (tree_entry(desc, &one)) {
                struct tree_entry_list *entry;
 
                entry = xmalloc(sizeof(struct tree_entry_list));
                entry->name = one.path;
                entry->sha1 = one.sha1;
                entry->mode = one.mode;
-               entry->directory = S_ISDIR(one.mode) != 0;
-               entry->executable = (one.mode & S_IXUSR) != 0;
-               entry->symlink = S_ISLNK(one.mode) != 0;
                entry->next = NULL;
 
                *list_p = entry;
@@ -69,10 +58,17 @@ static int entcmp(const char *name1, int dir1, const char *name2, int dir2)
        return ret;
 }
 
+static inline void remove_entry(int remove)
+{
+       if (remove >= 0)
+               remove_cache_entry_at(remove);
+}
+
 static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                            const char *base, struct unpack_trees_options *o,
                            struct tree_entry_list *df_conflict_list)
 {
+       int remove;
        int baselen = strlen(base);
        int src_size = len + 1;
        int i_stk = i_stk;
@@ -140,9 +136,9 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 #endif
                        if (!first || entcmp(first, firstdir,
                                             posns[i]->name,
-                                            posns[i]->directory) > 0) {
+                                            S_ISDIR(posns[i]->mode)) > 0) {
                                first = posns[i]->name;
-                               firstdir = posns[i]->directory;
+                               firstdir = S_ISDIR(posns[i]->mode);
                        }
                }
                /* No name means we're done */
@@ -156,10 +152,11 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 
                subposns = xcalloc(len, sizeof(struct tree_list_entry *));
 
+               remove = -1;
                if (cache_name && !strcmp(cache_name, first)) {
                        any_files = 1;
                        src[0] = active_cache[o->pos];
-                       remove_cache_entry_at(o->pos);
+                       remove = o->pos;
                }
 
                for (i = 0; i < len; i++) {
@@ -176,11 +173,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                                continue;
                        }
 
-                       if (posns[i]->directory) {
+                       if (S_ISDIR(posns[i]->mode)) {
                                struct tree *tree = lookup_tree(posns[i]->sha1);
+                               struct tree_desc t;
                                any_dirs = 1;
                                parse_tree(tree);
-                               subposns[i] = create_tree_entry_list(tree);
+                               init_tree_desc(&t, tree->buffer, tree->size);
+                               subposns[i] = create_tree_entry_list(&t);
                                posns[i] = posns[i]->next;
                                src[i + o->merge] = o->df_conflict_entry;
                                continue;
@@ -223,13 +222,14 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                                                printf("\n");
                                }
 #endif
-                               ret = o->fn(src, o);
+                               ret = o->fn(src, o, remove);
 
 #if DBRT_DEBUG > 1
                                printf("Added %d entries\n", ret);
 #endif
                                o->pos += ret;
                        } else {
+                               remove_entry(remove);
                                for (i = 0; i < src_size; i++) {
                                        if (src[i]) {
                                                add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK);
@@ -336,12 +336,10 @@ static void check_updates(struct cache_entry **src, int nr,
                stop_progress(&progress);;
 }
 
-int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
+int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options *o)
 {
-       unsigned len = object_list_length(trees);
        struct tree_entry_list **posns;
        int i;
-       struct object_list *posn = trees;
        struct tree_entry_list df_conflict_list;
        static struct cache_entry *dfc;
 
@@ -361,10 +359,9 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
 
        if (len) {
                posns = xmalloc(len * sizeof(struct tree_entry_list *));
-               for (i = 0; i < len; i++) {
-                       posns[i] = create_tree_entry_list((struct tree *) posn->item);
-                       posn = posn->next;
-               }
+               for (i = 0; i < len; i++)
+                       posns[i] = create_tree_entry_list(t+i);
+
                if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
                                     o, &df_conflict_list))
                        return -1;
@@ -412,6 +409,15 @@ static void verify_uptodate(struct cache_entry *ce,
                unsigned changed = ce_match_stat(ce, &st, 1);
                if (!changed)
                        return;
+               /*
+                * NEEDSWORK: the current default policy is to allow
+                * submodule to be out of sync wrt the supermodule
+                * index.  This needs to be tightened later for
+                * submodules that are marked to be automatically
+                * checked out.
+                */
+               if (S_ISGITLINK(ntohl(ce->ce_mode)))
+                       return;
                errno = 0;
        }
        if (errno == ENOENT)
@@ -425,11 +431,24 @@ static void invalidate_ce_path(struct cache_entry *ce)
                cache_tree_invalidate_path(active_cache_tree, ce->name);
 }
 
-static int verify_clean_subdirectory(const char *path, const char *action,
+/*
+ * Check that checking out ce->sha1 in subdir ce->name is not
+ * going to overwrite any working files.
+ *
+ * Currently, git does not checkout subprojects during a superproject
+ * checkout, so it is not going to overwrite anything.
+ */
+static int verify_clean_submodule(struct cache_entry *ce, const char *action,
+                                     struct unpack_trees_options *o)
+{
+       return 0;
+}
+
+static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
                                      struct unpack_trees_options *o)
 {
        /*
-        * we are about to extract "path"; we would not want to lose
+        * we are about to extract "ce->name"; we would not want to lose
         * anything in the existing directory there.
         */
        int namelen;
@@ -437,13 +456,24 @@ static int verify_clean_subdirectory(const char *path, const char *action,
        struct dir_struct d;
        char *pathbuf;
        int cnt = 0;
+       unsigned char sha1[20];
+
+       if (S_ISGITLINK(ntohl(ce->ce_mode)) &&
+           resolve_gitlink_ref(ce->name, "HEAD", sha1) == 0) {
+               /* If we are not going to update the submodule, then
+                * we don't care.
+                */
+               if (!hashcmp(sha1, ce->sha1))
+                       return 0;
+               return verify_clean_submodule(ce, action, o);
+       }
 
        /*
         * First let's make sure we do not have a local modification
         * in that directory.
         */
-       namelen = strlen(path);
-       pos = cache_name_pos(path, namelen);
+       namelen = strlen(ce->name);
+       pos = cache_name_pos(ce->name, namelen);
        if (0 <= pos)
                return cnt; /* we have it as nondirectory */
        pos = -pos - 1;
@@ -451,7 +481,7 @@ static int verify_clean_subdirectory(const char *path, const char *action,
                struct cache_entry *ce = active_cache[i];
                int len = ce_namelen(ce);
                if (len < namelen ||
-                   strncmp(path, ce->name, namelen) ||
+                   strncmp(ce->name, ce->name, namelen) ||
                    ce->name[namelen] != '/')
                        break;
                /*
@@ -469,16 +499,16 @@ static int verify_clean_subdirectory(const char *path, const char *action,
         * present file that is not ignored.
         */
        pathbuf = xmalloc(namelen + 2);
-       memcpy(pathbuf, path, namelen);
+       memcpy(pathbuf, ce->name, namelen);
        strcpy(pathbuf+namelen, "/");
 
        memset(&d, 0, sizeof(d));
        if (o->dir)
                d.exclude_per_dir = o->dir->exclude_per_dir;
-       i = read_directory(&d, path, pathbuf, namelen+1, NULL);
+       i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
        if (i)
                die("Updating '%s' would lose untracked files in it",
-                   path);
+                   ce->name);
        free(pathbuf);
        return cnt;
 }
@@ -487,7 +517,7 @@ static int verify_clean_subdirectory(const char *path, const char *action,
  * We do not want to remove or overwrite a working tree file that
  * is not tracked, unless it is ignored.
  */
-static void verify_absent(const char *path, const char *action,
+static void verify_absent(struct cache_entry *ce, const char *action,
                struct unpack_trees_options *o)
 {
        struct stat st;
@@ -495,15 +525,15 @@ static void verify_absent(const char *path, const char *action,
        if (o->index_only || o->reset || !o->update)
                return;
 
-       if (has_symlink_leading_path(path, NULL))
+       if (has_symlink_leading_path(ce->name, NULL))
                return;
 
-       if (!lstat(path, &st)) {
+       if (!lstat(ce->name, &st)) {
                int cnt;
 
-               if (o->dir && excluded(o->dir, path))
+               if (o->dir && excluded(o->dir, ce->name))
                        /*
-                        * path is explicitly excluded, so it is Ok to
+                        * ce->name is explicitly excluded, so it is Ok to
                         * overwrite it.
                         */
                        return;
@@ -515,7 +545,7 @@ static void verify_absent(const char *path, const char *action,
                         * files that are in "foo/" we would lose
                         * it.
                         */
-                       cnt = verify_clean_subdirectory(path, action, o);
+                       cnt = verify_clean_subdirectory(ce, action, o);
 
                        /*
                         * If this removed entries from the index,
@@ -543,7 +573,7 @@ static void verify_absent(const char *path, const char *action,
                 * delete this path, which is in a subdirectory that
                 * is being replaced with a blob.
                 */
-               cnt = cache_name_pos(path, strlen(path));
+               cnt = cache_name_pos(ce->name, strlen(ce->name));
                if (0 <= cnt) {
                        struct cache_entry *ce = active_cache[cnt];
                        if (!ce_stage(ce) && !ce->ce_mode)
@@ -551,7 +581,7 @@ static void verify_absent(const char *path, const char *action,
                }
 
                die("Untracked working tree file '%s' "
-                   "would be %s by merge.", path, action);
+                   "would be %s by merge.", ce->name, action);
        }
 }
 
@@ -575,7 +605,7 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
                }
        }
        else {
-               verify_absent(merge->name, "overwritten", o);
+               verify_absent(merge, "overwritten", o);
                invalidate_ce_path(merge);
        }
 
@@ -590,7 +620,7 @@ static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
        if (old)
                verify_uptodate(old, o);
        else
-               verify_absent(ce->name, "removed", o);
+               verify_absent(ce, "removed", o);
        ce->ce_mode = 0;
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
        invalidate_ce_path(ce);
@@ -620,7 +650,8 @@ static void show_stage_entry(FILE *o,
 #endif
 
 int threeway_merge(struct cache_entry **stages,
-               struct unpack_trees_options *o)
+               struct unpack_trees_options *o,
+               int remove)
 {
        struct cache_entry *index;
        struct cache_entry *head;
@@ -698,8 +729,10 @@ int threeway_merge(struct cache_entry **stages,
        }
 
        /* #1 */
-       if (!head && !remote && any_anc_missing)
+       if (!head && !remote && any_anc_missing) {
+               remove_entry(remove);
                return 0;
+       }
 
        /* Under the new "aggressive" rule, we resolve mostly trivial
         * cases that we historically had git-merge-one-file resolve.
@@ -707,18 +740,18 @@ int threeway_merge(struct cache_entry **stages,
        if (o->aggressive) {
                int head_deleted = !head && !df_conflict_head;
                int remote_deleted = !remote && !df_conflict_remote;
-               const char *path = NULL;
+               struct cache_entry *ce = NULL;
 
                if (index)
-                       path = index->name;
+                       ce = index;
                else if (head)
-                       path = head->name;
+                       ce = head;
                else if (remote)
-                       path = remote->name;
+                       ce = remote;
                else {
                        for (i = 1; i < o->head_idx; i++) {
                                if (stages[i] && stages[i] != o->df_conflict_entry) {
-                                       path = stages[i]->name;
+                                       ce = stages[i];
                                        break;
                                }
                        }
@@ -731,10 +764,11 @@ int threeway_merge(struct cache_entry **stages,
                if ((head_deleted && remote_deleted) ||
                    (head_deleted && remote && remote_match) ||
                    (remote_deleted && head && head_match)) {
+                       remove_entry(remove);
                        if (index)
                                return deleted_entry(index, index, o);
-                       else if (path && !head_deleted)
-                               verify_absent(path, "removed", o);
+                       else if (ce && !head_deleted)
+                               verify_absent(ce, "removed", o);
                        return 0;
                }
                /*
@@ -753,6 +787,7 @@ int threeway_merge(struct cache_entry **stages,
                verify_uptodate(index, o);
        }
 
+       remove_entry(remove);
        o->nontrivial_merge = 1;
 
        /* #2, #3, #4, #6, #7, #9, #10, #11. */
@@ -788,7 +823,8 @@ int threeway_merge(struct cache_entry **stages,
  *
  */
 int twoway_merge(struct cache_entry **src,
-               struct unpack_trees_options *o)
+               struct unpack_trees_options *o,
+               int remove)
 {
        struct cache_entry *current = src[0];
        struct cache_entry *oldtree = src[1];
@@ -816,6 +852,7 @@ int twoway_merge(struct cache_entry **src,
                }
                else if (oldtree && !newtree && same(current, oldtree)) {
                        /* 10 or 11 */
+                       remove_entry(remove);
                        return deleted_entry(oldtree, current, o);
                }
                else if (oldtree && newtree &&
@@ -825,6 +862,7 @@ int twoway_merge(struct cache_entry **src,
                }
                else {
                        /* all other failures */
+                       remove_entry(remove);
                        if (oldtree)
                                reject_merge(oldtree);
                        if (current)
@@ -836,8 +874,8 @@ int twoway_merge(struct cache_entry **src,
        }
        else if (newtree)
                return merged_entry(newtree, current, o);
-       else
-               return deleted_entry(oldtree, current, o);
+       remove_entry(remove);
+       return deleted_entry(oldtree, current, o);
 }
 
 /*
@@ -847,7 +885,8 @@ int twoway_merge(struct cache_entry **src,
  * stage0 does not have anything there.
  */
 int bind_merge(struct cache_entry **src,
-               struct unpack_trees_options *o)
+               struct unpack_trees_options *o,
+               int remove)
 {
        struct cache_entry *old = src[0];
        struct cache_entry *a = src[1];
@@ -870,7 +909,8 @@ int bind_merge(struct cache_entry **src,
  * - take the stat information from stage0, take the data from stage1
  */
 int oneway_merge(struct cache_entry **src,
-               struct unpack_trees_options *o)
+               struct unpack_trees_options *o,
+               int remove)
 {
        struct cache_entry *old = src[0];
        struct cache_entry *a = src[1];
@@ -879,8 +919,10 @@ int oneway_merge(struct cache_entry **src,
                return error("Cannot do a oneway merge of %d trees",
                             o->merge_size);
 
-       if (!a)
+       if (!a) {
+               remove_entry(remove);
                return deleted_entry(old, old, o);
+       }
        if (old && same(old, a)) {
                if (o->reset) {
                        struct stat st;
index fee7da43822b63e5b1f24444e5c51c43d3ff5760..5517faafad6ca3f327c81ab6e8e5f2701ff0ad14 100644 (file)
@@ -4,7 +4,8 @@
 struct unpack_trees_options;
 
 typedef int (*merge_fn_t)(struct cache_entry **src,
-               struct unpack_trees_options *options);
+               struct unpack_trees_options *options,
+               int remove);
 
 struct unpack_trees_options {
        int reset;
@@ -26,12 +27,12 @@ struct unpack_trees_options {
        struct cache_entry *df_conflict_entry;
 };
 
-extern int unpack_trees(struct object_list *trees,
+extern int unpack_trees(unsigned n, struct tree_desc *t,
                struct unpack_trees_options *options);
 
-int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o);
-int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o);
-int bind_merge(struct cache_entry **src, struct unpack_trees_options *o);
-int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o);
+int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o, int);
+int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o, int);
+int bind_merge(struct cache_entry **src, struct unpack_trees_options *o, int);
+int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o, int);
 
 #endif
index d3a09e78d56642db9b1fb1b1ead34ff60dd8c923..fe96ef15c43fa6e3f8f947f84ddce3c498e82859 100644 (file)
@@ -62,7 +62,7 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
        return safe_write(fd, data, sz);
 }
 
-FILE *pack_pipe = NULL;
+static FILE *pack_pipe = NULL;
 static void show_commit(struct commit *commit)
 {
        if (commit->object.flags & BOUNDARY)
@@ -678,7 +678,7 @@ int main(int argc, char **argv)
                        break;
                }
        }
-       
+
        if (i != argc-1)
                usage(upload_pack_usage);
        dir = argv[i];
diff --git a/var.c b/var.c
index e585e59d315061e2e82ea8a23843c128bcce2555..4127031910c83987c8ef394e3711942e2db8a8f9 100644 (file)
--- a/var.c
+++ b/var.c
@@ -67,8 +67,8 @@ int main(int argc, char **argv)
        val = read_var(argv[1]);
        if (!val)
                usage(var_usage);
-       
+
        printf("%s\n", val);
-       
+
        return 0;
 }
index 5c4bc8515ab9484131de7e065e08657315004f8c..e125e11d3b63e3dab9077d7b414e83e7ff7d16ad 100644 (file)
@@ -1,5 +1,45 @@
 #include "cache.h"
 
+/*
+ * Some cases use stdio, but want to flush after the write
+ * to get error handling (and to get better interactive
+ * behaviour - not buffering excessively).
+ *
+ * Of course, if the flush happened within the write itself,
+ * we've already lost the error code, and cannot report it any
+ * more. So we just ignore that case instead (and hope we get
+ * the right error code on the flush).
+ *
+ * If the file handle is stdout, and stdout is a file, then skip the
+ * flush entirely since it's not needed.
+ */
+void maybe_flush_or_die(FILE *f, const char *desc)
+{
+       static int skip_stdout_flush = -1;
+       struct stat st;
+       char *cp;
+
+       if (f == stdout) {
+               if (skip_stdout_flush < 0) {
+                       cp = getenv("GIT_FLUSH");
+                       if (cp)
+                               skip_stdout_flush = (atoi(cp) == 0);
+                       else if ((fstat(fileno(stdout), &st) == 0) &&
+                                S_ISREG(st.st_mode))
+                               skip_stdout_flush = 1;
+                       else
+                               skip_stdout_flush = 0;
+               }
+               if (skip_stdout_flush && !ferror(f))
+                       return;
+       }
+       if (fflush(f)) {
+               if (errno == EPIPE)
+                       exit(0);
+               die("write failure on %s: %s", desc, strerror(errno));
+       }
+}
+
 int read_in_full(int fd, void *buf, size_t count)
 {
        char *p = buf;
index 4bfe8f15d8a0e275f1ed2d2458cda4d592f7c630..52054201c2a8729e036a5d97337a5f85bcafc782 100644 (file)
@@ -198,7 +198,7 @@ static void wt_read_cache(struct wt_status *s)
        read_cache();
 }
 
-void wt_status_print_initial(struct wt_status *s)
+static void wt_status_print_initial(struct wt_status *s)
 {
        int i;
        char buf[PATH_MAX];
index 963bb89b08e64188ba278587f63f380653b019c6..be866d12d38f6f1328f5fae8c7108176d4ecba70 100644 (file)
@@ -130,4 +130,73 @@ int buffer_is_binary(const char *ptr, unsigned long size)
        return !!memchr(ptr, 0, size);
 }
 
+struct ff_regs {
+       int nr;
+       struct ff_reg {
+               regex_t re;
+               int negate;
+       } *array;
+};
 
+static long ff_regexp(const char *line, long len,
+               char *buffer, long buffer_size, void *priv)
+{
+       char *line_buffer = xstrndup(line, len); /* make NUL terminated */
+       struct ff_regs *regs = priv;
+       regmatch_t pmatch[2];
+       int result = 0, i;
+
+       for (i = 0; i < regs->nr; i++) {
+               struct ff_reg *reg = regs->array + i;
+               if (reg->negate ^ !!regexec(&reg->re,
+                                       line_buffer, 2, pmatch, 0)) {
+                       free(line_buffer);
+                       return -1;
+               }
+       }
+       i = pmatch[1].rm_so >= 0 ? 1 : 0;
+       line += pmatch[i].rm_so;
+       result = pmatch[i].rm_eo - pmatch[i].rm_so;
+       if (result > buffer_size)
+               result = buffer_size;
+       else
+               while (result > 0 && (isspace(line[result - 1]) ||
+                                       line[result - 1] == '\n'))
+                       result--;
+       memcpy(buffer, line, result);
+       free(line_buffer);
+       return result;
+}
+
+void xdiff_set_find_func(xdemitconf_t *xecfg, const char *value)
+{
+       int i;
+       struct ff_regs *regs;
+
+       xecfg->find_func = ff_regexp;
+       regs = xecfg->find_func_priv = xmalloc(sizeof(struct ff_regs));
+       for (i = 0, regs->nr = 1; value[i]; i++)
+               if (value[i] == '\n')
+                       regs->nr++;
+       regs->array = xmalloc(regs->nr * sizeof(struct ff_reg));
+       for (i = 0; i < regs->nr; i++) {
+               struct ff_reg *reg = regs->array + i;
+               const char *ep = strchr(value, '\n'), *expression;
+               char *buffer = NULL;
+
+               reg->negate = (*value == '!');
+               if (reg->negate && i == regs->nr - 1)
+                       die("Last expression must not be negated: %s", value);
+               if (*value == '!')
+                       value++;
+               if (ep)
+                       expression = buffer = xstrndup(value, ep - value);
+               else
+                       expression = value;
+               if (regcomp(&reg->re, expression, 0))
+                       die("Invalid regexp to look for hunk header: %s", expression);
+               if (buffer)
+                       free(buffer);
+               value = ep + 1;
+       }
+}
index 536f4e4d9784e0e5ffdc108a5be4bf758d1578ba..fb742dbb6ba2125b970b9d9510e208ec99954a2d 100644 (file)
@@ -20,4 +20,6 @@ int parse_hunk_header(char *line, int len,
 int read_mmfile(mmfile_t *ptr, const char *filename);
 int buffer_is_binary(const char *ptr, unsigned long size);
 
+extern void xdiff_set_find_func(xdemitconf_t *xecfg, const char *line);
+
 #endif
index e874a7c46a90104ebf586d0fc48964634f356763..c00ddaa6e987407743d2c8877f9ca6e772f89c86 100644 (file)
@@ -73,9 +73,13 @@ typedef struct s_xdemitcb {
        int (*outf)(void *, mmbuffer_t *, int);
 } xdemitcb_t;
 
+typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
+
 typedef struct s_xdemitconf {
        long ctxlen;
        unsigned long flags;
+       find_func_t find_func;
+       void *find_func_priv;
 } xdemitconf_t;
 
 typedef struct s_bdiffparam {
@@ -103,4 +107,3 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
 #endif /* #ifdef __cplusplus */
 
 #endif /* #if !defined(XDIFF_H) */
-
index 9aeebc473b11a3dbfa5b52930b4a8e5a35f22215..5cb7171a8f528881c6171defa5b102e87d7aa522 100644 (file)
@@ -565,4 +565,3 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 
        return 0;
 }
-
index 472aeaecfab85e7276baaceb446d2095eeab01fd..3e099dc445d6130f6a0ce2c6270a3b06d6ee119f 100644 (file)
@@ -57,4 +57,3 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg);
 
 #endif /* #if !defined(XDIFFI_H) */
-
index e291dc7608c4ab9b54a5d049bf3b555704e37fe8..d3d9c845c6420e4881636d779c7029f900a0b067 100644 (file)
@@ -69,7 +69,24 @@ static xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) {
 }
 
 
-static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
+static long def_ff(const char *rec, long len, char *buf, long sz, void *priv)
+{
+       if (len > 0 &&
+                       (isalpha((unsigned char)*rec) || /* identifier? */
+                        *rec == '_' || /* also identifier? */
+                        *rec == '$')) { /* identifiers from VMS and other esoterico */
+               if (len > sz)
+                       len = sz;
+               while (0 < len && isspace((unsigned char)rec[len - 1]))
+                       len--;
+               memcpy(buf, rec, len);
+               return len;
+       }
+       return -1;
+}
+
+static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll,
+               find_func_t ff, void *ff_priv) {
 
        /*
         * Be quite stupid about this for now.  Find a line in the old file
@@ -80,27 +97,17 @@ static void xdl_find_func(xdfile_t *xf, long i, char *buf, long sz, long *ll) {
        const char *rec;
        long len;
 
-       *ll = 0;
        while (i-- > 0) {
                len = xdl_get_rec(xf, i, &rec);
-               if (len > 0 &&
-                   (isalpha((unsigned char)*rec) || /* identifier? */
-                    *rec == '_' ||     /* also identifier? */
-                    *rec == '$')) {    /* mysterious GNU diff's invention */
-                       if (len > sz)
-                               len = sz;
-                       while (0 < len && isspace((unsigned char)rec[len - 1]))
-                               len--;
-                       memcpy(buf, rec, len);
-                       *ll = len;
+               if ((*ll = ff(rec, len, buf, sz, ff_priv)) >= 0)
                        return;
-               }
        }
+       *ll = 0;
 }
 
 
-int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
-                   xdemitconf_t const *xecfg) {
+static int xdl_emit_common(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                           xdemitconf_t const *xecfg) {
        xdfile_t *xdf = &xe->xdf1;
        const char *rchg = xdf->rchg;
        long ix;
@@ -120,6 +127,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
        xdchange_t *xch, *xche;
        char funcbuf[80];
        long funclen = 0;
+       find_func_t ff = xecfg->find_func ?  xecfg->find_func : def_ff;
 
        if (xecfg->flags & XDL_EMIT_COMMON)
                return xdl_emit_common(xe, xscr, ecb, xecfg);
@@ -143,7 +151,8 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
                if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
                        xdl_find_func(&xe->xdf1, s1, funcbuf,
-                                     sizeof(funcbuf), &funclen);
+                                     sizeof(funcbuf), &funclen,
+                                     ff, xecfg->find_func_priv);
                }
                if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
                                      funcbuf, funclen, ecb) < 0)
@@ -194,4 +203,3 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
        return 0;
 }
-
index e629417dd2028be30e661fe9059a9a3af30b482d..440a7390fa4abb0411c336cfba616e3229484e86 100644 (file)
@@ -31,4 +31,3 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
 
 
 #endif /* #if !defined(XEMIT_H) */
-
index 04a9da82c9cb7a2ab24d86efb0e6c6aafdd55f3b..526ccb344d231fb978f53b80deb17ec6c8fed368 100644 (file)
@@ -40,4 +40,3 @@
 
 
 #endif /* #if !defined(XINCLUDE_H) */
-
index e2cd2023b31e794bc0c5ba28f64ae4ce4e128184..8ef232cfad12d706aaafe705bf16c546f3597182 100644 (file)
@@ -51,4 +51,3 @@ do { \
 
 
 #endif /* #if !defined(XMACROS_H) */
-
index 1be7b3195059bebffe83d17301f3ff0fb261799e..e87ab57c652b56b1a684e2a0a56885c1d1b27ef7 100644 (file)
@@ -466,4 +466,3 @@ static int xdl_optimize_ctxs(xdfile_t *xdf1, xdfile_t *xdf2) {
 
        return 0;
 }
-
index 344c569e8b4ebeec2f1e6130184a424ae1341bf6..8fb06a537451cbf3335ab4bdacb0f992e9744338 100644 (file)
@@ -32,4 +32,3 @@ void xdl_free_env(xdfenv_t *xe);
 
 
 #endif /* #if !defined(XPREPARE_H) */
-
index 3593a664fc2e58a6cdd2a5f3bdee46dd5ffdaa8e..2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 100644 (file)
@@ -65,4 +65,3 @@ typedef struct s_xdfenv {
 
 
 #endif /* #if !defined(XTYPES_H) */
-
index bf91c0f73c658637d078ac93226148faa3bb0dc4..2ade97b2574a9f77e7ae4002a4e07a6a38e46d07 100644 (file)
@@ -380,4 +380,3 @@ int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
 
        return 0;
 }
-
index 70d8b9838a7333bc5a1edb93cf0e9abdbcf146cc..d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 100644 (file)
@@ -45,4 +45,3 @@ int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
 
 
 #endif /* #if !defined(XUTILS_H) */
-