]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jc/diff-apply-patch'
authorJunio C Hamano <junkio@cox.net>
Wed, 14 Feb 2007 03:18:16 +0000 (19:18 -0800)
committerJunio C Hamano <junkio@cox.net>
Wed, 14 Feb 2007 03:18:16 +0000 (19:18 -0800)
* jc/diff-apply-patch:
  git-diff/git-apply: make diff output a bit friendlier to GNU patch (part 2)

446 files changed:
.gitignore
.mailmap [new file with mode: 0644]
Documentation/.gitignore
Documentation/Makefile
Documentation/RelNotes-1.5.0.txt [new file with mode: 0644]
Documentation/SubmittingPatches
Documentation/callouts.xsl
Documentation/cmd-list.perl [new file with mode: 0755]
Documentation/config.txt
Documentation/core-intro.txt [new file with mode: 0644]
Documentation/core-tutorial.txt
Documentation/cvs-migration.txt
Documentation/diff-format.txt
Documentation/diff-options.txt
Documentation/diffcore.txt
Documentation/docbook-xsl.css [new file with mode: 0644]
Documentation/everyday.txt
Documentation/fetch-options.txt
Documentation/git-add.txt
Documentation/git-am.txt
Documentation/git-apply.txt
Documentation/git-applymbox.txt
Documentation/git-applypatch.txt
Documentation/git-archive.txt
Documentation/git-bisect.txt
Documentation/git-blame.txt
Documentation/git-branch.txt
Documentation/git-cat-file.txt
Documentation/git-checkout-index.txt
Documentation/git-checkout.txt
Documentation/git-cherry-pick.txt
Documentation/git-clone.txt
Documentation/git-commit-tree.txt
Documentation/git-commit.txt
Documentation/git-config.txt [new file with mode: 0644]
Documentation/git-count-objects.txt
Documentation/git-cvsexportcommit.txt
Documentation/git-cvsimport.txt
Documentation/git-daemon.txt
Documentation/git-describe.txt
Documentation/git-diff-stages.txt [deleted file]
Documentation/git-diff-tree.txt
Documentation/git-diff.txt
Documentation/git-fast-import.txt [new file with mode: 0644]
Documentation/git-fetch-pack.txt
Documentation/git-fetch.txt
Documentation/git-for-each-ref.txt
Documentation/git-format-patch.txt
Documentation/git-fsck-objects.txt
Documentation/git-fsck.txt [new file with mode: 0644]
Documentation/git-gc.txt [new file with mode: 0644]
Documentation/git-grep.txt
Documentation/git-hash-object.txt
Documentation/git-http-fetch.txt
Documentation/git-http-push.txt
Documentation/git-init-db.txt
Documentation/git-init.txt [new file with mode: 0644]
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-file.txt [new file with mode: 0644]
Documentation/git-merge-index.txt
Documentation/git-merge-one-file.txt
Documentation/git-merge.txt
Documentation/git-mv.txt
Documentation/git-p4import.txt
Documentation/git-pack-redundant.txt
Documentation/git-pack-refs.txt
Documentation/git-parse-remote.txt
Documentation/git-patch-id.txt
Documentation/git-peek-remote.txt
Documentation/git-prune-packed.txt
Documentation/git-prune.txt
Documentation/git-pull.txt
Documentation/git-push.txt
Documentation/git-read-tree.txt
Documentation/git-rebase.txt
Documentation/git-receive-pack.txt
Documentation/git-reflog.txt [new file with mode: 0644]
Documentation/git-remote.txt [new file with mode: 0644]
Documentation/git-repack.txt
Documentation/git-repo-config.txt
Documentation/git-rerere.txt
Documentation/git-reset.txt
Documentation/git-resolve.txt [deleted file]
Documentation/git-rev-list.txt
Documentation/git-rev-parse.txt
Documentation/git-revert.txt
Documentation/git-rm.txt
Documentation/git-send-pack.txt
Documentation/git-sh-setup.txt
Documentation/git-shell.txt
Documentation/git-shortlog.txt
Documentation/git-show-branch.txt
Documentation/git-show.txt
Documentation/git-ssh-fetch.txt
Documentation/git-ssh-upload.txt
Documentation/git-status.txt
Documentation/git-svn.txt
Documentation/git-svnimport.txt
Documentation/git-symbolic-ref.txt
Documentation/git-tag.txt
Documentation/git-tar-tree.txt
Documentation/git-tools.txt
Documentation/git-update-index.txt
Documentation/git-update-ref.txt
Documentation/git-upload-archive.txt
Documentation/git-upload-pack.txt
Documentation/git-var.txt
Documentation/git-whatchanged.txt
Documentation/git-write-tree.txt
Documentation/git.txt
Documentation/gitk.txt
Documentation/glossary.txt
Documentation/hooks.txt
Documentation/howto/dangling-objects.txt [new file with mode: 0644]
Documentation/howto/rebase-from-internal-branch.txt
Documentation/howto/revert-branch-rebase.txt
Documentation/howto/setup-git-server-over-http.txt
Documentation/i18n.txt [new file with mode: 0644]
Documentation/install-doc-quick.sh [new file with mode: 0755]
Documentation/install-webdoc.sh
Documentation/pretty-formats.txt [new file with mode: 0644]
Documentation/pull-fetch-param.txt
Documentation/repository-layout.txt
Documentation/technical/racy-git.txt
Documentation/technical/send-pack-pipeline.txt [new file with mode: 0644]
Documentation/tutorial-2.txt
Documentation/tutorial.txt
Documentation/urls.txt
Documentation/user-manual.conf [new file with mode: 0644]
Documentation/user-manual.txt [new file with mode: 0644]
GIT-VERSION-GEN
INSTALL
Makefile
README
RelNotes [new symlink]
archive-tar.c
archive-zip.c
blob.c
builtin-add.c
builtin-annotate.c
builtin-apply.c
builtin-archive.c
builtin-blame.c
builtin-branch.c
builtin-commit-tree.c
builtin-config.c [moved from builtin-repo-config.c with 85% similarity]
builtin-count-objects.c
builtin-describe.c [new file with mode: 0644]
builtin-diff-stages.c [deleted file]
builtin-for-each-ref.c
builtin-fsck.c [moved from fsck-objects.c with 79% similarity]
builtin-grep.c
builtin-init-db.c
builtin-log.c
builtin-ls-files.c
builtin-mailinfo.c
builtin-mailsplit.c
builtin-merge-base.c [moved from merge-base.c with 79% similarity]
builtin-merge-file.c [new file with mode: 0644]
builtin-mv.c
builtin-name-rev.c
builtin-pack-objects.c
builtin-pack-refs.c
builtin-prune-packed.c
builtin-prune.c
builtin-push.c
builtin-read-tree.c
builtin-reflog.c [new file with mode: 0644]
builtin-rerere.c [new file with mode: 0644]
builtin-rev-list.c
builtin-rev-parse.c
builtin-rm.c
builtin-runstatus.c
builtin-shortlog.c [new file with mode: 0644]
builtin-show-branch.c
builtin-show-ref.c
builtin-stripspace.c
builtin-symbolic-ref.c
builtin-tar-tree.c
builtin-unpack-objects.c
builtin-update-index.c
builtin-update-ref.c
builtin-upload-archive.c
builtin-verify-pack.c
builtin.h
cache.h
color.c
combine-diff.c
commit.c
commit.h
compat/inet_ntop.c
compat/mmap.c
compat/pread.c [new file with mode: 0644]
compat/setenv.c
compat/strlcpy.c
compat/subprocess.py [deleted file]
compat/unsetenv.c
config.c
config.mak.in
configure.ac
connect.c
contrib/blameview/README [new file with mode: 0644]
contrib/blameview/blameview.perl [new file with mode: 0755]
contrib/colordiff/README [deleted file]
contrib/colordiff/colordiff.perl [deleted file]
contrib/completion/git-completion.bash
contrib/emacs/git-blame.el [new file with mode: 0644]
contrib/emacs/git.el
contrib/emacs/vc-git.el
contrib/examples/git-resolve.sh [moved from git-resolve.sh with 100% similarity]
contrib/fast-import/import-tars.perl [new file with mode: 0755]
contrib/gitview/gitview
contrib/hg-to-git/hg-to-git.py [new file with mode: 0755]
contrib/hg-to-git/hg-to-git.txt [new file with mode: 0644]
contrib/remotes2config.sh
contrib/vim/syntax/gitcommit.vim
convert-objects.c
daemon.c
date.c
describe.c [deleted file]
diff-delta.c
diff-lib.c
diff.c
diff.h
diffcore-order.c
diffcore-pickaxe.c
diffcore-rename.c
diffcore.h
dir.c
dir.h
entry.c
environment.c
exec_cmd.c
fast-import.c [new file with mode: 0644]
fetch-pack.c
fetch.c
generate-cmdlist.sh
git-add--interactive.perl [new file with mode: 0755]
git-am.sh
git-applymbox.sh
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-gc.sh [new file with mode: 0755]
git-gui/.gitignore [new file with mode: 0644]
git-gui/GIT-VERSION-GEN [new file with mode: 0755]
git-gui/Makefile [new file with mode: 0644]
git-gui/TODO [new file with mode: 0644]
git-gui/git-gui.sh [new file with mode: 0755]
git-instaweb.sh
git-lost-found.sh
git-ls-remote.sh
git-merge-one-file.sh
git-merge-recursive-old.py [deleted file]
git-merge-resolve.sh
git-merge.sh
git-p4import.py
git-parse-remote.sh
git-pull.sh
git-quiltimport.sh
git-rebase.sh
git-remote.perl [new file with mode: 0755]
git-repack.sh
git-request-pull.sh
git-rerere.perl [deleted file]
git-reset.sh
git-revert.sh
git-send-email.perl
git-sh-setup.sh
git-shortlog.perl [deleted file]
git-svn.perl
git-svnimport.perl
git-tag.sh
git-verify-tag.sh
git.c
git.spec.in
gitMergeCommon.py [deleted file]
gitk
gitweb/gitweb.css
gitweb/gitweb.perl
grep.c
help.c
http-fetch.c
http-push.c
http.h
ident.c
imap-send.c
index-pack.c
interpolate.c
local-fetch.c
lockfile.c
log-tree.c
merge-file.c
merge-index.c
merge-recursive.c
pack-check.c
pack.h
pager.c
patch-delta.c
path-list.c
path.c
peek-remote.c
perl/.gitignore
perl/Git.pm
perl/Makefile [new file with mode: 0644]
perl/Makefile.PL
perl/private-Error.pm
pkt-line.c
ppc/sha1ppc.S
quote.c
quote.h
reachable.c [new file with mode: 0644]
reachable.h [new file with mode: 0644]
read-cache.c
receive-pack.c
reflog-walk.c [new file with mode: 0644]
reflog-walk.h [new file with mode: 0644]
refs.c
refs.h
revision.c
revision.h
rsh.c
run-command.c
run-command.h
send-pack.c
server-info.c
setup.c
sha1_file.c
sha1_name.c
shallow.c [new file with mode: 0644]
ssh-fetch.c
ssh-upload.c
strbuf.c
t/Makefile
t/README
t/annotate-tests.sh
t/lib-git-svn.sh
t/t0000-basic.sh
t/t1004-read-tree-m-u-wf.sh
t/t1020-subdirectory.sh
t/t1200-tutorial.sh
t/t1300-repo-config.sh
t/t1400-update-ref.sh
t/t1410-reflog.sh [new file with mode: 0755]
t/t3200-branch.sh
t/t3210-pack-refs.sh
t/t3501-revert-cherry-pick.sh [new file with mode: 0755]
t/t3600-rm.sh
t/t3700-add.sh
t/t3800-mktag.sh
t/t3900-i18n-commit.sh [new file with mode: 0755]
t/t3900/1-UTF-8.txt [new file with mode: 0644]
t/t3900/2-UTF-8.txt [new file with mode: 0644]
t/t3900/EUCJP.txt [new file with mode: 0644]
t/t3900/ISO-2022-JP.txt [new file with mode: 0644]
t/t3900/ISO-8859-1.txt [new file with mode: 0644]
t/t3901-8859-1.txt [new file with mode: 0755]
t/t3901-i18n-patch.sh [new file with mode: 0755]
t/t3901-utf8.txt [new file with mode: 0755]
t/t4000-diff-format.sh
t/t4006-diff-mode.sh
t/t4013-diff-various.sh
t/t4013/diff.format-patch_--attach_--stdout_initial..master
t/t4013/diff.format-patch_--attach_--stdout_initial..master^
t/t4013/diff.format-patch_--attach_--stdout_initial..side
t/t4013/diff.format-patch_--stdout_initial..master
t/t4013/diff.format-patch_--stdout_initial..master^
t/t4013/diff.format-patch_--stdout_initial..side
t/t4015-diff-whitespace.sh
t/t4016-diff-quote.sh [new file with mode: 0755]
t/t4102-apply-rename.sh
t/t4116-apply-reverse.sh
t/t4200-rerere.sh [new file with mode: 0755]
t/t5000-tar-tree.sh
t/t5300-pack-object.sh
t/t5301-sliding-window.sh [new file with mode: 0755]
t/t5400-send-pack.sh
t/t5401-update-hooks.sh [new file with mode: 0755]
t/t5500-fetch-pack.sh
t/t5510-fetch.sh
t/t5520-pull.sh
t/t5600-clone-fail-cleanup.sh
t/t5700-clone-reference.sh
t/t5710-info-alternate.sh
t/t6005-rev-list-count.sh [new file with mode: 0755]
t/t6023-merge-file.sh [new file with mode: 0644]
t/t6023-merge-rename-nocruft.sh [new file with mode: 0755]
t/t6024-recursive-merge.sh [new file with mode: 0644]
t/t6120-describe.sh [new file with mode: 0755]
t/t6200-fmt-merge-msg.sh
t/t7001-mv.sh
t/t7201-co.sh
t/t9100-git-svn-basic.sh
t/t9101-git-svn-props.sh
t/t9102-git-svn-deep-rmdir.sh
t/t9103-git-svn-graft-branches.sh
t/t9104-git-svn-follow-parent.sh
t/t9105-git-svn-commit-diff.sh
t/t9106-git-svn-commit-diff-clobber.sh
t/t9200-git-cvsexportcommit.sh
t/t9300-fast-import.sh [new file with mode: 0755]
t/test-lib.sh
templates/Makefile
templates/hooks--commit-msg
templates/hooks--update
templates/remotes-- [deleted file]
test-date.c
test-delta.c
trace.c
tree-walk.c
tree.c
unpack-file.c
unpack-trees.c
unpack-trees.h
upload-pack.c
usage.c
utf8.c [new file with mode: 0644]
utf8.h [new file with mode: 0644]
var.c
write_or_die.c
wt-status.c
wt-status.h
xdiff-interface.c
xdiff-interface.h
xdiff/xdiff.h
xdiff/xdiffi.c
xdiff/xdiffi.h
xdiff/xemit.c
xdiff/xmerge.c [new file with mode: 0644]
xdiff/xutils.c

index 4c8c8e411500fe2ed0108c46838d50576ed15185..f15155d1b7bcb45db6a8d67eab51b535102353c1 100644 (file)
@@ -2,6 +2,7 @@ GIT-CFLAGS
 GIT-VERSION-FILE
 git
 git-add
+git-add--interactive
 git-am
 git-annotate
 git-apply
@@ -10,6 +11,7 @@ git-applypatch
 git-archimport
 git-archive
 git-bisect
+git-blame
 git-branch
 git-cat-file
 git-check-ref-format
@@ -21,6 +23,7 @@ git-clean
 git-clone
 git-commit
 git-commit-tree
+git-config
 git-convert-objects
 git-count-objects
 git-cvsexportcommit
@@ -30,16 +33,18 @@ git-daemon
 git-diff
 git-diff-files
 git-diff-index
-git-diff-stages
 git-diff-tree
 git-describe
+git-fast-import
 git-fetch
 git-fetch-pack
 git-findtags
 git-fmt-merge-msg
 git-for-each-ref
 git-format-patch
+git-fsck
 git-fsck-objects
+git-gc
 git-get-tar-commit-id
 git-grep
 git-hash-object
@@ -47,6 +52,7 @@ git-http-fetch
 git-http-push
 git-imap-send
 git-index-pack
+git-init
 git-init-db
 git-instaweb
 git-local-fetch
@@ -60,13 +66,12 @@ git-mailsplit
 git-merge
 git-merge-base
 git-merge-index
+git-merge-file
 git-merge-tree
 git-merge-octopus
 git-merge-one-file
 git-merge-ours
-git-merge-recur
 git-merge-recursive
-git-merge-recursive-old
 git-merge-resolve
 git-merge-stupid
 git-mktag
@@ -87,13 +92,14 @@ git-quiltimport
 git-read-tree
 git-rebase
 git-receive-pack
+git-reflog
 git-relink
+git-remote
 git-repack
 git-repo-config
 git-request-pull
 git-rerere
 git-reset
-git-resolve
 git-rev-list
 git-rev-parse
 git-revert
@@ -152,4 +158,3 @@ config.status
 config.mak.autogen
 config.mak.append
 configure
-git-blame
diff --git a/.mailmap b/.mailmap
new file mode 100644 (file)
index 0000000..c7a3a75
--- /dev/null
+++ b/.mailmap
@@ -0,0 +1,39 @@
+#
+# This list is used by git-shortlog to fix a few botched name translations
+# in the git archive, either because the author's full name was messed up
+# and/or not always written the same way, making contributions from the
+# same person appearing not to be so.
+#
+
+Aneesh Kumar K.V <aneesh.kumar@gmail.com>
+Chris Shoemaker <c.shoemaker@cox.net>
+Daniel Barkalow <barkalow@iabervon.org>
+David Kågedal <davidk@lysator.liu.se>
+Fredrik Kuivinen <freku045@student.liu.se>
+H. Peter Anvin <hpa@bonde.sc.orionmulti.com>
+H. Peter Anvin <hpa@tazenda.sc.orionmulti.com>
+H. Peter Anvin <hpa@trantor.hos.anvin.org>
+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>
+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>
+Lukas Sandström <lukass@etek.chalmers.se>
+Martin Langhoff <martin@catalyst.net.nz>
+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>
+Robert Fitzsimons <robfitz@273k.net>
+Santi Béjar <sbejar@gmail.com>
+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>
+Ville Skyttä <scop@xemacs.org>
+YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
+anonymous <linux@horizon.com>
+anonymous <linux@horizon.net>
index c87c61af00c6d2cd7212240e26089e24b90bbe05..6a51331b2fbf991d16dae9d4e486463dfa567311 100644 (file)
@@ -4,4 +4,4 @@
 *.7
 howto-index.txt
 doc.dep
-README
+cmds-*.txt
index c00f5f62b741150e92f38e6394bfa6f111b82b6e..9e7f2a78800e3f06b78f644b7cd6a4b632900a8a 100644 (file)
@@ -17,7 +17,7 @@ ARTICLES += hooks
 ARTICLES += everyday
 ARTICLES += git-tools
 # with their own formatting rules.
-SP_ARTICLES = glossary howto/revert-branch-rebase
+SP_ARTICLES = glossary howto/revert-branch-rebase user-manual
 
 DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
 
@@ -31,7 +31,10 @@ man1dir=$(mandir)/man1
 man7dir=$(mandir)/man7
 # DESTDIR=
 
+ASCIIDOC=asciidoc
+ASCIIDOC_EXTRA =
 INSTALL?=install
+DOC_REF = origin/man
 
 -include ../config.mak.autogen
 
@@ -56,8 +59,8 @@ man7: $(DOC_MAN7)
 
 install: man
        $(INSTALL) -d -m755 $(DESTDIR)$(man1dir) $(DESTDIR)$(man7dir)
-       $(INSTALL) $(DOC_MAN1) $(DESTDIR)$(man1dir)
-       $(INSTALL) $(DOC_MAN7) $(DESTDIR)$(man7dir)
+       $(INSTALL) -m644 $(DOC_MAN1) $(DESTDIR)$(man1dir)
+       $(INSTALL) -m644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
 
 
 #
@@ -70,30 +73,44 @@ doc.dep : $(wildcard *.txt) build-docdep.perl
 
 -include doc.dep
 
-git.7: README
+cmds_txt = cmds-ancillaryinterrogators.txt \
+       cmds-ancillarymanipulators.txt \
+       cmds-mainporcelain.txt \
+       cmds-plumbinginterrogators.txt \
+       cmds-plumbingmanipulators.txt \
+       cmds-synchingrepositories.txt \
+       cmds-synchelpers.txt \
+       cmds-purehelpers.txt \
+       cmds-foreignscminterface.txt
 
-README: ../README
-       cp $< $@
+$(cmds_txt): cmd-list.perl $(MAN1_TXT)
+       perl ./cmd-list.perl
 
+git.7 git.html: git.txt core-intro.txt
 
 clean:
-       rm -f *.xml *.html *.1 *.7 howto-index.txt howto/*.html doc.dep README
+       rm -f *.xml *.html *.1 *.7 howto-index.txt howto/*.html doc.dep
+       rm -f $(cmds_txt)
 
 %.html : %.txt
-       asciidoc -b xhtml11 -d manpage -f asciidoc.conf $<
+       $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf $(ASCIIDOC_EXTRA) $<
 
 %.1 %.7 : %.xml
        xmlto -m callouts.xsl man $<
 
 %.xml : %.txt
-       asciidoc -b docbook -d manpage -f asciidoc.conf $<
+       $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf $<
 
-git.html: git.txt README
+user-manual.xml: user-manual.txt user-manual.conf
+       $(ASCIIDOC) -b docbook -d book $<
+
+user-manual.html: user-manual.xml
+       xmlto html-nochunks $<
 
 glossary.html : glossary.txt sort_glossary.pl
        cat $< | \
        perl sort_glossary.pl | \
-       asciidoc -b xhtml11 - > glossary.html
+       $(ASCIIDOC) -b xhtml11 - > glossary.html
 
 howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
        rm -f $@+ $@
@@ -101,14 +118,17 @@ howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
        mv $@+ $@
 
 $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
-       asciidoc -b xhtml11 $*.txt
+       $(ASCIIDOC) -b xhtml11 $*.txt
 
 WEBDOC_DEST = /pub/software/scm/git/docs
 
 $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
        rm -f $@+ $@
-       sed -e '1,/^$$/d' $< | asciidoc -b xhtml11 - >$@+
+       sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+
        mv $@+ $@
 
 install-webdoc : html
        sh ./install-webdoc.sh $(WEBDOC_DEST)
+
+quick-install:
+       sh ./install-doc-quick.sh $(DOC_REF) $(mandir)
diff --git a/Documentation/RelNotes-1.5.0.txt b/Documentation/RelNotes-1.5.0.txt
new file mode 100644 (file)
index 0000000..84e7eaf
--- /dev/null
@@ -0,0 +1,468 @@
+GIT v1.5.0 Release Notes
+========================
+
+Old news
+--------
+
+This section is for people who are upgrading from ancient
+versions of git.  Although all of the changes in this section
+happened before the current v1.4.4 release, they are summarized
+here in the v1.5.0 release notes for people who skipped earlier
+versions.
+
+As of git v1.5.0 there are some optional features that changes
+the repository to allow data to be stored and transferred more
+efficiently.  These features are not enabled by default, as they
+will make the repository unusable with older versions of git.
+Specifically, the available options are:
+
+ - There is a configuration variable core.legacyheaders that
+   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 clients over dumb transports (e.g. http) using older
+   versions of git will also be affected.
+
+ - Since v1.4.3, configuration repack.usedeltabaseoffset allows
+   packfile to be created in more space efficient format, which
+   cannot be read by git older than that version.
+
+The above two are not enabled by default and you explicitly have
+to ask for them, because these two features make repositories
+unreadable by older versions of git, and in v1.5.0 we still do
+not enable them by default for the same reason.  We will change
+this default probably 1 year after 1.4.2's release, when it is
+reasonable to expect everybody to have new enough version of
+git.
+
+ - 'git pack-refs' appeared in v1.4.4; this command allows tags
+   to be accessed much more efficiently than the traditional
+   'one-file-per-tag' format.  Older git-native clients can
+   still fetch from a repository that packed and pruned refs
+   (the server side needs to run the up-to-date version of git),
+   but older dumb transports cannot.  Packing of refs is done by
+   an explicit user action, either by use of "git pack-refs
+   --prune" command or by use of "git gc" command.
+
+ - 'git -p' to paginate anything -- many commands do pagination
+   by default on a tty.  Introduced between v1.4.1 and v1.4.2;
+   this may surprise old timers.
+
+ - 'git archive' superseded 'git tar-tree' in v1.4.3;
+
+ - 'git cvsserver' was new invention in v1.3.0;
+
+ - 'git repo-config', 'git grep', 'git rebase' and 'gitk' were
+   seriously enhanced during v1.4.0 timeperiod.
+
+ - 'gitweb' became part of git.git during v1.4.0 timeperiod and
+   seriously modified since then.
+
+ - reflog is an v1.4.0 invention.  This allows you to name a
+   revision that a branch used to be at (e.g. "git diff
+   master@{yesterday} master" allows you to see changes since
+   yesterday's tip of the branch).
+
+
+Updates in v1.5.0 since v1.4.4 series
+-------------------------------------
+
+* Index manipulation
+
+ - git-add is to add contents to the index (aka "staging area"
+   for the next commit), whether the file the contents happen to
+   be is an existing one or a newly created one.
+
+ - git-add without any argument does not add everything
+   anymore.  Use 'git-add .' instead.  Also you can add
+   otherwise ignored files with an -f option.
+
+ - git-add tries to be more friendly to users by offering an
+   interactive mode ("git-add -i").
+
+ - git-commit <path> used to refuse to commit if <path> was
+   different between HEAD and the index (i.e. update-index was
+   used on it earlier).  This check was removed.
+
+ - git-rm is much saner and safer.  It is used to remove paths
+   from both the index file and the working tree, and makes sure
+   you are not losing any local modification before doing so.
+
+ - git-reset <tree> <paths>... can be used to revert index
+   entries for selected paths.
+
+ - git-update-index is much less visible.  Many suggestions to
+  use the command in git output and documentation have now been
+  replaced by simpler commands such as "git add" or "git rm".
+
+
+* Repository layout and objects transfer
+
+ - The data for origin repository is stored in the configuration
+   file $GIT_DIR/config, not in $GIT_DIR/remotes/, for newly
+   created clones.  The latter is still supported and there is
+   no need to convert your existing repository if you are
+   already comfortable with your workflow with the layout.
+
+ - git-clone always uses what is known as "separate remote"
+   layout for a newly created repository with a working tree.
+
+   A repository with the separate remote layout starts with only
+   one default branch, 'master', to be used for your own
+   development.  Unlike the traditional layout that copied all
+   the upstream branches into your branch namespace (while
+   renaming their 'master' to your 'origin'), the new layout
+   puts upstream branches into local "remote-tracking branches"
+   with their own namespace. These can be referenced with names
+   such as "origin/$upstream_branch_name" and are stored in
+   .git/refs/remotes rather than .git/refs/heads where normal
+   branches are stored.
+
+   This layout keeps your own branch namespace less cluttered,
+   avoids name collision with your upstream, makes it possible
+   to automatically track new branches created at the remote
+   after you clone from it, and makes it easier to interact with
+   more than one remote repository (you can use "git remote" to
+   add other repositories to track).  There might be some
+   surprises:
+
+   * 'git branch' does not show the remote tracking branches.
+     It only lists your own branches.  Use '-r' option to view
+     the tracking branches.
+
+   * If you are forking off of a branch obtained from the
+     upstream, you would have done something like 'git branch
+     my-next next', because traditional layout dropped the
+     tracking branch 'next' into your own branch namespace.
+     With the separate remote layout, you say 'git branch next
+     origin/next', which allows you to use the matching name
+     'next' for your own branch.  It also allows you to track a
+     remote other than 'origin' (i.e. where you initially cloned
+     from) and fork off of a branch from there the same way
+     (e.g. "git branch mingw j6t/master").
+
+   Repositories initialized with the traditional layout continue
+   to work.
+
+ - New branches that appear on the origin side after a clone is
+   made are also tracked automatically.  This is done with an
+   wildcard refspec "refs/heads/*:refs/remotes/origin/*", which
+   older git does not understand, so if you clone with 1.5.0,
+   you would need to downgrade remote.*.fetch in the
+   configuration file to specify each branch you are interested
+   in individually if you plan to fetch into the repository with
+   older versions of git (but why would you?).
+
+ - Similarly, wildcard refspec "refs/heads/*:refs/remotes/me/*"
+   can be given to "git-push" command to update the tracking
+   branches that is used to track the repository you are pushing
+   from on the remote side.
+
+ - git-branch and git-show-branch know remote tracking branches
+   (use the command line switch "-r" to list only tracked branches).
+
+ - git-push can now be used to delete a remote branch or a tag.
+   This requires the updated git on the remote side (use "git
+   push <remote> :refs/heads/<branch>" to delete "branch").
+
+ - git-push more aggressively keeps the transferred objects
+   packed.  Earlier we recommended to monitor amount of loose
+   objects and repack regularly, but you should repack when you
+   accumulated too many small packs this way as well.  Updated
+   git-count-objects helps you with this.
+
+ - git-fetch also more aggressively keeps the transferred objects
+   packed.  This behavior of git-push and git-fetch can be
+   tweaked with a single configuration transfer.unpacklimit (but
+   usually there should not be any need for a user to tweak it).
+
+ - A new command, git-remote, can help you manage your remote
+   tracking branch definitions.
+
+ - You may need to specify explicit paths for upload-pack and/or
+   receive-pack due to your ssh daemon configuration on the
+   other end.  This can now be done via remote.*.uploadpack and
+   remote.*.receivepack configuration.
+
+
+* Bare repositories
+
+ - Certain commands change their behavior in a bare repository
+   (i.e. a repository without associated working tree).  We use
+   a fairly conservative heuristic (if $GIT_DIR is ".git", or
+   ends with "/.git", the repository is not bare) to decide if a
+   repository is bare, but "core.bare" configuration variable
+   can be used to override the heuristic when it misidentifies
+   your repository.
+
+ - git-fetch used to complain updating the current branch but
+   this is now allowed for a bare repository.  So is the use of
+   'git-branch -f' to update the current branch.
+
+ - Porcelain-ish commands that require a working tree refuses to
+   work in a bare repository.
+
+
+* Reflog
+
+ - Reflog records the history from the view point of the local
+   repository. In other words, regardless of the real history,
+   the reflog shows the history as seen by one particular
+   repository (this enables you to ask "what was the current
+   revision in _this_ repository, yesterday at 1pm?").  This
+   facility is enabled by default for repositories with working
+   trees, and can be accessed with the "branch@{time}" and
+   "branch@{Nth}" notation.
+
+ - "git show-branch" learned showing the reflog data with the
+   new -g option.  "git log" has -s option to view reflog
+   entries in a more verbose manner.
+
+ - git-branch knows how to rename branches and moves existing
+   reflog data from the old branch to the new one.
+
+ - In addition to the reflog support in v1.4.4 series, HEAD
+   reference maintains its own log.  "HEAD@{5.minutes.ago}"
+   means the commit you were at 5 minutes ago, which takes
+   branch switching into account.  If you want to know where the
+   tip of your current branch was at 5 minutes ago, you need to
+   explicitly say its name (e.g. "master@{5.minutes.ago}") or
+   omit the refname altogether i.e. "@{5.minutes.ago}".
+
+ - The commits referred to by reflog entries are now protected
+   against pruning.  The new command "git reflog expire" can be
+   used to truncate older reflog entries and entries that refer
+   to commits that have been pruned away previously with older
+   versions of git.
+
+   Existing repositories that have been using reflog may get
+   complaints from fsck-objects and may not be able to run
+   git-repack, if you had run git-prune from older git; please
+   run "git reflog expire --stale-fix --all" first to remove
+   reflog entries that refer to commits that are no longer in
+   the repository when that happens.
+
+
+* Crufts removal
+
+ - We used to say "old commits are retrievable using reflog and
+   'master@{yesterday}' syntax as long as you haven't run
+   git-prune".  We no longer have to say the latter half of the
+   above sentence, as git-prune does not remove things reachable
+   from reflog entries.
+
+ - 'git-prune' by default does not remove _everything_
+   unreachable, as there is a one-day grace period built-in.
+
+ - There is a toplevel garbage collector script, 'git-gc', that
+   runs periodic cleanup functions, including 'git-repack -a -d',
+   'git-reflog expire', 'git-pack-refs --prune', and 'git-rerere
+   gc'.
+
+ - The output from fsck ("fsck-objects" is called just "fsck"
+   now, but the old name continues to work) was needlessly
+   alarming in that it warned missing objects that are reachable
+   only from dangling objects.  This has been corrected and the
+   output is much more useful.
+
+
+* Detached HEAD
+
+ - You can use 'git-checkout' to check out an arbitrary revision
+   or a tag as well, instead of named branches.  This will
+   dissociate your HEAD from the branch you are currently on.
+
+   A typical use of this feature is to "look around".  E.g.
+
+       $ git checkout v2.6.16
+       ... compile, test, etc.
+       $ git checkout v2.6.17
+       ... compile, test, etc.
+
+ - After detaching your HEAD, you can go back to an existing
+   branch with usual "git checkout $branch".  Also you can
+   start a new branch using "git checkout -b $newbranch" to
+   start a new branch at that commit.
+
+ - You can even pull from other repositories, make merges and
+   commits while your HEAD is detached.  Also you can use "git
+   reset" to jump to arbitrary commit, while still keeping your
+   HEAD detached.
+
+   Going back to attached state (i.e. on a particular branch) by
+   "git checkout $branch" can lose the current stat you arrived
+   in these ways, and "git checkout" refuses when the detached
+   HEAD is not pointed by any existing ref (an existing branch,
+   a remote tracking branch or a tag).  This safety can be
+   overridden with "git checkout -f $branch".
+
+
+* Packed refs
+
+ - Repositories with hundreds of tags have been paying large
+   overhead, both in storage and in runtime, due to the
+   traditional one-ref-per-file format.  A new command,
+   git-pack-refs, can be used to "pack" them in more efficient
+   representation (you can let git-gc do this for you).
+
+ - Clones and fetches over dumb transports are now aware of
+   packed refs and can download from repositories that use
+   them.
+
+
+* Configuration
+
+ - configuration related to color setting are consolidated under
+   color.* namespace (older diff.color.*, status.color.* are
+   still supported).
+
+ - 'git-repo-config' command is accessible as 'git-config' now.
+
+
+* Updated features
+
+ - git-describe uses better criteria to pick a base ref.  It
+   used to pick the one with the newest timestamp, but now it
+   picks the one that is topologically the closest (that is,
+   among ancestors of commit C, the ref T that has the shortest
+   output from "git-rev-list T..C" is chosen).
+
+ - git-describe gives the number of commits since the base ref
+   between the refname and the hash suffix.  E.g. the commit one
+   before v2.6.20-rc6 in the kernel repository is:
+
+       v2.6.20-rc5-306-ga21b069
+
+   which tells you that its object name begins with a21b069,
+   v2.6.20-rc5 is an ancestor of it (meaning, the commit
+   contains everything -rc5 has), and there are 306 commits
+   since v2.6.20-rc5.
+
+ - git-describe with --abbrev=0 can be used to show only the
+   name of the base ref.
+
+ - git-blame learned a new option, --incremental, that tells it
+   to output the blames as they are assigned.  A sample script
+   to use it is also included as contrib/blameview.
+
+ - git-blame starts annotating from the working tree by default.
+
+
+* Less external dependency
+
+ - We no longer require the "merge" program from the RCS suite.
+   All 3-way file-level merges are now done internally.
+
+ - The original implementation of git-merge-recursive which was
+   in Python has been removed; we have a C implementation of it
+   now.
+
+ - git-shortlog is no longer a Perl script.  It no longer
+   requires output piped from git-log; it can accept revision
+   parameters directly on the command line.
+
+
+* I18n
+
+ - We have always encouraged the commit message to be encoded in
+   UTF-8, but the users are allowed to use legacy encoding as
+   appropriate for their projects.  This will continue to be the
+   case.  However, a non UTF-8 commit encoding _must_ be
+   explicitly set with i18n.commitencoding in the repository
+   where a commit is made; otherwise git-commit-tree will
+   complain if the log message does not look like a valid UTF-8
+   string.
+
+ - The value of i18n.commitencoding in the originating
+   repository is recorded in the commit object on the "encoding"
+   header, if it is not UTF-8.  git-log and friends notice this,
+   and reencodes the message to the log output encoding when
+   displaying, if they are different.  The log output encoding
+   is determined by "git log --encoding=<encoding>",
+   i18n.logoutputencoding configuration, or i18n.commitencoding
+   configuration, in the decreasing order of preference, and
+   defaults to UTF-8.
+
+ - Tools for e-mailed patch application now default to -u
+   behavior; i.e. it always re-codes from the e-mailed encoding
+   to the encoding specified with i18n.commitencoding.  This
+   unfortunately forces projects that have happily been using a
+   legacy encoding without setting i18n.commitencoding to set
+   the configuration, but taken with other improvement, please
+   excuse us for this very minor one-time inconvenience.
+
+
+* e-mailed patches
+
+ - See the above I18n section.
+
+ - git-format-patch now enables --binary without being asked.
+   git-am does _not_ default to it, as sending binary patch via
+   e-mail is unusual and is harder to review than textual
+   patches and it is prudent to require the person who is
+   applying the patch to explicitly ask for it.
+
+ - The default suffix for git-format-patch output is now ".patch",
+   not ".txt".  This can be changed with --suffix=.txt option,
+   or setting the config variable "format.suffix" to ".txt".
+
+
+* Foreign SCM interfaces
+
+  - git-svn now requires the Perl SVN:: libraries, the
+    command-line backend was too slow and limited.
+
+  - the 'commit' subcommand of git-svn has been renamed to
+    'set-tree', and 'dcommit' is the recommended replacement for
+    day-to-day work.
+
+  - git fast-import backend.
+
+
+* User support
+
+ - Quite a lot of documentation updates.
+
+ - Bash completion scripts have been updated heavily.
+
+ - Better error messages for often used Porcelainish commands.
+
+ - Git GUI.  This is a simple Tk based graphical interface for
+   common Git operations.
+
+
+* Sliding mmap
+
+ - We used to assume that we can mmap the whole packfile while
+   in use, but with a large project this consumes huge virtual
+   memory space and truly huge ones would not fit in the
+   userland address space on 32-bit platforms.  We now mmap huge
+   packfile in pieces to avoid this problem.
+
+
+* Shallow clones
+
+ - There is a partial support for 'shallow' repositories that
+   keeps only recent history.  A 'shallow clone' is created by
+   specifying how deep that truncated history should be
+   (e.g. "git clone --depth=5 git://some.where/repo.git").
+
+   Currently a shallow repository has number of limitations:
+
+   - Cloning and fetching _from_ a shallow clone are not
+     supported (nor tested -- so they might work by accident but
+     they are not expected to).
+
+   - Pushing from nor into a shallow clone are not expected to
+     work.
+
+   - Merging inside a shallow repository would work as long as a
+     merge base is found in the recent history, but otherwise it
+     will be like merging unrelated histories and may result in
+     huge conflicts.
+
+   but this would be more than adequate for people who want to
+   look at near the tip of a big project with a deep history and
+   send patches in e-mail format.
index 646b6e73318e04cfff7b20abd5d06be424bce503..285781d9db8d337dadf5a59bbf46183d2f816fac 100644 (file)
@@ -23,7 +23,8 @@ probably need to split up your commit to finer grained pieces.
 
 Oh, another thing.  I am picky about whitespaces.  Make sure your
 changes do not trigger errors with the sample pre-commit hook shipped
-in templates/hooks--pre-commit.
+in templates/hooks--pre-commit.  To help ensure this does not happen,
+run git diff --check on your changes before you commit.
 
 
 (2) Generate your patch using git tools out of your commits.
@@ -72,7 +73,9 @@ other than the commit message itself.  Place such "cover letter"
 material between the three dash lines and the diffstat.
 
 Do not attach the patch as a MIME attachment, compressed or not.
-Do not let your e-mail client send quoted-printable.  Many
+Do not let your e-mail client send quoted-printable.  Do not let
+your e-mail client send format=flowed which would destroy
+whitespaces in your patches. Many
 popular e-mail applications will not always transmit a MIME
 attachment as plain text, making it impossible to comment on
 your code.  A MIME attachment also takes a bit more time to
@@ -312,3 +315,35 @@ settings but I haven't tried, yet.
        mail.identity.default.compose_html      => false
        mail.identity.id?.compose_html          => false
 
+
+Gnus
+----
+
+'|' in the *Summary* buffer can be used to pipe the current
+message to an external program, and this is a handy way to drive
+"git am".  However, if the message is MIME encoded, what is
+piped into the program is the representation you see in your
+*Article* buffer after unwrapping MIME.  This is often not what
+you would want for two reasons.  It tends to screw up non ASCII
+characters (most notably in people's names), and also
+whitespaces (fatal in patches).  Running 'C-u g' to display the
+message in raw form before using '|' to run the pipe can work
+this problem around.
+
+
+KMail
+-----
+
+This should help you to submit patches inline using KMail.
+
+1) Prepare the patch as a text file.
+
+2) Click on New Mail.
+
+3) Go under "Options" in the Composer window and be sure that
+"Word wrap" is not set.
+
+4) Use Message -> Insert file... and insert the patch.
+
+5) Back in the compose window: add whatever other text you wish to the
+message, complete the addressing and subject fields, and press send.
index ad03755d8fadd016ceb9b3118f457b864902e655..6a361a21367bfed4ae325049556f2e82e7e1dbe4 100644 (file)
        <xsl:apply-templates/>
        <xsl:text>.br&#10;</xsl:text>
 </xsl:template>
+
+<!-- sorry, this is not about callouts, but attempts to work around
+ spurious .sp at the tail of the line docbook stylesheets seem to add -->
+<xsl:template match="simpara">
+  <xsl:variable name="content">
+    <xsl:apply-templates/>
+  </xsl:variable>
+  <xsl:value-of select="normalize-space($content)"/>
+  <xsl:if test="not(ancestor::authorblurb) and
+                not(ancestor::personblurb)">
+    <xsl:text>&#10;&#10;</xsl:text>
+  </xsl:if>
+</xsl:template>
+
 </xsl:stylesheet>
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
new file mode 100755 (executable)
index 0000000..d4fd72d
--- /dev/null
@@ -0,0 +1,185 @@
+#
+
+sub format_one {
+       my ($out, $name) = @_;
+       my ($state, $description);
+       open I, '<', "$name.txt" or die "No such file $name.txt";
+       while (<I>) {
+               if (/^NAME$/) {
+                       $state = 1;
+                       next;
+               }
+               if ($state == 1 && /^----$/) {
+                       $state = 2;
+                       next;
+               }
+               next if ($state != 2);
+               chomp;
+               $description = $_;
+               last;
+       }
+       close I;
+       if (!defined $description) {
+               die "No description found in $name.txt";
+       }
+       if (my ($verify_name, $text) = ($description =~ /^($name) - (.*)/)) {
+               print $out "gitlink:$name\[1\]::\n";
+               print $out "\t$text.\n\n";
+       }
+       else {
+               die "Description does not match $name: $description";
+       }
+}
+
+my %cmds = ();
+while (<DATA>) {
+       next if /^#/;
+
+       chomp;
+       my ($name, $cat) = /^(\S+)\s+(.*)$/;
+       push @{$cmds{$cat}}, $name;
+}
+
+for my $cat (qw(ancillaryinterrogators
+               ancillarymanipulators
+               mainporcelain
+               plumbinginterrogators
+               plumbingmanipulators
+               synchingrepositories
+               foreignscminterface
+               purehelpers
+               synchelpers)) {
+       my $out = "cmds-$cat.txt";
+       open O, '>', "$out+" or die "Cannot open output file $out+";
+       for (@{$cmds{$cat}}) {
+               format_one(\*O, $_);
+       }
+       close O;
+       rename "$out+", "$out";
+}
+
+__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
+git-bisect                              mainporcelain
+git-blame                               ancillaryinterrogators
+git-branch                              mainporcelain
+git-cat-file                            plumbinginterrogators
+git-checkout-index                      plumbingmanipulators
+git-checkout                            mainporcelain
+git-check-ref-format                    purehelpers
+git-cherry                              ancillaryinterrogators
+git-cherry-pick                         mainporcelain
+git-clean                               mainporcelain
+git-clone                               mainporcelain
+git-commit                              mainporcelain
+git-commit-tree                         plumbingmanipulators
+git-convert-objects                     ancillarymanipulators
+git-count-objects                       ancillaryinterrogators
+git-cvsexportcommit                     foreignscminterface
+git-cvsimport                           foreignscminterface
+git-cvsserver                           foreignscminterface
+git-daemon                              synchingrepositories
+git-describe                            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-fmt-merge-msg                       purehelpers
+git-for-each-ref                        plumbinginterrogators
+git-format-patch                        mainporcelain
+git-fsck                               ancillaryinterrogators
+git-gc                                  mainporcelain
+git-get-tar-commit-id                   ancillaryinterrogators
+git-grep                                mainporcelain
+git-hash-object                         plumbingmanipulators
+git-http-fetch                          synchelpers
+git-http-push                           synchelpers
+git-imap-send                           foreignscminterface
+git-index-pack                          plumbingmanipulators
+git-init                                mainporcelain
+git-instaweb                            ancillaryinterrogators
+gitk                                    mainporcelain
+git-local-fetch                         synchingrepositories
+git-log                                 mainporcelain
+git-lost-found                          ancillarymanipulators
+git-ls-files                            plumbinginterrogators
+git-ls-remote                           plumbinginterrogators
+git-ls-tree                             plumbinginterrogators
+git-mailinfo                            purehelpers
+git-mailsplit                           purehelpers
+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-mktag                               plumbingmanipulators
+git-mktree                              plumbingmanipulators
+git-mv                                  mainporcelain
+git-name-rev                            plumbinginterrogators
+git-pack-objects                        plumbingmanipulators
+git-pack-redundant                      plumbinginterrogators
+git-pack-refs                           ancillarymanipulators
+git-parse-remote                        synchelpers
+git-patch-id                            purehelpers
+git-peek-remote                         purehelpers
+git-prune                               ancillarymanipulators
+git-prune-packed                        plumbingmanipulators
+git-pull                                mainporcelain
+git-push                                mainporcelain
+git-quiltimport                         foreignscminterface
+git-read-tree                           plumbingmanipulators
+git-rebase                              mainporcelain
+git-receive-pack                        synchelpers
+git-reflog                              ancillarymanipulators
+git-relink                              ancillarymanipulators
+git-repack                              ancillarymanipulators
+git-config                              ancillarymanipulators
+git-request-pull                        foreignscminterface
+git-rerere                              ancillaryinterrogators
+git-reset                               mainporcelain
+git-revert                              mainporcelain
+git-rev-list                            plumbinginterrogators
+git-rev-parse                           ancillaryinterrogators
+git-rm                                  mainporcelain
+git-runstatus                           ancillaryinterrogators
+git-send-email                          foreignscminterface
+git-send-pack                           synchingrepositories
+git-shell                               synchelpers
+git-shortlog                            mainporcelain
+git-show                                mainporcelain
+git-show-branch                         ancillaryinterrogators
+git-show-index                          plumbinginterrogators
+git-show-ref                            plumbinginterrogators
+git-sh-setup                            purehelpers
+git-ssh-fetch                           synchingrepositories
+git-ssh-upload                          synchingrepositories
+git-status                              mainporcelain
+git-stripspace                          purehelpers
+git-svn                                 foreignscminterface
+git-svnimport                           foreignscminterface
+git-symbolic-ref                        plumbingmanipulators
+git-tag                                 mainporcelain
+git-tar-tree                            plumbinginterrogators
+git-unpack-file                         plumbinginterrogators
+git-unpack-objects                      plumbingmanipulators
+git-update-index                        plumbingmanipulators
+git-update-ref                          plumbingmanipulators
+git-update-server-info                  synchingrepositories
+git-upload-archive                      synchelpers
+git-upload-pack                         synchelpers
+git-var                                 plumbinginterrogators
+git-verify-pack                         plumbinginterrogators
+git-verify-tag                          ancillaryinterrogators
+git-whatchanged                         ancillaryinterrogators
+git-write-tree                          plumbingmanipulators
index 9d3c71c3b87a9603973de42c76c4570a94420398..38655350f22bde08786b4a42e608ba76c1becea4 100644 (file)
@@ -2,21 +2,84 @@ CONFIGURATION FILE
 ------------------
 
 The git configuration file contains a number of variables that affect
-the git command's behavior. They can be used by both the git plumbing
+the git command's behavior. `.git/config` file for each repository
+is used to store the information for that repository, and
+`$HOME/.gitconfig` is used to store per user information to give
+fallback values for `.git/config` file.
+
+They can be used by both the git plumbing
 and the porcelains. The variables are divided into sections, where
 in the fully qualified variable name the variable itself is the last
 dot-separated segment and the section name is everything before the last
 dot. The variable names are case-insensitive and only alphanumeric
 characters are allowed. Some variables may appear multiple times.
 
+Syntax
+~~~~~~
+
 The syntax is fairly flexible and permissive; whitespaces are mostly
-ignored. The '#' and ';' characters begin comments to the end of line,
-blank lines are ignored, lines containing strings enclosed in square
-brackets start sections and all the other lines are recognized
-as setting variables, in the form 'name = value'. If there is no equal
-sign on the line, the entire line is taken as 'name' and the variable
-is recognized as boolean "true". String values may be entirely or partially
-enclosed in double quotes; some variables may require special value format.
+ignored.  The '#' and ';' characters begin comments to the end of line,
+blank lines are ignored.
+
+The file consists of sections and variables.  A section begins with
+the name of the section in square brackets and continues until the next
+section begins.  Section names are not case sensitive.  Only alphanumeric
+characters, '`-`' and '`.`' are allowed in section names.  Each variable
+must belong to some section, which means that there must be section
+header before first setting of a variable.
+
+Sections can be further divided into subsections.  To begin a subsection
+put its name in double quotes, separated by space from the section name,
+in the section header, like in example below:
+
+--------
+       [section "subsection"]
+
+--------
+
+Subsection names can contain any characters except newline (doublequote
+'`"`' and backslash have to be escaped as '`\"`' and '`\\`',
+respectively) and are case sensitive.  Section header cannot span multiple
+lines.  Variables may belong directly to a section or to a given subsection.
+You can have `[section]` if you have `[section "subsection"]`, but you
+don't need to.
+
+There is also (case insensitive) alternative `[section.subsection]` syntax.
+In this syntax subsection names follow the same restrictions as for section
+name.
+
+All the other lines are recognized as setting variables, in the form
+'name = value'.  If there is no equal sign on the line, the entire line
+is taken as 'name' and the variable is recognized as boolean "true".
+The variable names are case-insensitive and only alphanumeric
+characters and '`-`' are allowed.  There can be more than one value
+for a given variable; we say then that variable is multivalued.
+
+Leading and trailing whitespace in a variable value is discarded.
+Internal whitespace within a variable value is retained verbatim.
+
+The values following the equals sign in variable assign are all either
+a string, an integer, or a boolean.  Boolean values may be given as yes/no,
+0/1 or true/false.  Case is not significant in boolean values, when
+converting value to the canonical form using '--bool' type specifier;
+`git-config` will ensure that the output is "true" or "false".
+
+String values may be entirely or partially enclosed in double quotes.
+You need to enclose variable value in double quotes if you want to
+preserve leading or trailing whitespace, or if variable value contains
+beginning of comment characters (if it contains '#' or ';').
+Double quote '`"`' and backslash '`\`' characters in variable value must
+be escaped: use '`\"`' for '`"`' and '`\\`' for '`\`'.
+
+The following escape sequences (beside '`\"`' and '`\\`') are recognized:
+'`\n`' for newline character (NL), '`\t`' for horizontal tabulation (HT, TAB)
+and '`\b`' for backspace (BS).  No other char escape sequence, nor octal
+char sequences are valid.
+
+Variable value ending in a '`\`' is continued on the next line in the
+customary UNIX fashion.
+
+Some variables may require special value format.
 
 Example
 ~~~~~~~
@@ -31,6 +94,15 @@ Example
                external = "/usr/local/bin/gnu-diff -u"
                renames = true
 
+       [branch "devel"]
+               remote = origin
+               merge = refs/heads/devel
+
+       # Proxy settings
+       [core]
+               gitProxy="ssh" for "ssh://kernel.org/"
+               gitProxy=default-proxy ; for the rest
+
 Variables
 ~~~~~~~~~
 
@@ -77,10 +149,13 @@ core.logAllRefUpdates::
        only when the file exists.  If this configuration
        variable is set to true, missing "$GIT_DIR/logs/<ref>"
        file is automatically created for branch heads.
-
-       This information can be used to determine what commit
-       was the tip of a branch "2 days ago".  This value is
-       false by default (no automated creation of log files).
++
+This information can be used to determine what commit
+was the tip of a branch "2 days ago".
++
+This value is true by default in a repository that has
+a working directory associated with it, and false by
+default in a bare repository.
 
 core.repositoryFormatVersion::
        Internal variable identifying the repository format and layout
@@ -92,7 +167,7 @@ core.sharedRepository::
        group-writable). When 'all' (or 'world' or 'everybody'), the
        repository will be readable by all users, additionally to being
        group-shareable. When 'umask' (or 'false'), git will use permissions
-       reported by umask(2). See gitlink:git-init-db[1]. False by default.
+       reported by umask(2). See gitlink:git-init[1]. False by default.
 
 core.warnAmbiguousRefs::
        If true, git will warn you if the ref name you passed it is ambiguous
@@ -110,6 +185,34 @@ core.legacyheaders::
        database directly (where the "http://" and "rsync://" protocols
        count as direct access).
 
+core.packedGitWindowSize::
+       Number of bytes of a pack file to map into memory in a
+       single mapping operation.  Larger window sizes may allow
+       your system to process a smaller number of large pack files
+       more quickly.  Smaller window sizes will negatively affect
+       performance due to increased calls to the operating system's
+       memory manager, but may improve performance when accessing
+       a large number of large pack files.
++
+Default is 1 MiB if NO_MMAP was set at compile time, otherwise 32
+MiB on 32 bit platforms and 1 GiB on 64 bit platforms.  This should
+be reasonable for all users/operating systems.  You probably do
+not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
+core.packedGitLimit::
+       Maximum number of bytes to map simultaneously into memory
+       from pack files.  If Git needs to access more than this many
+       bytes at once to complete an operation it will unmap existing
+       regions to reclaim virtual address space within the process.
++
+Default is 256 MiB on 32 bit platforms and 8 GiB on 64 bit platforms.
+This should be reasonable for all users/operating systems, except on
+the largest projects.  You probably do not need to adjust this value.
++
+Common unit suffixes of 'k', 'm', or 'g' are supported.
+
 alias.*::
        Command aliases for the gitlink:git[1] command wrapper - e.g.
        after defining "alias.last = cat-file commit HEAD", the invocation
@@ -119,36 +222,81 @@ alias.*::
        spaces, the usual shell quoting and escaping is supported.
        quote pair and a backslash can be used to quote them.
 
+       If the alias expansion is prefixed with an exclamation point,
+       it will be treated as a shell command.  For example, defining
+       "alias.new = !gitk --all --not ORIG_HEAD", the invocation
+       "git new" is equivalent to running the shell command
+       "gitk --all --not ORIG_HEAD".
+
 apply.whitespace::
        Tells `git-apply` how to handle whitespaces, in the same way
        as the '--whitespace' option. See gitlink:git-apply[1].
 
 branch.<name>.remote::
        When in branch <name>, it tells `git fetch` which remote to fetch.
+       If this option is not given, `git fetch` defaults to remote "origin".
 
 branch.<name>.merge::
-       When in branch <name>, it tells `git fetch` the default remote branch
-       to be merged.
-
-pager.color::
-       A boolean to enable/disable colored output when the pager is in
-       use (default is true).
+       When in branch <name>, it tells `git fetch` the default refspec to
+       be marked for merging in FETCH_HEAD. The value has exactly to match
+       a remote part of one of the refspecs which are fetched from the remote
+       given by "branch.<name>.remote".
+       The merge information is used by `git pull` (which at first calls
+       `git fetch`) to lookup the default branch for merging. Without
+       this option, `git pull` defaults to merge the first refspec fetched.
+       Specify multiple values to get an octopus merge.
+
+color.branch::
+       A boolean to enable/disable color in the output of
+       gitlink:git-branch[1]. May be set to `true` (or `always`),
+       `false` (or `never`) or `auto`, in which case colors are used
+       only when the output is to a terminal. Defaults to false.
 
-diff.color::
+color.branch.<slot>::
+       Use customized color for branch coloration. `<slot>` is one of
+       `current` (the current branch), `local` (a local branch),
+       `remote` (a tracking branch in refs/remotes/), `plain` (other
+       refs).
++
+The value for these configuration variables is a list of colors (at most
+two) and attributes (at most one), separated by spaces.  The colors
+accepted are `normal`, `black`, `red`, `green`, `yellow`, `blue`,
+`magenta`, `cyan` and `white`; the attributes are `bold`, `dim`, `ul`,
+`blink` and `reverse`.  The first color given is the foreground; the
+second is the background.  The position of the attribute, if any,
+doesn't matter.
+
+color.diff::
        When true (or `always`), always use colors in patch.
        When false (or `never`), never.  When set to `auto`, use
        colors only when the output is to the terminal.
 
-diff.color.<slot>::
-       Use customized color for diff colorization.  `<slot>`
-       specifies which part of the patch to use the specified
-       color, and is one of `plain` (context text), `meta`
-       (metainformation), `frag` (hunk header), `old` (removed
-       lines), or `new` (added lines).  The value for these
-       configuration variables can be one of: `normal`, `bold`,
-       `dim`, `ul`, `blink`, `reverse`, `reset`, `black`,
-       `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, or
-       `white`.
+color.diff.<slot>::
+       Use customized color for diff colorization.  `<slot>` specifies
+       which part of the patch to use the specified color, and is one
+       of `plain` (context text), `meta` (metainformation), `frag`
+       (hunk header), `old` (removed lines), `new` (added lines),
+       `commit` (commit headers), or `whitespace` (highlighting dubious
+       whitespace).  The values of these variables may be specified as
+       in color.branch.<slot>.
+
+color.pager::
+       A boolean to enable/disable colored output when the pager is in
+       use (default is true).
+
+color.status::
+       A boolean to enable/disable color in the output of
+       gitlink:git-status[1]. May be set to `true` (or `always`),
+       `false` (or `never`) or `auto`, in which case colors are used
+       only when the output is to a terminal. Defaults to false.
+
+color.status.<slot>::
+       Use customized color for status colorization. `<slot>` is
+       one of `header` (the header text of the status message),
+       `added` or `updated` (files which are added but not committed),
+       `changed` (files which are changed but not added in the index),
+       or `untracked` (files which are not tracked by git). The values of
+       these variables may be specified as in color.branch.<slot>.
 
 diff.renameLimit::
        The number of files to consider when performing the copy/rename
@@ -159,10 +307,50 @@ diff.renames::
        will enable basic rename detection.  If set to "copies" or
        "copy", it will detect copies, as well.
 
+fetch.unpackLimit::
+       If the number of objects fetched over the git native
+       transfer is below this
+       limit, then the objects will be unpacked into loose object
+       files. However if the number of received objects equals or
+       exceeds this limit then the received pack will be stored as
+       a pack, after adding any missing delta bases.  Storing the
+       pack from a push can make the push operation complete faster,
+       especially on slow filesystems.
+
 format.headers::
        Additional email headers to include in a patch to be submitted
        by mail.  See gitlink:git-format-patch[1].
 
+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
+       from the repository.  Setting this to `true` lets `git
+       gc` to run `git pack-refs`.  Setting this to `false` tells
+       `git gc` never to run `git pack-refs`. The default setting is
+       `notbare`. Enable it only when you know you do not have to
+       support such clients.  The default setting will change to `true`
+       at some stage, and setting this to `false` will continue to
+       prevent `git pack-refs` from being run from `git gc`.
+
+gc.reflogexpire::
+       `git reflog expire` removes reflog entries older than
+       this time; defaults to 90 days.
+
+gc.reflogexpireunreachable::
+       `git reflog expire` removes reflog entries older than
+       this time and are not reachable from the current tip;
+       defaults to 30 days.
+
+gc.rerereresolved::
+       Records of conflicted merge you resolved earlier are
+       kept for this many days when `git rerere gc` is run.
+       The default is 60 days.  See gitlink:git-rerere[1].
+
+gc.rerereunresolved::
+       Records of conflicted merge you have not resolved are
+       kept for this many days when `git rerere gc` is run.
+       The default is 15 days.  See gitlink:git-rerere[1].
+
 gitcvs.enabled::
        Whether the cvs pserver interface is enabled for this repository.
        See gitlink:git-cvsserver[1].
@@ -219,10 +407,27 @@ i18n.commitEncoding::
        browser (and possibly at other places in the future or in other
        porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'.
 
+i18n.logOutputEncoding::
+       Character encoding the commit messages are converted to when
+       running `git-log` and friends.
+
+log.showroot::
+       If true, the initial commit will be shown as a big creation event.
+       This is equivalent to a diff against an empty tree.
+       Tools like gitlink:git-log[1] or gitlink:git-whatchanged[1], which
+       normally hide the root commit will now show it. True by default.
+
 merge.summary::
        Whether to include summaries of merged commits in newly created
        merge commit messages. 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.
+
 pack.window::
        The size of the window used by gitlink:git-pack-objects[1] when no
        window size is given on the command line. Defaults to 10.
@@ -246,6 +451,14 @@ remote.<name>.push::
        The default set of "refspec" for gitlink:git-push[1]. See
        gitlink:git-push[1].
 
+remote.<name>.receivepack::
+       The default program to execute on the remote side when pushing.  See
+       option \--exec of gitlink:git-push[1].
+
+remote.<name>.uploadpack::
+       The default program to execute on the remote side when fetching.  See
+       option \--exec of gitlink:git-fetch-pack[1].
+
 repack.usedeltabaseoffset::
        Allow gitlink:git-repack[1] to create packs that uses
        delta-base offset.  Defaults to false.
@@ -258,20 +471,6 @@ showbranch.default::
        The default set of branches for gitlink:git-show-branch[1].
        See gitlink:git-show-branch[1].
 
-status.color::
-       A boolean to enable/disable color in the output of
-       gitlink:git-status[1]. May be set to `true` (or `always`),
-       `false` (or `never`) or `auto`, in which case colors are used
-       only when the output is to a terminal. Defaults to false.
-
-status.color.<slot>::
-       Use customized color for status colorization. `<slot>` is
-       one of `header` (the header text of the status message),
-       `updated` (files which are updated but not committed),
-       `changed` (files which are changed but not updated in the index),
-       or `untracked` (files which are not tracked by git). The values of
-       these variables may be specified as in diff.color.<slot>.
-
 tar.umask::
        By default, gitlink:git-tar-tree[1] sets file and directories modes
        to 0666 or 0777. While this is both useful and acceptable for projects
@@ -293,6 +492,13 @@ user.name::
        Can be overridden by the 'GIT_AUTHOR_NAME' and 'GIT_COMMITTER_NAME'
        environment variables.  See gitlink:git-commit-tree[1].
 
+user.signingkey::
+       If gitlink:git-tag[1] is not selecting the key you want it to
+       automatically when creating a signed tag, you can override the
+       default selection with this variable.  This option is passed
+       unchanged to gpg's --local-user parameter, so you may specify a key
+       using any method that gpg supports.
+
 whatchanged.difftree::
        The default gitlink:git-diff-tree[1] arguments to be used
        for gitlink:git-whatchanged[1].
@@ -316,3 +522,8 @@ receive.denyNonFastForwards::
        even if that push is forced. This configuration variable is
        set when initializing a shared repository.
 
+transfer.unpackLimit::
+       When `fetch.unpackLimit` or `receive.unpackLimit` are
+       not set, the value of this variable is used instead.
+
+
diff --git a/Documentation/core-intro.txt b/Documentation/core-intro.txt
new file mode 100644 (file)
index 0000000..24b060b
--- /dev/null
@@ -0,0 +1,591 @@
+////////////////////////////////////////////////////////////////
+
+       GIT - the stupid content tracker
+
+////////////////////////////////////////////////////////////////
+
+"git" can mean anything, depending on your mood.
+
+ - random three-letter combination that is pronounceable, and not
+   actually used by any common UNIX command.  The fact that it is a
+   mispronunciation of "get" may or may not be relevant.
+ - stupid. contemptible and despicable. simple. Take your pick from the
+   dictionary of slang.
+ - "global information tracker": you're in a good mood, and it actually
+   works for you. Angels sing, and a light suddenly fills the room.
+ - "goddamn idiotic truckload of sh*t": when it breaks
+
+This is a (not so) stupid but extremely fast directory content manager.
+It doesn't do a whole lot at its core, but what it 'does' do is track
+directory contents efficiently.
+
+There are two object abstractions: the "object database", and the
+"current directory cache" aka "index".
+
+The Object Database
+~~~~~~~~~~~~~~~~~~~
+The object database is literally just a content-addressable collection
+of objects.  All objects are named by their content, which is
+approximated by the SHA1 hash of the object itself.  Objects may refer
+to other objects (by referencing their SHA1 hash), and so you can
+build up a hierarchy of objects.
+
+All objects have a statically determined "type" aka "tag", which is
+determined at object creation time, and which identifies the format of
+the object (i.e. how it is used, and how it can refer to other
+objects).  There are currently four different object types: "blob",
+"tree", "commit" and "tag".
+
+A "blob" object cannot refer to any other object, and is, like the type
+implies, a pure storage object containing some user data.  It is used to
+actually store the file data, i.e. a blob object is associated with some
+particular version of some file.
+
+A "tree" object is an object that ties one or more "blob" objects into a
+directory structure. In addition, a tree object can refer to other tree
+objects, thus creating a directory hierarchy.
+
+A "commit" object ties such directory hierarchies together into
+a DAG of revisions - each "commit" is associated with exactly one tree
+(the directory hierarchy at the time of the commit). In addition, a
+"commit" refers to one or more "parent" commit objects that describe the
+history of how we arrived at that directory hierarchy.
+
+As a special case, a commit object with no parents is called the "root"
+object, and is the point of an initial project commit.  Each project
+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.
+
+A "tag" object symbolically identifies and can be used to sign other
+objects. It contains the identifier and type of another object, a
+symbolic name (of course!) and, optionally, a signature.
+
+Regardless of object type, all objects share the following
+characteristics: they are all deflated with zlib, and have a header
+that not only specifies their type, but also provides size information
+about the data in the object.  It's worth noting that the SHA1 hash
+that is used to name the object is the hash of the original data
+plus this header, so `sha1sum` 'file' does not match the object name
+for 'file'.
+(Historical note: in the dawn of the age of git the hash
+was the sha1 of the 'compressed' object.)
+
+As a result, the general consistency of an object can always be tested
+independently of the contents or the type of the object: all objects can
+be validated by verifying that (a) their hashes match the content of the
+file and (b) the object successfully inflates to a stream of bytes that
+forms a sequence of <ascii type without space> + <space> + <ascii decimal
+size> + <byte\0> + <binary object data>.
+
+The structured objects can further have their structure and
+connectivity to other objects verified. This is generally done with
+the `git-fsck` program, which generates a full dependency graph
+of all objects, and verifies their internal consistency (in addition
+to just verifying their superficial consistency through the hash).
+
+The object types in some more detail:
+
+Blob Object
+~~~~~~~~~~~
+A "blob" object is nothing but a binary blob of data, and doesn't
+refer to anything else.  There is no signature or any other
+verification of the data, so while the object is consistent (it 'is'
+indexed by its sha1 hash, so the data itself is certainly correct), it
+has absolutely no other attributes.  No name associations, no
+permissions.  It is purely a blob of data (i.e. normally "file
+contents").
+
+In particular, since the blob is entirely defined by its data, if two
+files in a directory tree (or in multiple different versions of the
+repository) have the same contents, they will share the same blob
+object. The object is totally independent of its location in the
+directory tree, and renaming a file does not change the object that
+file is associated with in any way.
+
+A blob is typically created when gitlink:git-update-index[1]
+is run, and its data can be accessed by gitlink:git-cat-file[1].
+
+Tree Object
+~~~~~~~~~~~
+The next hierarchical object type is the "tree" object.  A tree object
+is a list of mode/name/blob data, sorted by name.  Alternatively, the
+mode data may specify a directory mode, in which case instead of
+naming a blob, that name is associated with another TREE object.
+
+Like the "blob" object, a tree object is uniquely determined by the
+set contents, and so two separate but identical trees will always
+share the exact same object. This is true at all levels, i.e. it's
+true for a "leaf" tree (which does not refer to any other trees, only
+blobs) as well as for a whole subdirectory.
+
+For that reason a "tree" object is just a pure data abstraction: it
+has no history, no signatures, no verification of validity, except
+that since the contents are again protected by the hash itself, we can
+trust that the tree is immutable and its contents never change.
+
+So you can trust the contents of a tree to be valid, the same way you
+can trust the contents of a blob, but you don't know where those
+contents 'came' from.
+
+Side note on trees: since a "tree" object is a sorted list of
+"filename+content", you can create a diff between two trees without
+actually having to unpack two trees.  Just ignore all common parts,
+and your diff will look right.  In other words, you can effectively
+(and efficiently) tell the difference between any two random trees by
+O(n) where "n" is the size of the difference, rather than the size of
+the tree.
+
+Side note 2 on trees: since the name of a "blob" depends entirely and
+exclusively on its contents (i.e. there are no names or permissions
+involved), you can see trivial renames or permission changes by
+noticing that the blob stayed the same.  However, renames with data
+changes need a smarter "diff" implementation.
+
+A tree is created with gitlink:git-write-tree[1] and
+its data can be accessed by gitlink:git-ls-tree[1].
+Two trees can be compared with gitlink:git-diff-tree[1].
+
+Commit Object
+~~~~~~~~~~~~~
+The "commit" object is an object that introduces the notion of
+history into the picture.  In contrast to the other objects, it
+doesn't just describe the physical state of a tree, it describes how
+we got there, and why.
+
+A "commit" is defined by the tree-object that it results in, the
+parent commits (zero, one or more) that led up to that point, and a
+comment on what happened.  Again, a commit is not trusted per se:
+the contents are well-defined and "safe" due to the cryptographically
+strong signatures at all levels, but there is no reason to believe
+that the tree is "good" or that the merge information makes sense.
+The parents do not have to actually have any relationship with the
+result, for example.
+
+Note on commits: unlike real SCM's, commits do not contain
+rename information or file mode change information.  All of that is
+implicit in the trees involved (the result tree, and the result trees
+of the parents), and describing that makes no sense in this idiotic
+file manager.
+
+A commit is created with gitlink:git-commit-tree[1] and
+its data can be accessed by gitlink:git-cat-file[1].
+
+Trust
+~~~~~
+An aside on the notion of "trust". Trust is really outside the scope
+of "git", but it's worth noting a few things.  First off, since
+everything is hashed with SHA1, you 'can' trust that an object is
+intact and has not been messed with by external sources.  So the name
+of an object uniquely identifies a known state - just not a state that
+you may want to trust.
+
+Furthermore, since the SHA1 signature of a commit refers to the
+SHA1 signatures of the tree it is associated with and the signatures
+of the parent, a single named commit specifies uniquely a whole set
+of history, with full contents.  You can't later fake any step of the
+way once you have the name of a commit.
+
+So to introduce some real trust in the system, the only thing you need
+to do is to digitally sign just 'one' special note, which includes the
+name of a top-level commit.  Your digital signature shows others
+that you trust that commit, and the immutability of the history of
+commits tells others that they can trust the whole history.
+
+In other words, you can easily validate a whole archive by just
+sending out a single email that tells the people the name (SHA1 hash)
+of the top commit, and digitally sign that email using something
+like GPG/PGP.
+
+To assist in this, git also provides the tag object...
+
+Tag Object
+~~~~~~~~~~
+Git provides the "tag" object to simplify creating, managing and
+exchanging symbolic and signed tokens.  The "tag" object at its
+simplest simply symbolically identifies another object by containing
+the sha1, type and symbolic name.
+
+However it can optionally contain additional signature information
+(which git doesn't care about as long as there's less than 8k of
+it). This can then be verified externally to git.
+
+Note that despite the tag features, "git" itself only handles content
+integrity; the trust framework (and signature provision and
+verification) has to come from outside.
+
+A tag is created with gitlink:git-mktag[1],
+its data can be accessed by gitlink:git-cat-file[1],
+and the signature can be verified by
+gitlink:git-verify-tag[1].
+
+
+The "index" aka "Current Directory Cache"
+-----------------------------------------
+The index is a simple binary file, which contains an efficient
+representation of a virtual directory content at some random time.  It
+does so by a simple array that associates a set of names, dates,
+permissions and content (aka "blob") objects together.  The cache is
+always kept ordered by name, and names are unique (with a few very
+specific rules) at any point in time, but the cache has no long-term
+meaning, and can be partially updated at any time.
+
+In particular, the index certainly does not need to be consistent with
+the current directory contents (in fact, most operations will depend on
+different ways to make the index 'not' be consistent with the directory
+hierarchy), but it has three very important attributes:
+
+'(a) it can re-generate the full state it caches (not just the
+directory structure: it contains pointers to the "blob" objects so
+that it can regenerate the data too)'
+
+As a special case, there is a clear and unambiguous one-way mapping
+from a current directory cache to a "tree object", which can be
+efficiently created from just the current directory cache without
+actually looking at any other data.  So a directory cache at any one
+time uniquely specifies one and only one "tree" object (but has
+additional data to make it easy to match up that tree object with what
+has happened in the directory)
+
+'(b) it has efficient methods for finding inconsistencies between that
+cached state ("tree object waiting to be instantiated") and the
+current state.'
+
+'(c) it can additionally efficiently represent information about merge
+conflicts between different tree objects, allowing each pathname to be
+associated with sufficient information about the trees involved that
+you can create a three-way merge between them.'
+
+Those are the three ONLY things that the directory cache does.  It's a
+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.
+
+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.
+
+
+
+The Workflow
+------------
+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:
+
+1) working directory -> index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update the index with information from the working directory with
+the gitlink:git-update-index[1] command.  You
+generally update the index information by just specifying the filename
+you want to update, like so:
+
+       git-update-index filename
+
+but to avoid common mistakes with filename globbing etc, the command
+will not normally add totally new entries or remove old entries,
+i.e. it will normally just update existing cache entries.
+
+To tell git that yes, you really do realize that certain files no
+longer exist, or that new files should be added, you
+should use the `--remove` and `--add` flags respectively.
+
+NOTE! A `--remove` flag does 'not' mean that subsequent filenames will
+necessarily be removed: if the files still exist in your directory
+structure, the index will be updated with their new status, not
+removed. The only thing `--remove` means is that update-cache will be
+considering a removed file to be a valid thing, and if the file really
+does not exist any more, it will update the index accordingly.
+
+As a special case, you can also do `git-update-index --refresh`, which
+will refresh the "stat" information of each index to match the current
+stat information. It will 'not' update the object status itself, and
+it will only update the fields that are used to quickly test whether
+an object still matches its old backing store object.
+
+2) index -> object database
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You write your current index file to a "tree" object with the program
+
+       git-write-tree
+
+that doesn't come with any options - it will just write out the
+current index into the set of tree objects that describe that state,
+and it will return the name of the resulting top-level tree. You can
+use that tree to re-generate the index at any time by going in the
+other direction:
+
+3) object database -> index
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You read a "tree" file from the object database, and use that to
+populate (and overwrite - don't do this if your index contains any
+unsaved state that you might want to restore later!) your current
+index.  Normal operation is just
+
+               git-read-tree <sha1 of tree>
+
+and your index file will now be equivalent to the tree that you saved
+earlier. However, that is only your 'index' file: your working
+directory contents have not been modified.
+
+4) index -> working directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update your working directory from the index by "checking out"
+files. This is not a very common operation, since normally you'd just
+keep your files updated, and rather than write to your working
+directory, you'd tell the index files about the changes in your
+working directory (i.e. `git-update-index`).
+
+However, if you decide to jump to a new version, or check out somebody
+else's version, or just restore a previous tree, you'd populate your
+index file with read-tree, and then you need to check out the result
+with
+
+               git-checkout-index filename
+
+or, if you want to check out all of the index, use `-a`.
+
+NOTE! git-checkout-index normally refuses to overwrite old files, so
+if you have an old version of the tree already checked out, you will
+need to use the "-f" flag ('before' the "-a" flag or the filename) to
+'force' the checkout.
+
+
+Finally, there are a few odds and ends which are not purely moving
+from one representation to the other:
+
+5) Tying it all together
+~~~~~~~~~~~~~~~~~~~~~~~~
+To commit a tree you have instantiated with "git-write-tree", you'd
+create a "commit" object that refers to that tree and the history
+behind it - most notably the "parent" commits that preceded it in
+history.
+
+Normally a "commit" has one parent: the previous state of the tree
+before a certain change was made. However, sometimes it can have two
+or more parent commits, in which case we call it a "merge", due to the
+fact that such a commit brings together ("merges") two or more
+previous states represented by other commits.
+
+In other words, while a "tree" represents a particular directory state
+of a working directory, a "commit" represents that state in "time",
+and explains how we got there.
+
+You create a commit object by giving it the tree that describes the
+state at the time of the commit, and a list of parents:
+
+       git-commit-tree <tree> -p <parent> [-p <parent2> ..]
+
+and then giving the reason for the commit on stdin (either through
+redirection from a pipe or file, or by just typing it at the tty).
+
+git-commit-tree will return the name of the object that represents
+that commit, and you should save it away for later use. Normally,
+you'd commit a new `HEAD` state, and while git doesn't care where you
+save the note about that state, in practice we tend to just write the
+result to the file pointed at by `.git/HEAD`, so that we can always see
+what the last committed state was.
+
+Here is an ASCII art by Jon Loeliger that illustrates how
+various pieces fit together.
+
+------------
+
+                     commit-tree
+                      commit obj
+                       +----+
+                       |    |
+                       |    |
+                       V    V
+                    +-----------+
+                    | Object DB |
+                    |  Backing  |
+                    |   Store   |
+                    +-----------+
+                       ^
+           write-tree  |     |
+             tree obj  |     |
+                       |     |  read-tree
+                       |     |  tree obj
+                             V
+                    +-----------+
+                    |   Index   |
+                    |  "cache"  |
+                    +-----------+
+         update-index  ^
+             blob obj  |     |
+                       |     |
+    checkout-index -u  |     |  checkout-index
+             stat      |     |  blob obj
+                             V
+                    +-----------+
+                    |  Working  |
+                    | Directory |
+                    +-----------+
+
+------------
+
+
+6) Examining the data
+~~~~~~~~~~~~~~~~~~~~~
+
+You can examine the data represented in the object database and the
+index with various helper tools. For every object, you can use
+gitlink:git-cat-file[1] to examine details about the
+object:
+
+               git-cat-file -t <objectname>
+
+shows the type of the object, and once you have the type (which is
+usually implicit in where you find the object), you can use
+
+               git-cat-file blob|tree|commit|tag <objectname>
+
+to show its contents. NOTE! Trees have binary content, and as a result
+there is a special helper for showing that content, called
+`git-ls-tree`, which turns the binary content into a more easily
+readable form.
+
+It's especially instructive to look at "commit" objects, since those
+tend to be small and fairly self-explanatory. In particular, if you
+follow the convention of having the top commit name in `.git/HEAD`,
+you can do
+
+               git-cat-file commit HEAD
+
+to see what the top commit was.
+
+7) Merging multiple trees
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Git helps you do a three-way merge, which you can expand to n-way by
+repeating the merge procedure arbitrary times until you finally
+"commit" the state.  The normal situation is that you'd only do one
+three-way merge (two parents), and commit it, but if you like to, you
+can do multiple parents in one go.
+
+To do a three-way merge, you need the two sets of "commit" objects
+that you want to merge, use those to find the closest common parent (a
+third "commit" object), and then use those commit objects to find the
+state of the directory ("tree" object) at these points.
+
+To get the "base" for the merge, you first look up the common parent
+of two commits with
+
+               git-merge-base <commit1> <commit2>
+
+which will return you the commit they are both based on.  You should
+now look up the "tree" objects of those commits, which you can easily
+do with (for example)
+
+               git-cat-file commit <commitname> | head -1
+
+since the tree object information is always the first line in a commit
+object.
+
+Once you know the three trees you are going to merge (the one
+"original" tree, aka the common case, and the two "result" trees, aka
+the branches you want to merge), you do a "merge" read into the
+index. This will complain if it has to throw away your old index contents, so you should
+make sure that you've committed those - in fact you would normally
+always do a merge against your last commit (which should thus match
+what you have in your current index anyway).
+
+To do the merge, do
+
+               git-read-tree -m -u <origtree> <yourtree> <targettree>
+
+which will do all trivial merge operations for you directly in the
+index file, and you can just write the result out with
+`git-write-tree`.
+
+Historical note.  We did not have `-u` facility when this
+section was first written, so we used to warn that
+the merge is done in the index file, not in your
+working tree, and your working tree will not match your
+index after this step.
+This is no longer true.  The above command, thanks to `-u`
+option, updates your working tree with the merge results for
+paths that have been trivially merged.
+
+
+8) Merging multiple trees, continued
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sadly, many merges aren't trivial. If there are files that have
+been added.moved 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
+other tools before you can write out the result.
+
+You can examine such index state with `git-ls-files --unmerged`
+command.  An example:
+
+------------------------------------------------
+$ git-read-tree -m $orig HEAD $target
+$ git-ls-files --unmerged
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello.c
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello.c
+100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello.c
+------------------------------------------------
+
+Each line of the `git-ls-files --unmerged` output begins with
+the blob mode bits, blob SHA1, 'stage number', and the
+filename.  The 'stage number' is git's way to say which tree it
+came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD`
+tree, and stage3 `$target` tree.
+
+Earlier we said that trivial merges are done inside
+`git-read-tree -m`.  For example, if the file did not change
+from `$orig` to `HEAD` nor `$target`, or if the file changed
+from `$orig` to `HEAD` and `$orig` to `$target` the same way,
+obviously the final outcome is what is in `HEAD`.  What the
+above example shows is that file `hello.c` was changed from
+`$orig` to `HEAD` and `$orig` to `$target` in a different way.
+You could resolve this by running your favorite 3-way merge
+program, e.g.  `diff3` or `merge`, on the blob objects from
+these three stages yourself, like this:
+
+------------------------------------------------
+$ git-cat-file blob 263414f... >hello.c~1
+$ git-cat-file blob 06fa6a2... >hello.c~2
+$ git-cat-file blob cc44c73... >hello.c~3
+$ merge hello.c~2 hello.c~1 hello.c~3
+------------------------------------------------
+
+This would leave the merge result in `hello.c~2` file, along
+with conflict markers if there are conflicts.  After verifying
+the merge result makes sense, you can tell git what the final
+merge result for this file is by:
+
+       mv -f hello.c~2 hello.c
+       git-update-index hello.c
+
+When a path is in unmerged state, running `git-update-index` for
+that path tells git to mark the path resolved.
+
+The above is the description of a git merge at the lowest level,
+to help you understand what conceptually happens under the hood.
+In practice, nobody, not even git itself, uses three `git-cat-file`
+for this.  There is `git-merge-index` program that extracts the
+stages to temporary files and calls a "merge" script on it:
+
+       git-merge-index git-merge-one-file hello.c
+
+and that is what higher level `git merge -s resolve` is implemented
+with.
index 47505aa20a56b4dc29fad86d66e8c6638edf03e0..97cdb90cb4e5763a4d0cec1a0d01f5380b4e512f 100644 (file)
@@ -46,18 +46,18 @@ 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`.
 To start up, create a subdirectory for it, change into that
-subdirectory, and initialize the git infrastructure with `git-init-db`:
+subdirectory, and initialize the git infrastructure with `git-init`:
 
 ------------------------------------------------
 $ mkdir git-tutorial
 $ cd git-tutorial
-$ git-init-db
+$ git-init
 ------------------------------------------------
 
 to which git will reply
 
 ----------------
-defaulting to local storage area
+Initialized empty Git repository in .git/
 ----------------
 
 which is just git's way of saying that you haven't been doing anything
@@ -336,17 +336,9 @@ $ commit=$(echo 'Initial commit' | git-commit-tree $tree)
 $ git-update-ref HEAD $commit
 ------------------------------------------------
 
-which will say:
-
-----------------
-Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb
-----------------
-
-just to warn you about the fact that it created a totally new commit
-that is not related to anything else. Normally you do this only *once*
-for a project ever, and all later commits will be parented on top of an
-earlier commit, and you'll never see this "Committing initial tree"
-message ever again.
+In this case this creates a totally new commit that is not related to
+anything else. Normally you do this only *once* for a project ever, and
+all later commits will be parented on top of an earlier commit.
 
 Again, normally you'd never actually do this by hand. There is a
 helpful script called `git commit` that will do all of this for you. So
@@ -632,7 +624,7 @@ name for the state at that point.
 Copying repositories
 --------------------
 
-git repositories are normally totally self-sufficient and relocatable
+git repositories are normally totally self-sufficient and relocatable.
 Unlike CVS, for example, there is no separate notion of
 "repository" and "working tree". A git repository normally *is* the
 working tree, with the local git information hidden in the `.git`
@@ -914,18 +906,13 @@ 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:
 
 ----------------
-       Trying really trivial in-index merge...
-       fatal: Merge requires file-level merging
-       Nope.
-       ...
        Auto-merging hello 
        CONFLICT (content): Merge conflict in hello 
        Automatic merge failed; fix up by hand
 ----------------
 
-which is way too verbose, but it basically tells you that it failed the
-really trivial merge ("Simple merge") and did an "Automatic merge"
-instead, but that too failed due to conflicts in `hello`.
+It tells you that it did an "Automatic merge", which
+failed due to conflicts in `hello`.
 
 Not to worry. It left the (trivial) conflict in `hello` in the same form you
 should already be well used to if you've ever used CVS, so let's just
@@ -990,7 +977,7 @@ see more complex cases.
 Now, let's pretend you are the one who did all the work in
 `mybranch`, and the fruit of your hard work has finally been merged
 to the `master` branch. Let's go back to `mybranch`, and run
-resolve to get the "upstream changes" back to your branch.
+`git merge` to get the "upstream changes" back to your branch.
 
 ------------
 $ git checkout mybranch
@@ -1009,7 +996,7 @@ Fast forward
 ----------------
 
 Because your branch did not contain anything more than what are
-already merged into the `master` branch, the resolve operation did
+already merged into the `master` branch, the merge operation did
 not actually do a merge. Instead, it just updated the top of
 the tree of your branch to that of the `master` branch. This is
 often called 'fast forward' merge.
@@ -1112,11 +1099,11 @@ programs, which are 'commit walkers'; they outlived their
 usefulness when git Native and SSH transports were introduced,
 and not used by `git pull` or `git push` scripts.
 
-Once you fetch from the remote repository, you `resolve` that
+Once you fetch from the remote repository, you `merge` that
 with your current branch.
 
 However -- it's such a common thing to `fetch` and then
-immediately `resolve`, that it's called `git pull`, and you can
+immediately `merge`, that it's called `git pull`, and you can
 simply do
 
 ----------------
@@ -1131,52 +1118,32 @@ You could do without using any branches at all, by
 keeping as many local repositories as you would like to have
 branches, and merging between them with `git pull`, just like
 you merge between branches. The advantage of this approach is
-that it lets you keep set of files for each `branch` checked
+that it lets you keep set of files for each `branch` checked
 out and you may find it easier to switch back and forth if you
 juggle multiple lines of development simultaneously. Of
 course, you will pay the price of more disk usage to hold
 multiple working trees, but disk space is cheap these days.
 
-[NOTE]
-You could even pull from your own repository by
-giving '.' as <remote-repository> parameter to `git pull`.  This
-is useful when you want to merge a local branch (or more, if you
-are making an Octopus) into the current branch.
-
 It is likely that you will be pulling from the same remote
 repository from time to time. As a short hand, you can store
-the remote repository URL in a file under .git/remotes/
-directory, like this:
-
-------------------------------------------------
-$ mkdir -p .git/remotes/
-$ cat >.git/remotes/linus <<\EOF
-URL: http://www.kernel.org/pub/scm/git/git.git/
-EOF
-------------------------------------------------
-
-and use the filename to `git pull` instead of the full URL.
-The URL specified in such file can even be a prefix
-of a full URL, like this:
+the remote repository URL in the local repository's config file
+like this:
 
 ------------------------------------------------
-$ cat >.git/remotes/jgarzik <<\EOF
-URL: http://www.kernel.org/pub/scm/linux/git/jgarzik/
-EOF
+$ git config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/
 ------------------------------------------------
 
+and use the "linus" keyword with `git pull` instead of the full URL.
 
 Examples.
 
 . `git pull linus`
 . `git pull linus tag v0.99.1`
-. `git pull jgarzik/netdev-2.6.git/ e100`
 
 the above are equivalent to:
 
 . `git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD`
 . `git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1`
-. `git pull http://www.kernel.org/pub/.../jgarzik/netdev-2.6.git e100`
 
 
 How does the merge work?
@@ -1333,7 +1300,7 @@ differences since stage 2 (i.e. your version).
 Publishing your work
 --------------------
 
-So we can use somebody else's work from a remote repository; but
+So, we can use somebody else's work from a remote repository, but
 how can *you* prepare a repository to let other people pull from
 it?
 
@@ -1379,11 +1346,11 @@ $ mkdir my-git.git
 ------------
 
 Then, make that directory into a git repository by running
-`git init-db`, but this time, since its name is not the usual
+`git init`, but this time, since its name is not the usual
 `.git`, we do things slightly differently:
 
 ------------
-$ GIT_DIR=my-git.git git-init-db
+$ GIT_DIR=my-git.git git-init
 ------------
 
 Make sure this directory is available for others you want your
@@ -1502,8 +1469,8 @@ Working with Others
 Although git is a truly distributed system, it is often
 convenient to organize your project with an informal hierarchy
 of developers. Linux kernel development is run this way. There
-is a nice illustration (page 17, "Merges to Mainline") in Randy
-Dunlap's presentation (`http://tinyurl.com/a2jdg`).
+is a nice illustration (page 17, "Merges to Mainline") in
+link:http://tinyurl.com/a2jdg[Randy Dunlap's presentation].
 
 It should be stressed that this hierarchy is purely *informal*.
 There is nothing fundamental in git that enforces the "chain of
@@ -1519,7 +1486,7 @@ A recommended workflow for a "project lead" goes like this:
 +
 If other people are pulling from your repository over dumb
 transport protocols (HTTP), you need to keep this repository
-'dumb transport friendly'.  After `git init-db`,
+'dumb transport friendly'.  After `git init`,
 `$GIT_DIR/hooks/post-update` copied from the standard templates
 would contain a call to `git-update-server-info` but the
 `post-update` hook itself is disabled by default -- enable it
@@ -1554,7 +1521,8 @@ on that project and has an own "public repository" goes like this:
 
 1. Prepare your work repository, by `git clone` the public
    repository of the "project lead". The URL used for the
-   initial cloning is stored in `.git/remotes/origin`.
+   initial cloning is stored in the remote.origin.url
+   configuration variable.
 
 2. Prepare a public repository accessible to others, just like
    the "project lead" person does.
@@ -1594,14 +1562,15 @@ like this:
 1. Prepare your work repository, by `git clone` the public
    repository of the "project lead" (or a "subsystem
    maintainer", if you work on a subsystem). The URL used for
-   the initial cloning is stored in `.git/remotes/origin`.
+   the initial cloning is stored in the remote.origin.url
+   configuration variable.
 
 2. Do your work in your repository on 'master' branch.
 
 3. Run `git fetch origin` from the public repository of your
    upstream every once in a while. This does only the first
    half of `git pull` but does not merge. The head of the
-   public repository is stored in `.git/refs/heads/origin`.
+   public repository is stored in `.git/refs/remotes/origin/master`.
 
 4. Use `git cherry origin` to see which ones of your patches
    were accepted, and/or use `git rebase origin` to port your
@@ -1689,11 +1658,11 @@ $ git reset --hard master~2
 
 You can make sure 'git show-branch' matches the state before
 those two 'git merge' you just did.  Then, instead of running
-two 'git merge' commands in a row, you would pull these two
+two 'git merge' commands in a row, you would merge these two
 branch heads (this is known as 'making an Octopus'):
 
 ------------
-$ git pull . commit-fix diff-fix
+$ git merge commit-fix diff-fix
 $ git show-branch
 ! [commit-fix] Fix commit message normalization.
  ! [diff-fix] Fix rename detection.
@@ -1709,7 +1678,7 @@ $ git show-branch
 
 Note that you should not do Octopus because you can.  An octopus
 is a valid thing to do and often makes it easier to view the
-commit history if you are pulling more than two independent
+commit history if you are merging more than two independent
 changes at the same time.  However, if you have merge conflicts
 with any of the branches you are merging in and need to hand
 resolve, that is an indication that the development happened in
index 6812683a16852ea165dff7dd0582b53138e3f12c..764cc560b4aeeb7248d94f939c976634bd395823 100644 (file)
@@ -1,90 +1,87 @@
 git for CVS users
 =================
 
-So you're a CVS user. That's OK, it's a treatable condition.  The job of
-this document is to put you on the road to recovery, by helping you
-convert an existing cvs repository to git, and by showing you how to use a
-git repository in a cvs-like fashion.
+Git differs from CVS in that every working tree contains a repository with
+a full copy of the project history, and no repository is inherently more
+important than any other.  However, you can emulate the CVS model by
+designating a single shared repository which people can synchronize with;
+this document explains how to do that.
 
 Some basic familiarity with git is required.  This
 link:tutorial.html[tutorial introduction to git] should be sufficient.
 
-First, note some ways that git differs from CVS:
+Developing against a shared repository
+--------------------------------------
 
-  * Commits are atomic and project-wide, not per-file as in CVS.
-
-  * Offline work is supported: you can make multiple commits locally,
-    then submit them when you're ready.
-
-  * Branching is fast and easy.
+Suppose a shared repository is set up in /pub/repo.git on the host
+foo.com.  Then as an individual committer you can clone the shared
+repository over ssh with:
 
-  * Every working tree contains a repository with a full copy of the
-    project history, and no repository is inherently more important than
-    any other.  However, you can emulate the CVS model by designating a
-    single shared repository which people can synchronize with; see below
-    for details.
+------------------------------------------------
+$ git clone foo.com:/pub/repo.git/ my-project
+$ cd my-project
+------------------------------------------------
 
-Importing a CVS archive
------------------------
+and hack away.  The equivalent of `cvs update` is
 
-First, install version 2.1 or higher of cvsps from
-link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
-sure it is in your path.  The magic command line is then
+------------------------------------------------
+$ git pull origin
+------------------------------------------------
 
--------------------------------------------
-$ git cvsimport -v -d <cvsroot> -C <destination> <module>
--------------------------------------------
+which merges in any work that others might have done since the clone
+operation.  If there are uncommitted changes in your working tree, commit
+them first before running git pull.
 
-This puts a git archive of the named CVS module in the directory
-<destination>, which will be created if necessary.  The -v option makes
-the conversion script very chatty.
+[NOTE]
+================================
+The `pull` command knows where to get updates from because of certain
+configuration variables that were set by the first `git clone`
+command; see `git config -l` and the gitlink:git-config[1] man
+page for details.
+================================
 
-The import checks out from CVS every revision of every file.  Reportedly
-cvsimport can average some twenty revisions per second, so for a
-medium-sized project this should not take more than a couple of minutes.
-Larger projects or remote repositories may take longer.
+You can update the shared repository with your changes by first committing
+your changes, and then using the gitlink:git-push[1] command:
 
-The main trunk is stored in the git branch named `origin`, and additional
-CVS branches are stored in git branches with the same names.  The most
-recent version of the main trunk is also left checked out on the `master`
-branch, so you can start adding your own changes right away.
+------------------------------------------------
+$ git push origin master
+------------------------------------------------
 
-The import is incremental, so if you call it again next month it will
-fetch any CVS updates that have been made in the meantime.  For this to
-work, you must not modify the imported branches; instead, create new
-branches for your own changes, and merge in the imported branches as
-necessary.
+to "push" those commits to the shared repository.  If someone else has
+updated the repository more recently, `git push`, like `cvs commit`, will
+complain, in which case you must pull any changes before attempting the
+push again.
 
-Development Models
-------------------
+In the `git push` command above we specify the name of the remote branch
+to update (`master`).  If we leave that out, `git push` tries to update
+any branches in the remote repository that have the same name as a branch
+in the local repository.  So the last `push` can be done with either of:
 
-CVS users are accustomed to giving a group of developers commit access to
-a common repository.  In the next section we'll explain how to do this
-with git.  However, the distributed nature of git allows other development
-models, and you may want to first consider whether one of them might be a
-better fit for your project.
+------------
+$ git push origin
+$ git push foo.com:/pub/project.git/
+------------
 
-For example, you can choose a single person to maintain the project's
-primary public repository.  Other developers then clone this repository
-and each work in their own clone.  When they have a series of changes that
-they're happy with, they ask the maintainer to pull from the branch
-containing the changes.  The maintainer reviews their changes and pulls
-them into the primary repository, which other developers pull from as
-necessary to stay coordinated.  The Linux kernel and other projects use
-variants of this model.
+as long as the shared repository does not have any branches
+other than `master`.
 
-With a small group, developers may just pull changes from each other's
-repositories without the need for a central maintainer.
+Setting Up a Shared Repository
+------------------------------
 
-Emulating the CVS Development Model
------------------------------------
+We assume you have already created a git repository for your project,
+possibly created from scratch or from a tarball (see the
+link:tutorial.html[tutorial]), or imported from an already existing CVS
+repository (see the next section).
 
-Start with an ordinary git working directory containing the project, and
-remove the checked-out files, keeping just the bare .git directory:
+Assume your existing repo is at /home/alice/myproject.  Create a new "bare"
+repository (a repository without a working tree) and fetch your project into
+it:
 
 ------------------------------------------------
-$ mv project/.git /pub/repo.git
-$ rm -r project/
+$ mkdir /pub/my-repo.git
+$ cd /pub/my-repo.git
+$ git --bare init --shared
+$ git --bare fetch /home/alice/myproject master:master
 ------------------------------------------------
 
 Next, give every team member read/write access to this repository.  One
@@ -97,75 +94,42 @@ Put all the committers in the same group, and make the repository
 writable by that group:
 
 ------------------------------------------------
-$ chgrp -R $group repo.git
-$ find repo.git -mindepth 1 -type d |xargs chmod ug+rwx,g+s
-$ GIT_DIR=repo.git git repo-config core.sharedrepository true
+$ chgrp -R $group /pub/my-repo.git
 ------------------------------------------------
 
 Make sure committers have a umask of at most 027, so that the directories
 they create are writable and searchable by other group members.
 
-Suppose this repository is now set up in /pub/repo.git on the host
-foo.com.  Then as an individual committer you can clone the shared
-repository:
-
-------------------------------------------------
-$ git clone foo.com:/pub/repo.git/ my-project
-$ cd my-project
-------------------------------------------------
-
-and hack away.  The equivalent of `cvs update` is
-
-------------------------------------------------
-$ git pull origin
-------------------------------------------------
-
-which merges in any work that others might have done since the clone
-operation.
-
-[NOTE]
-================================
-The first `git clone` places the following in the
-`my-project/.git/remotes/origin` file, and that's why the previous step
-and the next step both work.
-------------
-URL: foo.com:/pub/project.git/ my-project
-Pull: master:origin
-------------
-================================
-
-You can update the shared repository with your changes using:
+Importing a CVS archive
+-----------------------
 
-------------------------------------------------
-$ git push origin master
-------------------------------------------------
+First, install version 2.1 or higher of cvsps from
+link:http://www.cobite.com/cvsps/[http://www.cobite.com/cvsps/] and make
+sure it is in your path.  Then cd to a checked out CVS working directory
+of the project you are interested in and run gitlink:git-cvsimport[1]:
 
-If someone else has updated the repository more recently, `git push`, like
-`cvs commit`, will complain, in which case you must pull any changes
-before attempting the push again.
+-------------------------------------------
+$ git cvsimport -C <destination>
+-------------------------------------------
 
-In the `git push` command above we specify the name of the remote branch
-to update (`master`).  If we leave that out, `git push` tries to update
-any branches in the remote repository that have the same name as a branch
-in the local repository.  So the last `push` can be done with either of:
+This puts a git archive of the named CVS module in the directory
+<destination>, which will be created if necessary.
 
-------------
-$ git push origin
-$ git push repo.shared.xz:/pub/scm/project.git/
-------------
+The import checks out from CVS every revision of every file.  Reportedly
+cvsimport can average some twenty revisions per second, so for a
+medium-sized project this should not take more than a couple of minutes.
+Larger projects or remote repositories may take longer.
 
-as long as the shared repository does not have any branches
-other than `master`.
+The main trunk is stored in the git branch named `origin`, and additional
+CVS branches are stored in git branches with the same names.  The most
+recent version of the main trunk is also left checked out on the `master`
+branch, so you can start adding your own changes right away.
 
-[NOTE]
-============
-Because of this behavior, if the shared repository and the developer's
-repository both have branches named `origin`, then a push like the above
-attempts to update the `origin` branch in the shared repository from the
-developer's `origin` branch.  The results may be unexpected, so it's
-usually best to remove any branch named `origin` from the shared
-repository.
-============
+The import is incremental, so if you call it again next month it will
+fetch any CVS updates that have been made in the meantime.  For this to
+work, you must not modify the imported branches; instead, create new
+branches for your own changes, and merge in the imported branches as
+necessary.
 
 Advanced Shared Repository Management
 -------------------------------------
@@ -178,127 +142,30 @@ You can enforce finer grained permissions using update hooks.  See
 link:howto/update-hook-example.txt[Controlling access to branches using
 update hooks].
 
-CVS annotate
-------------
+Providing CVS Access to a git Repository
+----------------------------------------
+
+It is also possible to provide true CVS access to a git repository, so
+that developers can still use CVS; see gitlink:git-cvsserver[1] for
+details.
+
+Alternative Development Models
+------------------------------
+
+CVS users are accustomed to giving a group of developers commit access to
+a common repository.  As we've seen, this is also possible with git.
+However, the distributed nature of git allows other development models,
+and you may want to first consider whether one of them might be a better
+fit for your project.
+
+For example, you can choose a single person to maintain the project's
+primary public repository.  Other developers then clone this repository
+and each work in their own clone.  When they have a series of changes that
+they're happy with, they ask the maintainer to pull from the branch
+containing the changes.  The maintainer reviews their changes and pulls
+them into the primary repository, which other developers pull from as
+necessary to stay coordinated.  The Linux kernel and other projects use
+variants of this model.
 
-So, something has gone wrong, and you don't know whom to blame, and
-you're an ex-CVS user and used to do "cvs annotate" to see who caused
-the breakage. You're looking for the "git annotate", and it's just
-claiming not to find such a script. You're annoyed.
-
-Yes, that's right.  Core git doesn't do "annotate", although it's
-technically possible, and there are at least two specialized scripts out
-there that can be used to get equivalent information (see the git
-mailing list archives for details). 
-
-git has a couple of alternatives, though, that you may find sufficient
-or even superior depending on your use.  One is called "git-whatchanged"
-(for obvious reasons) and the other one is called "pickaxe" ("a tool for
-the software archaeologist"). 
-
-The "git-whatchanged" script is a truly trivial script that can give you
-a good overview of what has changed in a file or a directory (or an
-arbitrary list of files or directories).  The "pickaxe" support is an
-additional layer that can be used to further specify exactly what you're
-looking for, if you already know the specific area that changed.
-
-Let's step back a bit and think about the reason why you would
-want to do "cvs annotate a-file.c" to begin with.
-
-You would use "cvs annotate" on a file when you have trouble
-with a function (or even a single "if" statement in a function)
-that happens to be defined in the file, which does not do what
-you want it to do.  And you would want to find out why it was
-written that way, because you are about to modify it to suit
-your needs, and at the same time you do not want to break its
-current callers.  For that, you are trying to find out why the
-original author did things that way in the original context.
-
-Many times, it may be enough to see the commit log messages of
-commits that touch the file in question, possibly along with the
-patches themselves, like this:
-
-       $ git-whatchanged -p a-file.c
-
-This will show log messages and patches for each commit that
-touches a-file.
-
-This, however, may not be very useful when this file has many
-modifications that are not related to the piece of code you are
-interested in.  You would see many log messages and patches that
-do not have anything to do with the piece of code you are
-interested in.  As an example, assuming that you have this piece
-of code that you are interested in in the HEAD version:
-
-       if (frotz) {
-               nitfol();
-       }
-
-you would use git-rev-list and git-diff-tree like this:
-
-       $ git-rev-list HEAD |
-         git-diff-tree --stdin -v -p -S'if (frotz) {
-               nitfol();
-       }'
-
-We have already talked about the "\--stdin" form of git-diff-tree
-command that reads the list of commits and compares each commit
-with its parents (otherwise you should go back and read the tutorial).
-The git-whatchanged command internally runs
-the equivalent of the above command, and can be used like this:
-
-       $ git-whatchanged -p -S'if (frotz) {
-               nitfol();
-       }'
-
-When the -S option is used, git-diff-tree command outputs
-differences between two commits only if one tree has the
-specified string in a file and the corresponding file in the
-other tree does not.  The above example looks for a commit that
-has the "if" statement in it in a file, but its parent commit
-does not have it in the same shape in the corresponding file (or
-the other way around, where the parent has it and the commit
-does not), and the differences between them are shown, along
-with the commit message (thanks to the -v flag).  It does not
-show anything for commits that do not touch this "if" statement.
-
-Also, in the original context, the same statement might have
-appeared at first in a different file and later the file was
-renamed to "a-file.c".  CVS annotate would not help you to go
-back across such a rename, but git would still help you in such
-a situation.  For that, you can give the -C flag to
-git-diff-tree, like this:
-
-       $ git-whatchanged -p -C -S'if (frotz) {
-               nitfol();
-       }'
-
-When the -C flag is used, file renames and copies are followed.
-So if the "if" statement in question happens to be in "a-file.c"
-in the current HEAD commit, even if the file was originally
-called "o-file.c" and then renamed in an earlier commit, or if
-the file was created by copying an existing "o-file.c" in an
-earlier commit, you will not lose track.  If the "if" statement
-did not change across such a rename or copy, then the commit that
-does rename or copy would not show in the output, and if the
-"if" statement was modified while the file was still called
-"o-file.c", it would find the commit that changed the statement
-when it was in "o-file.c".
-
-NOTE: The current version of "git-diff-tree -C" is not eager
-  enough to find copies, and it will miss the fact that a-file.c
-  was created by copying o-file.c unless o-file.c was somehow
-  changed in the same commit.
-
-You can use the --pickaxe-all flag in addition to the -S flag.
-This causes the differences from all the files contained in
-those two commits, not just the differences between the files
-that contain this changed "if" statement:
-
-       $ git-whatchanged -p -C -S'if (frotz) {
-               nitfol();
-       }' --pickaxe-all
-
-NOTE: This option is called "--pickaxe-all" because -S
-  option is internally called "pickaxe", a tool for software
-  archaeologists.
+With a small group, developers may just pull changes from each other's
+repositories without the need for a central maintainer.
index e4520e28e53661159454e02c703be772d43bbc09..378e72f38f37eef50135c3907ccd605652f4fd96 100644 (file)
@@ -65,62 +65,17 @@ Generating patches with -p
 
 When "git-diff-index", "git-diff-tree", or "git-diff-files" are run
 with a '-p' option, they do not produce the output described above;
-instead they produce a patch file.
+instead they produce a patch file.  You can customize the creation
+of such patches via the GIT_EXTERNAL_DIFF and the GIT_DIFF_OPTS
+environment variables.
 
-The patch generation can be customized at two levels.
-
-1. When the environment variable 'GIT_EXTERNAL_DIFF' is not set,
-   these commands internally invoke "diff" like this:
-
-      diff -L a/<path> -L b/<path> -pu <old> <new>
-+
-For added files, `/dev/null` is used for <old>.  For removed
-files, `/dev/null` is used for <new>
-+
-The "diff" formatting options can be customized via the
-environment variable 'GIT_DIFF_OPTS'.  For example, if you
-prefer context diff:
-
-      GIT_DIFF_OPTS=-c git-diff-index -p HEAD
-
-
-2. When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
-   program named by it is called, instead of the diff invocation
-   described above.
-+
-For a path that is added, removed, or modified,
-'GIT_EXTERNAL_DIFF' is called with 7 parameters:
-
-     path old-file old-hex old-mode new-file new-hex new-mode
-+
-where:
-
-     <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
-                     contents of <old|new>,
-     <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
-     <old|new>-mode:: are the octal representation of the file modes.
-
-+ 
-The file parameters can point at the user's working file
-(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
-when a new file is added), or a temporary file (e.g. `old-file` in the
-index).  'GIT_EXTERNAL_DIFF' should not worry about unlinking the
-temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
-
-For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
-parameter, <path>.
-
-
-git specific extension to diff format
--------------------------------------
-
-What -p option produces is slightly different from the
-traditional diff format.
+What the -p option produces is slightly different from the traditional
+diff format.
 
 1.   It is preceded with a "git diff" header, that looks like
      this:
 
-     diff --git a/file1 b/file2
+       diff --git a/file1 b/file2
 +
 The `a/` and `b/` filenames are the same unless rename/copy is
 involved.  Especially, even for a creation or a deletion,
@@ -204,7 +159,7 @@ or like this (when '--cc' option is used):
        deleted file mode <mode>,<mode>
 +
 The `mode <mode>,<mode>..<mode>` line appears only if at least one of
-the <mode> is diferent from the rest. Extended headers with
+the <mode> is different from the rest. Extended headers with
 information about detected contents movement (renames and
 copying detection) are designed to work with diff of two
 <tree-ish> and are not used by combined diff format.
index e112172ca57da75ce6c2dd447cc97c6aa2b6499e..019a39f2bf7253f3938d87a21a5467e37e33a2e0 100644 (file)
 --numstat::
        Similar to \--stat, but shows number of added and
        deleted lines in decimal notation and pathname without
-       abbreviation, to make it more machine friendly.
+       abbreviation, to make it more machine friendly.  For
+       binary files, outputs two `-` instead of saying
+       `0 0`.
+
+--shortstat::
+       Output only the last line of the --stat format containing total
+       number of modified files, as well as number of added and deleted
+       lines.
 
 --summary::
        Output a condensed summary of extended header information
        Turn off rename detection, even when the configuration
        file gives the default to do so.
 
+--check::
+       Warn if changes introduce trailing whitespace
+       or an indent that uses a space before a tab.
+
 --full-index::
        Instead of the first handful characters, show full
        object name of pre- and post-image blob on the "index"
 -a::
        Shorthand for "--text".
 
+--ignore-space-change::
+       Ignore changes in amount of white space.  This ignores white
+       space at line end, and consider all other sequences of one or
+       more white space characters to be equivalent.
+
+-b::
+       Shorthand for "--ignore-space-change".
+
+--ignore-all-space::
+       Ignore white space when comparing lines.  This ignores
+       difference even if one line has white space where the other
+       line has none.
+
+-w::
+       Shorthand for "--ignore-all-space".
+
 For more detailed explanation on these common options, see also
 link:diffcore.html[diffcore documentation].
index cb4e562004e58439a0055d9ed6a6bdab249dfcdc..34cd306bb1ee6ac73986cf3371114381b4165a0d 100644 (file)
@@ -6,8 +6,8 @@ June 2005
 Introduction
 ------------
 
-The diff commands git-diff-index, git-diff-files, git-diff-tree, and
-git-diff-stages can be told to manipulate differences they find in
+The diff commands git-diff-index, git-diff-files, and git-diff-tree
+can be told to manipulate differences they find in
 unconventional ways before showing diff(1) output.  The manipulation
 is collectively called "diffcore transformation".  This short note
 describes what they are and how to use them to produce diff outputs
@@ -30,9 +30,6 @@ files:
 
  - git-diff-tree compares contents of two "tree" objects;
 
- - git-diff-stages compares contents of blobs at two stages in an
-   unmerged index file.
-
 In all of these cases, the commands themselves compare
 corresponding paths in the two sets of files.  The result of
 comparison is passed from these commands to what is internally
diff --git a/Documentation/docbook-xsl.css b/Documentation/docbook-xsl.css
new file mode 100644 (file)
index 0000000..8821e30
--- /dev/null
@@ -0,0 +1,286 @@
+/*\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
index 9677671892700ae246dcfe36dc806a548fafe2d2..08c61b1f1ac6ca880c6c3a4f311f65486aabd23e 100644 (file)
@@ -25,35 +25,38 @@ Basic Repository[[Basic Repository]]
 
 Everybody uses these commands to maintain git repositories.
 
-  * gitlink:git-init-db[1] or gitlink:git-clone[1] to create a
+  * gitlink:git-init[1] or gitlink:git-clone[1] to create a
     new repository.
 
-  * gitlink:git-fsck-objects[1] to check the repository for errors.
+  * gitlink:git-fsck[1] to check the repository for errors.
 
   * gitlink:git-prune[1] to remove unused objects in the repository.
 
   * gitlink:git-repack[1] to pack loose objects for efficiency.
 
+  * gitlink:git-gc[1] to do common housekeeping tasks such as
+    repack and prune.
+
 Examples
 ~~~~~~~~
 
 Check health and remove cruft.::
 +
 ------------
-$ git fsck-objects <1>
-$ git prune
+$ git fsck <1>
 $ git count-objects <2>
 $ git repack <3>
-$ git prune <4>
+$ git gc <4>
 ------------
 +
-<1> running without "--full" is usually cheap and assures the
+<1> running without `\--full` is usually cheap and assures the
 repository health reasonably well.
 <2> check how many loose objects there are and how much
 disk space is wasted by not repacking.
-<3> without "-a" repacks incrementally.  repacking every 4-5MB
+<3> without `-a` repacks incrementally.  repacking every 4-5MB
 of loose objects accumulation may be a good rule of thumb.
-<4> after repack, prune removes the duplicate loose objects.
+<4> it is easier to use `git gc` than individual housekeeping commands
+such as `prune` and `repack`.  This runs `repack -a -d`.
 
 Repack a small project into single pack.::
 +
@@ -80,8 +83,7 @@ following commands.
   * gitlink:git-checkout[1] and gitlink:git-branch[1] to switch
     branches.
 
-  * gitlink:git-add[1] and gitlink:git-update-index[1] to manage
-    the index file.
+  * gitlink:git-add[1] to manage the index file.
 
   * gitlink:git-diff[1] and gitlink:git-status[1] to see what
     you are in the middle of doing.
@@ -91,8 +93,7 @@ following commands.
   * gitlink:git-reset[1] and gitlink:git-checkout[1] (with
     pathname parameters) to undo changes.
 
-  * gitlink:git-pull[1] with "." as the remote to merge between
-    local branches.
+  * gitlink:git-merge[1] to merge between local branches.
 
   * gitlink:git-rebase[1] to maintain topic branches.
 
@@ -101,12 +102,12 @@ following commands.
 Examples
 ~~~~~~~~
 
-Use a tarball as a starting point for a new repository:
+Use a tarball as a starting point for a new repository.::
 +
 ------------
 $ tar zxf frotz.tar.gz
 $ cd frotz
-$ git-init-db
+$ git-init
 $ git add . <1>
 $ git commit -m 'import of frotz source tree.'
 $ git tag v2.43 <2>
@@ -123,7 +124,7 @@ $ edit/compile/test
 $ git checkout -- curses/ux_audio_oss.c <2>
 $ git add curses/ux_audio_alsa.c <3>
 $ edit/compile/test
-$ git diff <4>
+$ git diff HEAD <4>
 $ git commit -a -s <5>
 $ edit/compile/test
 $ git reset --soft HEAD^ <6>
@@ -131,15 +132,15 @@ $ edit/compile/test
 $ git diff ORIG_HEAD <7>
 $ git commit -a -c ORIG_HEAD <8>
 $ git checkout master <9>
-$ git pull . alsa-audio <10>
+$ git merge alsa-audio <10>
 $ git log --since='3 days ago' <11>
 $ git log v2.43.. curses/ <12>
 ------------
 +
 <1> create a new topic branch.
-<2> revert your botched changes in "curses/ux_audio_oss.c".
+<2> revert your botched changes in `curses/ux_audio_oss.c`.
 <3> you need to tell git if you added a new file; removal and
-modification will be caught if you do "commit -a" later.
+modification will be caught if you do `git commit -a` later.
 <4> to see what changes you are committing.
 <5> commit everything as you have tested, with your sign-off.
 <6> take the last commit back, keeping what is in the working tree.
@@ -147,11 +148,12 @@ modification will be caught if you do "commit -a" later.
 <8> redo the commit undone in the previous step, using the message
 you originally wrote.
 <9> switch to the master branch.
-<10> merge a topic branch into your master branch
+<10> merge a topic branch into your master branch.
 <11> review commit logs; other forms to limit output can be
-combined and include --max-count=10 (show 10 commits), --until='2005-12-10'.
-<12> view only the changes that touch what's in curses/
-directory, since v2.43 tag.
+combined and include `\--max-count=10` (show 10 commits),
+`\--until=2005-12-10`, etc.
+<12> view only the changes that touch what's in `curses/`
+directory, since `v2.43` tag.
 
 
 Individual Developer (Participant)[[Individual Developer (Participant)]]
@@ -193,7 +195,7 @@ $ git fetch --tags <8>
 +
 <1> repeat as needed.
 <2> extract patches from your branch for e-mail submission.
-<3> "pull" fetches from "origin" by default and merges into the
+<3> `git pull` fetches from `origin` by default and merges into the
 current branch.
 <4> immediately after pulling, look at the changes done upstream
 since last time we checked, only in the
@@ -201,37 +203,41 @@ area we are interested in.
 <5> fetch from a specific branch from a specific repository and merge.
 <6> revert the pull.
 <7> garbage collect leftover objects from reverted pull.
-<8> from time to time, obtain official tags from the "origin"
-and store them under .git/refs/tags/.
+<8> from time to time, obtain official tags from the `origin`
+and store them under `.git/refs/tags/`.
 
 
 Push into another repository.::
 +
 ------------
-satellite$ git clone mothership:frotz/.git frotz <1>
+satellite$ git clone mothership:frotz frotz <1>
 satellite$ cd frotz
-satellite$ cat .git/remotes/origin <2>
-URL: mothership:frotz/.git
-Pull: master:origin
-satellite$ echo 'Push: master:satellite' >>.git/remotes/origin <3>
+satellite$ git config --get-regexp '^(remote|branch)\.' <2>
+remote.origin.url mothership:frotz
+remote.origin.fetch refs/heads/*:refs/remotes/origin/*
+branch.master.remote origin
+branch.master.merge refs/heads/master
+satellite$ git config remote.origin.push \
+           master:refs/remotes/satellite/master <3>
 satellite$ edit/compile/test/commit
 satellite$ git push origin <4>
 
 mothership$ cd frotz
 mothership$ git checkout master
-mothership$ git pull . satellite <5>
+mothership$ git merge satellite/master <5>
 ------------
 +
 <1> mothership machine has a frotz repository under your home
 directory; clone from it to start a repository on the satellite
 machine.
-<2> clone creates this file by default.  It arranges "git pull"
-to fetch and store the master branch head of mothership machine
-to local "origin" branch.
-<3> arrange "git push" to push local "master" branch to
-"satellite" branch of the mothership machine.
-<4> push will stash our work away on "satellite" branch on the
-mothership machine.  You could use this as a back-up method.
+<2> clone sets these configuration variables by default.
+It arranges `git pull` to fetch and store the branches of mothership
+machine to local `remotes/origin/*` tracking branches.
+<3> arrange `git push` to push local `master` branch to
+`remotes/satellite/master` branch of the mothership machine.
+<4> push will stash our work away on `remotes/satellite/master`
+tracking branch on the mothership machine.  You could use this as
+a back-up method.
 <5> on mothership machine, merge the work done on the satellite
 machine into the master branch.
 
@@ -247,7 +253,7 @@ $ git format-patch -k -m --stdout v2.6.14..private2.6.14 |
 +
 <1> create a private branch based on a well known (but somewhat behind)
 tag.
-<2> forward port all changes in private2.6.14 branch to master branch
+<2> forward port all changes in `private2.6.14` branch to `master` branch
 without a formal "merging".
 
 
@@ -284,13 +290,13 @@ $ mailx <3>
 & s 2 3 4 5 ./+to-apply
 & s 7 8 ./+hold-linus
 & q
-$ git checkout master
+$ git checkout -b topic/one master
 $ git am -3 -i -s -u ./+to-apply <4>
 $ compile/test
 $ git checkout -b hold/linus && git am -3 -i -s -u ./+hold-linus <5>
 $ git checkout topic/one && git rebase master <6>
-$ git checkout pu && git reset --hard master <7>
-$ git pull . topic/one topic/two && git pull . hold/linus <8>
+$ git checkout pu && git reset --hard next <7>
+$ git merge topic/one topic/two && git merge hold/linus <8>
 $ git checkout maint
 $ git cherry-pick master~4 <9>
 $ compile/test
@@ -307,29 +313,32 @@ they are.
 that are not quite ready.
 <4> apply them, interactively, with my sign-offs.
 <5> create topic branch as needed and apply, again with my
-sign-offs. 
+sign-offs.
 <6> rebase internal topic branch that has not been merged to the
 master, nor exposed as a part of a stable branch.
-<7> restart "pu" every time from the master.
+<7> restart `pu` every time from the next.
 <8> and bundle topic branches still cooking.
 <9> backport a critical fix.
 <10> create a signed tag.
 <11> make sure I did not accidentally rewind master beyond what I
-already pushed out.  "ko" shorthand points at the repository I have
+already pushed out.  `ko` shorthand points at the repository I have
 at kernel.org, and looks like this:
 +
 ------------
 $ cat .git/remotes/ko
 URL: kernel.org:/pub/scm/git/git.git
 Pull: master:refs/tags/ko-master
+Pull: next:refs/tags/ko-next
 Pull: maint:refs/tags/ko-maint
 Push: master
+Push: next
 Push: +pu
 Push: maint
 ------------
 +
-In the output from "git show-branch", "master" should have
-everything "ko-master" has.
+In the output from `git show-branch`, `master` should have
+everything `ko-master` has, and `next` should have
+everything `ko-next` has.
 
 <12> push out the bleeding edge.
 <13> push the tag out, too.
@@ -406,7 +415,7 @@ $ grep git /etc/shells <2>
 ------------
 +
 <1> log-in shell is set to /usr/bin/git-shell, which does not
-allow anything but "git push" and "git pull".  The users should
+allow anything but `git push` and `git pull`.  The users should
 get an ssh access to the machine.
 <2> in many distributions /etc/shells needs to list what is used
 as the login shell.
index 13f34d3ca28a402d56420f3bf5615837cf529e0a..5b4d184a73d013b8da11db3adb08905c0101bf88 100644 (file)
 -u, \--update-head-ok::
        By default `git-fetch` refuses to update the head which
        corresponds to the current branch.  This flag disables the
-       check.  Note that fetching into the current branch will not
-       update the index and working directory, so use it with care.
+       check.  This is purely for the internal use for `git-pull`
+       to communicate with `git-fetch`, and unless you are
+       implementing your own Porcelain you are not supposed to
+       use it.
+
+\--depth=<depth>::
+       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.
 
index 6342ea33e4a34f19ca04c79157d80cb230c15f5c..b73a99d61f57c7d532bbf8dc94bf2cb167d78058 100644 (file)
@@ -3,24 +3,45 @@ git-add(1)
 
 NAME
 ----
-git-add - Add files to the index file
+git-add - Add file contents to the changeset to be committed next
 
 SYNOPSIS
 --------
-'git-add' [-n] [-v] [--] <file>...
+'git-add' [-n] [-v] [-f] [--interactive | -i] [--] <file>...
 
 DESCRIPTION
 -----------
-A simple wrapper for git-update-index to add files to the index,
-for people used to do "cvs add".
+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 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 'git status' command can be used to obtain a summary of what is included
+for the next commit.
+
+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.
+
+Please see gitlink:git-commit[1] for alternative ways to add content to a
+commit.
 
-It only adds non-ignored files, to add ignored files use
-"git update-index --add".
 
 OPTIONS
 -------
 <file>...::
-       Files to add to the index (see gitlink:git-ls-files[1]).
+       Files to add content from.  Fileglobs (e.g. `*.c`) can
+       be given to add all matching files.  Also a
+       leading directory name (e.g. `dir` to add `dir/file1`
+       and `dir/file2`) can be given to add all files in the
+       directory, recursively.
 
 -n::
         Don't actually add the file(s), just show if they exist.
@@ -28,33 +49,25 @@ OPTIONS
 -v::
         Be verbose.
 
+-f::
+       Allow adding otherwise ignored files.
+
+\i, \--interactive::
+       Add modified contents in the working tree interactively to
+       the index.
+
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
        for command-line options).
 
 
-DISCUSSION
-----------
-
-The list of <file> given to the command is fed to `git-ls-files`
-command to list files that are not registered in the index and
-are not ignored/excluded by `$GIT_DIR/info/exclude` file or
-`.gitignore` file in each directory.  This means two things:
-
-. You can put the name of a directory on the command line, and
-  the command will add all files in it and its subdirectories;
-
-. Giving the name of a file that is already in index does not
-  run `git-update-index` on that path.
-
-
 EXAMPLES
 --------
 git-add Documentation/\\*.txt::
 
-       Adds all `\*.txt` files that are not in the index under
-       `Documentation` directory and its subdirectories.
+       Adds content from all `\*.txt` files under `Documentation`
+       directory and its subdirectories.
 +
 Note that the asterisk `\*` is quoted from the shell in this
 example; this lets the command to include the files from
@@ -62,15 +75,131 @@ subdirectories of `Documentation/` directory.
 
 git-add git-*.sh::
 
-       Adds all git-*.sh scripts that are not in the index.
+       Considers adding content from all git-*.sh scripts.
        Because this example lets shell expand the asterisk
        (i.e. you are listing the files explicitly), it does not
-       add `subdir/git-foo.sh` to the index.
+       consider `subdir/git-foo.sh`.
+
+Interactive mode
+----------------
+When the command enters the interactive mode, it shows the
+output of the 'status' subcommand, and then goes into its
+interactive command loop.
+
+The command loop shows the list of subcommands available, and
+gives a prompt "What now> ".  In general, when the prompt ends
+with a single '>', you can pick only one of the choices given
+and type return, like this:
+
+------------
+    *** Commands ***
+      1: status       2: update       3: revert       4: add untracked
+      5: patch        6: diff         7: quit         8: help
+    What now> 1
+------------
+
+You also could say "s" or "sta" or "status" above as long as the
+choice is unique.
+
+The main command loop has 6 subcommands (plus help and quit).
+
+status::
+
+   This shows the change between HEAD and index (i.e. what will be
+   committed if you say "git commit"), and between index and
+   working tree files (i.e. what you could stage further before
+   "git commit" using "git-add") for each path.  A sample output
+   looks like this:
++
+------------
+              staged     unstaged path
+     1:       binary      nothing foo.png
+     2:     +403/-35        +1/-1 git-add--interactive.perl
+------------
++
+It shows that foo.png has differences from HEAD (but that is
+binary so line count cannot be shown) and there is no
+difference between indexed copy and the working tree
+version (if the working tree version were also different,
+'binary' would have been shown in place of 'nothing').  The
+other file, git-add--interactive.perl, has 403 lines added
+and 35 lines deleted if you commit what is in the index, but
+working tree file has further modifications (one addition and
+one deletion).
+
+update::
+
+   This shows the status information and gives prompt
+   "Update>>".  When the prompt ends with double '>>', you can
+   make more than one selection, concatenated with whitespace or
+   comma.  Also you can say ranges.  E.g. "2-5 7,9" to choose
+   2,3,4,5,7,9 from the list.  You can say '*' to choose
+   everything.
++
+What you chose are then highlighted with '*',
+like this:
++
+------------
+           staged     unstaged path
+  1:       binary      nothing foo.png
+* 2:     +403/-35        +1/-1 git-add--interactive.perl
+------------
++
+To remove selection, prefix the input with `-`
+like this:
++
+------------
+Update>> -2
+------------
++
+After making the selection, answer with an empty line to stage the
+contents of working tree files for selected paths in the index.
+
+revert::
+
+  This has a very similar UI to 'update', and the staged
+  information for selected paths are reverted to that of the
+  HEAD version.  Reverting new paths makes them untracked.
+
+add untracked::
+
+  This has a very similar UI to 'update' and
+  'revert', and lets you add untracked paths to the index.
+
+patch::
+
+  This lets you choose one path out of 'status' like selection.
+  After choosing the path, it presents diff between the index
+  and the working tree file and asks you if you want to stage
+  the change of each hunk.  You can say:
+
+       y - add the change from that hunk to index
+       n - do not add the change from that hunk to index
+       a - add the change from that hunk and all the rest to index
+       d - do not the change from that hunk nor any of the rest to index
+       j - do not decide on this hunk now, and view the next
+           undecided hunk
+       J - do not decide on this hunk now, and view the next hunk
+       k - do not decide on this hunk now, and view the previous
+           undecided hunk
+       K - do not decide on this hunk now, and view the previous hunk
++
+After deciding the fate for all hunks, if there is any hunk
+that was chosen, the index is updated with the selected hunks.
+
+diff::
+
+  This lets you review what will be committed (i.e. between
+  HEAD and index).
+
 
 See Also
 --------
+gitlink:git-status[1]
 gitlink:git-rm[1]
-gitlink:git-ls-files[1]
+gitlink:git-mv[1]
+gitlink:git-commit[1]
+gitlink:git-update-index[1]
 
 Author
 ------
index 910457d3b388f2c3b1dbe25f5609e3584fb4f113..4fb1d844133ba8350361ee67d074935805a24156 100644 (file)
@@ -3,14 +3,15 @@ git-am(1)
 
 NAME
 ----
-git-am - Apply a series of patches in a mailbox
+git-am - Apply a series of patches from a mailbox
 
 
 SYNOPSIS
 --------
 [verse]
-'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
-         [--interactive] [--whitespace=<option>] <mbox>...
+'git-am' [--signoff] [--dotest=<dir>] [--utf8 | --no-utf8] [--binary] [--3way]
+         [--interactive] [--whitespace=<option>] [-C<n>] [-p<n>]
+        <mbox>...
 'git-am' [--skip | --resolved]
 
 DESCRIPTION
@@ -21,6 +22,10 @@ current branch.
 
 OPTIONS
 -------
+<mbox>...::
+       The list of mailbox files to read patches from. If you do not
+       supply this argument, reads from the standard input.
+
 --signoff::
        Add `Signed-off-by:` line to the commit message, using
        the committer identity of yourself.
@@ -29,8 +34,21 @@ OPTIONS
        Instead of `.dotest` directory, use <dir> as a working
        area to store extracted patches.
 
---utf8, --keep::
-       Pass `-u` and `-k` flags to `git-mailinfo` (see
+--keep::
+       Pass `-k` flag to `git-mailinfo` (see gitlink:git-mailinfo[1]).
+
+--utf8::
+       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 was optional in prior versions of git, but now it is the
+default.   You could use `--no-utf8` to override this.
+
+--no-utf8::
+       Do not pass `-u` flag to `git-mailinfo` (see
        gitlink:git-mailinfo[1]).
 
 --binary::
@@ -51,6 +69,10 @@ OPTIONS
        This flag is passed to the `git-apply` program that applies
        the patch.
 
+-C<n>, -p<n>::
+       These flag are passed to the `git-apply` program that applies
+       the patch.
+
 --interactive::
        Run interactively, just like git-applymbox.
 
index 2cc32d1c5efa0ba9c69ce59d481ca852045096c0..065ba1bf24828d8dfbb4495068621aae8e57e31a 100644 (file)
@@ -3,7 +3,7 @@ git-apply(1)
 
 NAME
 ----
-git-apply - Apply patch on a git index file and a work tree
+git-apply - Apply a patch on a git index file and a working tree
 
 
 SYNOPSIS
@@ -33,8 +33,9 @@ OPTIONS
 --numstat::
        Similar to \--stat, but shows number of added and
        deleted lines in decimal notation and pathname without
-       abbreviation, to make it more machine friendly.  Turns
-       off "apply".
+       abbreviation, to make it more machine friendly.  For
+       binary files, outputs two `-` instead of saying
+       `0 0`.  Turns off "apply".
 
 --summary::
        Instead of applying the patch, output a condensed
index f74c6a49b3c5d52ed662a83832c7d06915fac65b..95dc65a583552d4f7e2d1fa34c448832c66a3353 100644 (file)
@@ -42,13 +42,13 @@ OPTIONS
        and the current tree.
 
 -u::
-       By default, the commit log message, author name and
-       author email are taken from the e-mail without any
-       charset conversion, after minimally decoding MIME
-       transfer encoding.  This flag causes the resulting
-       commit to be encoded in utf-8 by transliterating them.
-       Note that the patch is always used as is without charset
-       conversion, even with this flag.
+       The commit log message, author name and author email are
+       taken from the e-mail, and after minimally decoding MIME
+       transfer encoding, re-coded in UTF-8 by transliterating
+       them.  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.
 
 -c .dotest/<num>::
        When the patch contained in an e-mail does not cleanly
index 2b1ff1454b48a6f45897a330ce4273b29f800b21..451434a757eab84d502a9529c571bc647e8b76d7 100644 (file)
@@ -12,6 +12,9 @@ SYNOPSIS
 
 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.
index 031fcd5190bf4baef7d87534ef5b6a39fe8985a9..493474b2eeabb2f7928b0e9f91cfd1ff64763a6d 100644 (file)
@@ -3,7 +3,7 @@ git-archive(1)
 
 NAME
 ----
-git-archive - Creates a archive of the files in the named tree
+git-archive - Creates an archive of files from a named tree
 
 
 SYNOPSIS
index ac4b4965a93fd5087896e8e807f6ad28db566df7..16ec7269b29c995fe051f74a37ba9d015d972006 100644 (file)
@@ -3,7 +3,7 @@ git-bisect(1)
 
 NAME
 ----
-git-bisect - Find the change that introduced a bug
+git-bisect - Find the change that introduced a bug by binary search
 
 
 SYNOPSIS
index bdfc6669285dc895a69fc0037246810bfa979de5..5c9888d0143ec8e6c8f7c6782139cde38a25ff17 100644 (file)
@@ -8,8 +8,8 @@ git-blame - Show what revision and author last modified each line of a file
 SYNOPSIS
 --------
 [verse]
-'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>]
-            [-M] [-C] [-C] [--since=<date>] [<rev>] [--] <file>
+'git-blame' [-c] [-l] [-t] [-f] [-n] [-p] [--incremental] [-L n,m] [-S <revs-file>]
+            [-M] [-C] [-C] [--since=<date>] [<rev> | --contents <file>] [--] <file>
 
 DESCRIPTION
 -----------
@@ -24,7 +24,7 @@ replaced; you need to use a tool such as gitlink:git-diff[1] or the "pickaxe"
 interface briefly mentioned in the following paragraph.
 
 Apart from supporting file annotation, git also supports searching the
-development history for when a code snippet occured in a change. This makes it
+development history for when a code snippet occurred in a change. This makes it
 possible to track when a code snippet was added to a file, moved or copied
 between files, and eventually deleted or replaced. It works by searching for
 a text string in the diff. A small example:
@@ -63,6 +63,17 @@ OPTIONS
 -p, --porcelain::
        Show in a format designed for machine consumption.
 
+--incremental::
+       Show the result incrementally in a format designed for
+       machine consumption.
+
+--contents <file>::
+       When <rev> is not specified, the command annotates the
+       changes starting backwards from the working tree copy.
+       This flag makes the command pretend as if the working
+       tree copy has the contents of he named file (specify
+       `-` to make the command read from the standard input).
+
 -M::
        Detect moving lines in the file as well.  When a commit
        moves a block of lines in a file (e.g. the original file
@@ -89,7 +100,7 @@ THE PORCELAIN FORMAT
 --------------------
 
 In this format, each line is output after a header; the
-header at the minumum has the first line which has:
+header at the minimum has the first line which has:
 
 - 40-byte SHA-1 of the commit the line is attributed to;
 - the line number of the line in the original file;
@@ -112,15 +123,18 @@ header, prefixed by a TAB. This is to allow adding more
 header elements later.
 
 
-SPECIFIYING RANGES
-------------------
+SPECIFYING RANGES
+-----------------
 
 Unlike `git-blame` and `git-annotate` in older git, the extent
 of annotation can be limited to both line ranges and revision
 ranges.  When you are interested in finding the origin for
-ll. 40-60 for file `foo`, you can use `-L` option like this:
+ll. 40-60 for file `foo`, you can use `-L` option like these
+(they mean the same thing -- both ask for 21 lines starting at
+line 40):
 
        git blame -L 40,60 foo
+       git blame -L 40,+21 foo
 
 Also you can use regular expression to specify the line range.
 
@@ -155,6 +169,47 @@ parents, using `commit{caret}!` notation:
        git blame -C -C -f $commit^! -- foo
 
 
+INCREMENTAL OUTPUT
+------------------
+
+When called with `--incremental` option, the command outputs the
+result as it is built.  The output generally will talk about
+lines touched by more recent commits first (i.e. the lines will
+be annotated out of order) and is meant to be used by
+interactive viewers.
+
+The output format is similar to the Porcelain format, but it
+does not contain the actual lines from the file that is being
+annotated.
+
+. Each blame entry always starts with a line of:
+
+       <40-byte hex sha1> <sourceline> <resultline> <num_lines>
++
+Line numbers count from 1.
+
+. The first time that commit shows up in the stream, it has various
+  other information about it printed out with a one-word tag at the
+  beginning of each line about that "extended commit info" (author,
+  email, committer, dates, summary etc).
+
+. Unlike Porcelain format, the filename information is always
+  given and terminates the entry:
+
+       "filename" <whitespace-quoted-filename-goes-here>
++
+and thus it's really quite easy to parse for some line- and word-oriented
+parser (which should be quite natural for most scripting languages).
++
+[NOTE]
+For people who do parsing: to make it more robust, just ignore any
+lines in between the first and last one ("<sha1>" and "filename" lines)
+where you don't recognize the tag-words (or care about that particular
+one) at the beginning of the "extended information" lines. That way, if
+there is ever added information (like the commit encoding or extended
+commit commentary), a blame viewer won't ever care.
+
+
 SEE ALSO
 --------
 gitlink:git-annotate[1]
index d43ef1dec4f27361af1a32983c53aeea15468fe9..aa1fdd402aa1771ab77af5e274a7e0452bbe8aa5 100644 (file)
@@ -3,28 +3,38 @@ git-branch(1)
 
 NAME
 ----
-git-branch - List, create, or delete branches.
+git-branch - List, create, or delete branches
 
 SYNOPSIS
 --------
 [verse]
-'git-branch' [-r]
+'git-branch' [--color | --no-color] [-r | -a] [-v [--abbrev=<length>]]
 'git-branch' [-l] [-f] <branchname> [<start-point>]
-'git-branch' (-d | -D) <branchname>...
+'git-branch' (-m | -M) [<oldbranch>] <newbranch>
+'git-branch' (-d | -D) [-r] <branchname>...
 
 DESCRIPTION
 -----------
-With no arguments given (or just `-r`) a list of available branches
+With no arguments given a list of existing branches
 will be shown, the current branch will be highlighted with an asterisk.
+Option `-r` causes the remote-tracking branches to be listed,
+and option `-a` shows both.
 
 In its second form, a new branch named <branchname> will be created.
 It will start out with a head equal to the one given as <start-point>.
 If no <start-point> is given, the branch will be created with a head
 equal to that of the currently checked out branch.
 
+With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
+If <oldbranch> had a corresponding reflog, it is renamed to match
+<newbranch>, and a reflog entry is created to remember the branch
+renaming. If <newbranch> exists, -M must be used to force the rename
+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.
+has a ref log then the ref log will also be deleted. Use -r together with -d
+to delete remote-tracking branches.
 
 
 OPTIONS
@@ -44,8 +54,31 @@ OPTIONS
        Force the creation of a new branch even if it means deleting
        a branch that already exists with the same name.
 
+-m::
+       Move/rename a branch and the corresponding reflog.
+
+-M::
+       Move/rename a branch even if the new branchname already exists.
+
+--color::
+       Color branches to highlight current, local, and remote branches.
+
+--no-color::
+       Turn off branch colors, even when the configuration file gives the
+       default to color output.
+
 -r::
-       List only the "remote" branches.
+       List or delete (if used with -d) the remote-tracking branches.
+
+-a::
+       List both remote-tracking branches and local branches.
+
+-v::
+       Show sha1 and commit subject line for each head.
+
+--abbrev=<length>::
+       Alter minimum display length for sha1 in output listing,
+       default value is 7.
 
 <branchname>::
        The name of the branch to create or delete.
@@ -58,6 +91,12 @@ OPTIONS
        be given as a branch name, a commit-id, or a tag.  If this option
        is omitted, the current branch is assumed.
 
+<oldbranch>::
+       The name of an existing branch to rename.
+
+<newbranch>::
+       The new name for an existing branch. The same restrictions as for
+       <branchname> applies.
 
 
 Examples
@@ -80,10 +119,12 @@ Delete unneeded branch::
 ------------
 $ git clone git://git.kernel.org/.../git.git my.git
 $ cd my.git
-$ git branch -D todo    <1>
+$ git branch -d -r todo html man   <1>
+$ git branch -D test               <2>
 ------------
 +
-<1> delete todo 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 todo branch.
 
 
index 5e9cbf875d72ae4a537467197ddde98dbec3cf3b..075c0d05efca85c2357e09aa640aa4b7f0305419 100644 (file)
@@ -3,7 +3,7 @@ git-cat-file(1)
 
 NAME
 ----
-git-cat-file - Provide content or type information for repository objects
+git-cat-file - Provide content or type/size information for repository objects
 
 
 SYNOPSIS
@@ -19,7 +19,9 @@ or '-s' is used to find the object size.
 OPTIONS
 -------
 <object>::
-       The sha1 identifier of the object.
+       The name of the object to show.
+       For a more complete list of ways to spell object names, see
+       "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
 
 -t::
        Instead of the content, show the object type identified by
index 765c173e1507056953e61159f32b9af6b64a3b15..6dd6db04bb91ae97472ce40d6b76ff8d409b99f7 100644 (file)
@@ -3,7 +3,7 @@ git-checkout-index(1)
 
 NAME
 ----
-git-checkout-index - Copy files from the index to the working directory
+git-checkout-index - Copy files from the index to the working tree
 
 
 SYNOPSIS
index fbdbadc74fbe558285323353b7aa006f3cfd559b..e4ffde4fdd78f4fe476951c9eeabebf143647eb3 100644 (file)
@@ -8,8 +8,8 @@ git-checkout - Checkout and switch to a branch
 SYNOPSIS
 --------
 [verse]
-'git-checkout' [-f] [-b <new_branch> [-l]] [-m] [<branch>]
-'git-checkout' [-m] [<branch>] <paths>...
+'git-checkout' [-q] [-f] [-b <new_branch> [-l]] [-m] [<branch>]
+'git-checkout' [<tree-ish>] <paths>...
 
 DESCRIPTION
 -----------
@@ -22,15 +22,20 @@ be created.
 
 When <paths> are given, this command does *not* switch
 branches.  It updates the named paths in the working tree from
-the index file (i.e. it runs `git-checkout-index -f -u`).  In
+the index file (i.e. it runs `git-checkout-index -f -u`), or a
+named commit.  In
 this case, `-f` and `-b` options are meaningless and giving
-either of them results in an error.  <branch> argument can be
-used to specify a specific tree-ish to update the index for the
-given paths before updating the working tree.
+either of them results in an error.  <tree-ish> argument can be
+used to specify a specific tree-ish (i.e. commit, tag or tree)
+to update the index for the given paths before updating the
+working tree.
 
 
 OPTIONS
 -------
+-q::
+       Quiet, supress feedback messages.
+
 -f::
        Force a re-read of everything.
 
@@ -63,7 +68,47 @@ and mark the resolved paths with `git update-index`.
 
 <branch>::
        Branch to checkout; may be any object ID that resolves to a
-       commit. Defaults to HEAD.
+       commit.  Defaults to HEAD.
++
+When this parameter names a non-branch (but still a valid commit object),
+your HEAD becomes 'detached'.
+
+
+Detached HEAD
+-------------
+
+It is sometimes useful to be able to 'checkout' a commit that is
+not at the tip of one of your branches.  The most obvious
+example is to check out the commit at a tagged official release
+point, like this:
+
+------------
+$ git checkout v2.6.18
+------------
+
+Earlier versions of git did not allow this and asked you to
+create a temporary branch using `-b` option, but starting from
+version 1.5.0, the above command 'detaches' your HEAD from the
+current branch and directly point at the commit named by the tag
+(`v2.6.18` in the above example).
+
+You can use usual git commands while in this state.  You can use
+`git-reset --hard $othercommit` to further move around, for
+example.  You can make changes and create a new commit on top of
+a detached HEAD.  You can even create a merge by using `git
+merge $othercommit`.
+
+The state you are in while your HEAD is detached is not recorded
+by any branch (which is natural --- you are not on any branch).
+What this means is that you can discard your temporary commits
+and merges by switching back to an existing branch (e.g. `git
+checkout master`), and a later `git prune` or `git gc` would
+garbage-collect them.  If you did this by mistake, you can ask
+the reflog for HEAD where you were, e.g.
+
+------------
+$ git log -g -2 HEAD
+------------
 
 
 EXAMPLES
index 875edb6b9f5ddc6d19b2de29cebbad5ebb7d37a0..3149d08da83f5cd7b92f13da47298791ce9a3737 100644 (file)
@@ -19,6 +19,8 @@ OPTIONS
 -------
 <commit>::
        Commit to cherry-pick.
+       For a more complete list of ways to spell commits, see
+       "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
 
 -e|--edit::
        With this option, `git-cherry-pick` will let you edit the commit
index 86060472ad88088bcb59df5eb9acce097f68cb83..707376f22cf2c849aae4cfca463d239b5f4fdf68 100644 (file)
@@ -3,7 +3,7 @@ git-clone(1)
 
 NAME
 ----
-git-clone - Clones a repository
+git-clone - Clones a repository into a new directory
 
 
 SYNOPSIS
@@ -11,25 +11,25 @@ SYNOPSIS
 [verse]
 'git-clone' [--template=<template_directory>] [-l [-s]] [-q] [-n] [--bare]
          [-o <name>] [-u <upload-pack>] [--reference <repository>]
-         [--use-separate-remote] <repository> [<directory>]
+         [--depth=<depth>] <repository> [<directory>]
 
 DESCRIPTION
 -----------
-Clones a repository into a newly created directory.  All remote
-branch heads are copied under `$GIT_DIR/refs/heads/`, except
-that the remote `master` is also copied to `origin` branch.
 
-In addition, `$GIT_DIR/remotes/origin` file is set up to have
-this line:
+Clones a repository into a newly created directory, creates
+remote-tracking branches for each branch in the cloned repository
+(visible using `git branch -r`), and creates and checks out an initial
+branch equal to the cloned repository's currently active branch.
 
-       Pull: master:origin
+After the clone, a plain `git fetch` without arguments will update
+all the remote-tracking branches, and a `git pull` without
+arguments will in addition merge the remote master branch into the
+current master branch, if any.
 
-This is to help the typical workflow of working off of the
-remote `master` branch.  Every time `git pull` without argument
-is run, the progress on the remote `master` branch is tracked by
-copying it into the local `origin` branch, and merged into the
-branch you are currently working on.  Remote branches other than
-`master` are also added there to be tracked.
+This default configuration is achieved by creating references to
+the remote branch heads under `$GIT_DIR/refs/remotes/origin` and
+by initializing `remote.origin.url` and `remote.origin.fetch`
+configuration variables.
 
 
 OPTIONS
@@ -71,17 +71,18 @@ OPTIONS
        Make a 'bare' GIT repository.  That is, instead of
        creating `<directory>` and placing the administrative
        files in `<directory>/.git`, make the `<directory>`
-       itself the `$GIT_DIR`. This implies `-n` option.  When
-       this option is used, neither the `origin` branch nor the
-       default `remotes/origin` file is created.
+       itself the `$GIT_DIR`. This obviously implies the `-n`
+       because there is nowhere to check out the working tree.
+       Also the branch heads at the remote are copied directly
+       to corresponding local branch heads, without mapping
+       them to `refs/remotes/origin/`.  When this option is
+       used, neither remote-tracking branches nor the related
+       configuration variables are created.
 
 --origin <name>::
 -o <name>::
-       Instead of using the branch name 'origin' to keep track
-       of the upstream repository, use <name> instead.  Note
-       that the shorthand name stored in `remotes/origin` is
-       not affected, but the local branch name to pull the
-       remote `master` branch into is.
+       Instead of using the remote name 'origin' to keep track
+       of the upstream repository, use <name> instead.
 
 --upload-pack <upload-pack>::
 -u <upload-pack>::
@@ -95,10 +96,14 @@ OPTIONS
        if unset the templates are taken from the installation
        defined default, typically `/usr/share/git-core/templates`.
 
---use-separate-remote::
-       Save remotes heads under `$GIT_DIR/remotes/origin/` instead
-       of `$GIT_DIR/refs/heads/`.  Only the master branch is saved
-       in the latter.
+--depth=<depth>::
+       Create a 'shallow' clone with a history truncated to the
+       specified number of revs.  A shallow repository has
+       number of limitations (you cannot clone or fetch from
+       it, nor push from nor into it), but is adequate if you
+       want to only look at near the tip of a large project
+       with a long history, and would want to send in a fixes
+       as patches.
 
 <repository>::
        The (possibly remote) repository to clone from.  It can
index 41d1a1c4b340b17a545feebe9877def8b009933f..cf25507f8f37194461e939bfa389ca00af05a5b1 100644 (file)
@@ -3,7 +3,7 @@ git-commit-tree(1)
 
 NAME
 ----
-git-commit-tree - Creates a new commit object
+git-commit-tree - Create a new commit object
 
 
 SYNOPSIS
@@ -12,6 +12,9 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
+This is usually not what an end user wants to run directly.  See
+gitlink:git-commit[1] instead.
+
 Creates a new commit object based on the provided tree object and
 emits the new commit object id on stdout. If no parent is given then
 it is considered to be an initial tree.
@@ -81,6 +84,11 @@ Your parents must have hated you!::
 Your sysadmin must hate you!::
     The password(5) name field is longer than a giant static buffer.
 
+Discussion
+----------
+
+include::i18n.txt[]
+
 See Also
 --------
 gitlink:git-write-tree[1]
index 517a86b238a91a1c853b5fbe331a5cc984e95878..2187eee4164038346ed7a3ae32f563aeb74f23f7 100644 (file)
@@ -3,36 +3,53 @@ git-commit(1)
 
 NAME
 ----
-git-commit - Record your changes
+git-commit - Record changes to the repository
 
 SYNOPSIS
 --------
 [verse]
-'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg>]
-          [--no-verify] [--amend] [-e] [--author <author>]
+'git-commit' [-a] [-s] [-v] [(-c | -C) <commit> | -F <file> | -m <msg> |
+           --amend] [--no-verify] [-e] [--author <author>]
           [--] [[-i | -o ]<file>...]
 
 DESCRIPTION
 -----------
-Updates the index file for given paths, or all modified files if
-'-a' is specified, and makes a commit object.  The command specified
-by either the VISUAL or EDITOR environment variables are used to edit
-the commit log message.
+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:
 
-Several environment variable are used during commits.  They are
-documented in gitlink:git-commit-tree[1].
+1. by using gitlink:git-add[1] to incrementally "add" changes to the
+   next commit 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;
+
+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;
+
+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.
+
+The gitlink:git-status[1] command can be used to obtain a
+summary of what is included by any of the above for the next
+commit by giving the same set of parameters you would give to
+this command.
+
+If you make a commit and then found a mistake immediately after
+that, you can recover from it with gitlink:git-reset[1].
 
-This command can run `commit-msg`, `pre-commit`, and
-`post-commit` hooks.  See link:hooks.html[hooks] for more
-information.
 
 OPTIONS
 -------
 -a|--all::
-       Update all paths in the index file.  This flag notices
-       files that have been modified and deleted, but new files
-       you have not told git about are not affected.
+       Tell the command to automatically stage files that have
+       been modified and deleted, but new files you have not
+       told git about are not affected.
 
 -c or -C <commit>::
        Take existing commit object, and reuse the log message
@@ -55,16 +72,9 @@ OPTIONS
 -s|--signoff::
        Add Signed-off-by line at the end of the commit message.
 
--v|--verify::
-       Look for suspicious lines the commit introduces, and
-       abort committing if there is one.  The definition of
-       'suspicious lines' is currently the lines that has
-       trailing whitespaces, and the lines whose indentation
-       has a SP character immediately followed by a TAB
-       character.  This is the default.
-
--n|--no-verify::
-       The opposite of `--verify`.
+--no-verify::
+       This option bypasses the pre-commit hook.
+       See also link:hooks.html[hooks].
 
 -e|--edit::
        The message taken from file with `-F`, command line with
@@ -95,69 +105,146 @@ but can be used to amend a merge commit.
 --
 
 -i|--include::
-       Instead of committing only the files specified on the
-       command line, update them in the index file and then
-       commit the whole index.  This is the traditional
-       behavior.
+       Before making a commit out of staged contents so far,
+       stage the contents of paths given on the command line
+       as well.  This is usually not what you want unless you
+       are concluding a conflicted merge.
 
--o|--only::
-       Commit only the files specified on the command line.
-       This format cannot be used during a merge, nor when the
-       index and the latest commit does not match on the
-       specified paths to avoid confusion.
+-q|--quiet::
+       Suppress commit summary message.
 
 \--::
        Do not interpret any more arguments as options.
 
 <file>...::
-       Files to be committed.  The meaning of these is
-       different between `--include` and `--only`.  Without
-       either, it defaults `--only` semantics.
-
-If you make a commit and then found a mistake immediately after
-that, you can recover from it with gitlink:git-reset[1].
+       When files are given on the command line, the command
+       commits the contents of the named files, without
+       recording the changes already staged.  The contents of
+       these files are also staged for the next commit on top
+       of what have been staged before.
 
 
-Discussion
+EXAMPLES
+--------
+When recording your own work, the contents of modified files in
+your working tree are temporarily stored to a staging area
+called the "index" with gitlink:git-add[1].  Removal
+of a file is staged with gitlink:git-rm[1].  After building the
+state to be committed incrementally with these commands, `git
+commit` (without any pathname parameter) is used to record what
+has been staged so far.  This is the most basic form of the
+command.  An example:
+
+------------
+$ edit hello.c
+$ git rm goodbye.c
+$ git add hello.c
+$ git commit
+------------
+
+Instead of staging files after each individual change, you can
+tell `git commit` to notice the changes to the files whose
+contents are tracked in
+your working tree and do corresponding `git add` and `git rm`
+for you.  That is, this example does the same as the earlier
+example if there is no other change in your working tree:
+
+------------
+$ edit hello.c
+$ rm goodbye.c
+$ git commit -a
+------------
+
+The command `git commit -a` first looks at your working tree,
+notices that you have modified hello.c and removed goodbye.c,
+and performs necessary `git add` and `git rm` for you.
+
+After staging changes to many files, you can alter the order the
+changes are recorded in, by giving pathnames to `git commit`.
+When pathnames are given, the command makes a commit that
+only records the changes made to the named paths:
+
+------------
+$ edit hello.c hello.h
+$ git add hello.c hello.h
+$ edit Makefile
+$ git commit Makefile
+------------
+
+This makes a commit that records the modification to `Makefile`.
+The changes staged for `hello.c` and `hello.h` are not included
+in the resulting commit.  However, their changes are not lost --
+they are still staged and merely held back.  After the above
+sequence, if you do:
+
+------------
+$ git commit
+------------
+
+this second commit would record the changes to `hello.c` and
+`hello.h` as expected.
+
+After a merge (initiated by either gitlink:git-merge[1] or
+gitlink:git-pull[1]) stops because of conflicts, cleanly merged
+paths are already staged to be committed for you, and paths that
+conflicted are left in unmerged state.  You would have to first
+check which paths are conflicting with gitlink:git-status[1]
+and after fixing them manually in your working tree, you would
+stage the result as usual with gitlink:git-add[1]:
+
+------------
+$ git status | grep unmerged
+unmerged: hello.c
+$ edit hello.c
+$ git add hello.c
+------------
+
+After resolving conflicts and staging the result, `git ls-files -u`
+would stop mentioning the conflicted path.  When you are done,
+run `git commit` to finally record the merge:
+
+------------
+$ git commit
+------------
+
+As with the case to record your own changes, you can use `-a`
+option to save typing.  One difference is that during a merge
+resolution, you cannot use `git commit` with pathnames to
+alter the order the changes are committed, because the merge
+should be recorded as a single commit.  In fact, the command
+refuses to run when given pathnames (but see `-i` option).
+
+
+DISCUSSION
 ----------
 
-`git commit` without _any_ parameter commits the tree structure
-recorded by the current index file.  This is a whole-tree commit
-even the command is invoked from a subdirectory.
-
-`git commit --include paths...` is equivalent to
-
-       git update-index --remove paths...
-       git commit
-
-That is, update the specified paths to the index and then commit
-the whole tree.
+Though not required, it's a good idea to begin the commit message
+with a single short (less than 50 character) line summarizing the
+change, followed by a blank line and then a more thorough description.
+Tools that turn commits into email, for example, use the first line
+on the Subject: line and the rest of the commit in the body.
 
-`git commit paths...` largely bypasses the index file and
-commits only the changes made to the specified paths.  It has
-however several safety valves to prevent confusion.
+include::i18n.txt[]
 
-. It refuses to run during a merge (i.e. when
-  `$GIT_DIR/MERGE_HEAD` exists), and reminds trained git users
-  that the traditional semantics now needs -i flag.
+ENVIRONMENT VARIABLES
+---------------------
+The command specified by either the VISUAL or EDITOR environment
+variables is used to edit the commit log message.
 
-. It refuses to run if named `paths...` are different in HEAD
-  and the index (ditto about reminding).  Added paths are OK.
-  This is because an earlier `git diff` (not `git diff HEAD`)
-  would have shown the differences since the last `git
-  update-index paths...` to the user, and an inexperienced user
-  may mistakenly think that the changes between the index and
-  the HEAD (i.e. earlier changes made before the last `git
-  update-index paths...` was done) are not being committed.
-
-. It reads HEAD commit into a temporary index file, updates the
-  specified `paths...` and makes a commit.  At the same time,
-  the real index file is also updated with the same `paths...`.
+HOOKS
+-----
+This command can run `commit-msg`, `pre-commit`, and
+`post-commit` hooks.  See link:hooks.html[hooks] for more
+information.
 
-`git commit --all` updates the index file with _all_ changes to
-the working tree, and makes a whole-tree commit, regardless of
-which subdirectory the command is invoked in.
 
+SEE ALSO
+--------
+gitlink:git-add[1],
+gitlink:git-rm[1],
+gitlink:git-mv[1],
+gitlink:git-merge[1],
+gitlink:git-commit-tree[1]
 
 Author
 ------
diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
new file mode 100644 (file)
index 0000000..6624484
--- /dev/null
@@ -0,0 +1,227 @@
+git-config(1)
+=============
+
+NAME
+----
+git-config - Get and set repository or global options
+
+
+SYNOPSIS
+--------
+[verse]
+'git-config' [--global] [type] name [value [value_regex]]
+'git-config' [--global] [type] --add name value
+'git-config' [--global] [type] --replace-all name [value [value_regex]]
+'git-config' [--global] [type] --get name [value_regex]
+'git-config' [--global] [type] --get-all name [value_regex]
+'git-config' [--global] [type] --unset name [value_regex]
+'git-config' [--global] [type] --unset-all name [value_regex]
+'git-config' [--global] -l | --list
+
+DESCRIPTION
+-----------
+You can query/set/replace/unset options with this command. The name is
+actually the section and the key separated by a dot, and the value will be
+escaped.
+
+Multiple lines can be added to an option by using the '--add' option.
+If you want to update or unset an option which can occur on multiple
+lines, a POSIX regexp `value_regex` needs to be given.  Only the
+existing values that match the regexp are updated or unset.  If
+you want to handle the lines that do *not* match the regex, just
+prepend a single exclamation mark in front (see 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). If no type specifier is passed,
+no checks or transformations are performed on the value.
+
+This command will fail if:
+
+. The .git/config file is invalid,
+. Can not write to .git/config,
+. no section was provided,
+. the section or key is invalid,
+. you try to unset an option which does not exist,
+. you try to unset/set an option for which multiple lines match, or
+. you use --global option without $HOME being properly set.
+
+
+OPTIONS
+-------
+
+--replace-all::
+       Default behavior is to replace at most one line. This replaces
+       all lines matching the key (and optionally the value_regex).
+
+--add::
+       Adds a new line to the option without altering any existing
+       values.  This is the same as providing '^$' as the value_regex.
+
+--get::
+       Get the value for a given key (optionally filtered by a regex
+       matching the value). Returns error code 1 if the key was not
+       found and error code 2 if multiple key values were found.
+
+--get-all::
+       Like get, but does not fail if the number of values for the key
+       is not exactly one.
+
+--get-regexp::
+       Like --get-all, but interprets the name as a regular expression.
+
+--global::
+       Use global ~/.gitconfig file rather than the repository .git/config.
+
+--unset::
+       Remove the line matching the key from config file.
+
+--unset-all::
+       Remove all matching lines from config file.
+
+-l, --list::
+       List all variables set in config file.
+
+--bool::
+       git-config will ensure that the output is "true" or "false"
+
+--int::
+       git-config will ensure that the output is a simple
+       decimal number.  An optional value suffix of 'k', 'm', or 'g'
+       in the config file will cause the value to be multiplied
+       by 1024, 1048576, or 1073741824 prior to output.
+
+
+ENVIRONMENT
+-----------
+
+GIT_CONFIG::
+       Take the configuration from the given file instead of .git/config.
+       Using the "--global" option forces this to ~/.gitconfig.
+
+GIT_CONFIG_LOCAL::
+       Currently the same as $GIT_CONFIG; when Git will support global
+       configuration files, this will cause it to take the configuration
+       from the global configuration file in addition to the given file.
+
+
+EXAMPLE
+-------
+
+Given a .git/config like this:
+
+       #
+       # This is the config file, and
+       # a '#' or ';' character indicates
+       # a comment
+       #
+
+       ; core variables
+       [core]
+               ; Don't trust file modes
+               filemode = false
+
+       ; Our diff algorithm
+       [diff]
+               external = "/usr/local/bin/gnu-diff -u"
+               renames = true
+
+       ; 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
+
+------------
+% git config core.filemode true
+------------
+
+The hypothetical proxy command entries actually have a postfix to discern
+what URL they apply to. Here is how to change the entry for kernel.org
+to "ssh".
+
+------------
+% git config core.gitproxy '"ssh" for kernel.org' 'for kernel.org$'
+------------
+
+This makes sure that only the key/value pair for kernel.org is replaced.
+
+To delete the entry for renames, do
+
+------------
+% git config --unset diff.renames
+------------
+
+If you want to delete an entry for a multivar (like core.gitproxy above),
+you have to provide a regex matching the value of exactly one line.
+
+To query the value for a given key, do
+
+------------
+% git config --get core.filemode
+------------
+
+or
+
+------------
+% git config core.filemode
+------------
+
+or, to query a multivar:
+
+------------
+% git config --get core.gitproxy "for kernel.org$"
+------------
+
+If you want to know all the values for a multivar, do:
+
+------------
+% git config --get-all core.gitproxy
+------------
+
+If you like to live dangerous, you can replace *all* core.gitproxy by a
+new one with
+
+------------
+% git config --replace-all core.gitproxy ssh
+------------
+
+However, if you really only want to replace the line for the default proxy,
+i.e. the one without a "for ..." postfix, do something like this:
+
+------------
+% git config core.gitproxy ssh '! for '
+------------
+
+To actually match only values with an exclamation mark, you have to
+
+------------
+% git config section.key value '[!]'
+------------
+
+To add a new proxy, without altering any of the existing ones, use
+
+------------
+% git config core.gitproxy '"proxy" for example.com'
+------------
+
+
+include::config.txt[]
+
+
+Author
+------
+Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 198ce77a8a541ba33855bf5236b4791e2b395557..91c8c92c76648ed5bfdaab6b991e7cb1ce519733 100644 (file)
@@ -3,7 +3,7 @@ git-count-objects(1)
 
 NAME
 ----
-git-count-objects - Reports on unpacked objects
+git-count-objects - Count unpacked number of objects and their disk consumption
 
 SYNOPSIS
 --------
@@ -20,8 +20,8 @@ OPTIONS
 -v::
        In addition to the number of loose objects and disk
        space consumed, it reports the number of in-pack
-       objects, and number of objects that can be removed by
-       running `git-prune-packed`.
+       objects, number of packs, and number of objects that can be
+       removed by running `git-prune-packed`.
 
 
 Author
index 092d0d6730294a5ce701522575bc1f8d91755a1d..27d531b888ef0468022e2ec0780729acc5151147 100644 (file)
@@ -3,12 +3,12 @@ git-cvsexportcommit(1)
 
 NAME
 ----
-git-cvsexportcommit - Export a commit to a CVS checkout
+git-cvsexportcommit - Export a single commit to a CVS checkout
 
 
 SYNOPSIS
 --------
-'git-cvsexportcommit' [-h] [-v] [-c] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
+'git-cvsexportcommit' [-h] [-v] [-c] [-P] [-p] [-a] [-f] [-m msgprefix] [PARENTCOMMIT] COMMITID
 
 
 DESCRIPTION
@@ -46,6 +46,9 @@ OPTIONS
 -f::
        Force the merge even if the files are not up to date.
 
+-P::
+       Force the parent commit, even if it is not a direct parent.
+
 -m::
        Prepend the commit message with the provided prefix. 
        Useful for patch series and the like.
index d21d66bfebe368c034afdabf79e8f143813e619d..f5450de74a220c2c3c842835749c20f34cfef1f2 100644 (file)
@@ -3,7 +3,7 @@ git-cvsimport(1)
 
 NAME
 ----
-git-cvsimport - Import a CVS repository into git
+git-cvsimport - Salvage your data out of another SCM people love to hate
 
 
 SYNOPSIS
@@ -90,15 +90,28 @@ If you need to pass multiple options, separate them with a comma.
        Print a short usage message and exit.
 
 -z <fuzz>::
-        Pass the timestamp fuzz factor to cvsps.
+       Pass the timestamp fuzz factor to cvsps, in seconds. If unset,
+       cvsps defaults to 300s.
 
 -s <subst>::
        Substitute the character "/" in branch names with <subst>
 
 -A <author-conv-file>::
-       CVS by default uses the unix username when writing its
+       CVS by default uses the Unix username when writing its
        commit logs. Using this option and an author-conv-file
        in this format
+
+-a::
+       Import all commits, including recent ones. cvsimport by default
+       skips commits that have a timestamp less than 10 minutes ago.
+
+-S <regex>::
+       Skip paths matching the regex.
+
+-L <limit>::
+       Limit the number of commits imported. Workaround for cases where
+       cvsimport leaks memory.
+
 +
 ---------
        exon=Andreas Ericsson <ae@op5.se>
index 993adc7c5adf63f144d7ce257a670363abe18322..9ddab712031cc2b71ccfc83dd5b6b475b5bfc809 100644 (file)
@@ -131,14 +131,14 @@ Giving these options is an error when used with `--inetd`; use
 the facility of inet daemon to achieve the same before spawning
 `git-daemon` if needed.
 
---enable-service, --disable-service::
+--enable=service, --disable=service::
        Enable/disable the service site-wide per default.  Note
        that a service disabled site-wide can still be enabled
        per repository if it is marked overridable and the
        repository enables the service with an configuration
        item.
 
---allow-override, --forbid-override::
+--allow-override=service, --forbid-override=service::
        Allow/forbid overriding the site-wide default with per
        repository configuration.  By default, all the services
        are overridable.
index 2700f35bdb6ec7aae9520eca0d21bcf5089bc750..47a583d3a605d95fb860e6ea7142cebf374ec5aa 100644 (file)
@@ -14,8 +14,8 @@ DESCRIPTION
 -----------
 The command finds the most recent tag that is reachable from a
 commit, and if the commit itself is pointed at by the tag, shows
-the tag.  Otherwise, it suffixes the tag name with abbreviated
-object name of the commit.
+the tag.  Otherwise, it suffixes the tag name with the number of
+additional commits and the abbreviated object name of the commit.
 
 
 OPTIONS
@@ -35,6 +35,16 @@ OPTIONS
        Instead of using the default 8 hexadecimal digits as the
        abbreviated object name, use <n> digits.
 
+--candidates=<n>::
+       Instead of considering only the 10 most recent tags as
+       candidates to describe the input committish consider
+       up to <n> candidates.  Increasing <n> above 10 will take
+       slightly longer but may produce a more accurate result.
+
+--debug::
+       Verbosely display information about the searching strategy
+       being employed to standard error.  The tag name will still
+       be printed to standard out.
 
 EXAMPLES
 --------
@@ -42,12 +52,18 @@ EXAMPLES
 With something like git.git current tree, I get:
 
        [torvalds@g5 git]$ git-describe parent
-       v1.0.4-g2414721b
+       v1.0.4-14-g2414721
 
 i.e. the current head of my "parent" branch is based on v1.0.4,
-but since it has a few commits on top of that, it has added the
-git hash of the thing to the end: "-g" + 8-char shorthand for
-the commit `2414721b194453f058079d897d13c4e377f92dc6`.
+but since it has a handful commits on top of that,
+describe has added the number of additional commits ("14") and
+an abbreviated object name for the commit itself ("2414721")
+at the end.
+
+The number of additional commits is the number
+of commits which would be displayed by "git log v1.0.4..parent".
+The hash suffix is "-g" + 7-char abbreviation for the tip commit
+of parent (which was `2414721b194453f058079d897d13c4e377f92dc6`).
 
 Doing a "git-describe" on a tag-name will just show the tag name:
 
@@ -58,16 +74,43 @@ With --all, the command can use branch heads as references, so
 the output shows the reference path as well:
 
        [torvalds@g5 git]$ git describe --all --abbrev=4 v1.0.5^2
-       tags/v1.0.0-g975b
+       tags/v1.0.0-21-g975b
 
        [torvalds@g5 git]$ git describe --all HEAD^
-       heads/lt/describe-g975b
+       heads/lt/describe-7-g975b
+
+With --abbrev set to 0, the command can be used to find the
+closest tagname without any suffix:
+
+       [torvalds@g5 git]$ git describe --abbrev=0 v1.0.5^2
+       tags/v1.0.0
+
+SEARCH STRATEGY
+---------------
+
+For each committish supplied "git describe" will first look for
+a tag which tags exactly that commit.  Annotated tags will always
+be preferred over lightweight tags, and tags with newer dates will
+always be preferred over tags with older dates.  If an exact match
+is found, its name will be output and searching will stop.
+
+If an exact match was not found "git describe" will walk back
+through the commit history to locate an ancestor commit which
+has been tagged.  The ancestor's tag will be output along with an
+abbreviation of the input committish's SHA1.
+
+If multiple tags were found during the walk then the tag which
+has the fewest commits different from the input committish will be
+selected and output.  Here fewest commits different is defined as
+the number of commits which would be shown by "git log tag..input"
+will be the smallest number of commits possible.
 
 
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>, but somewhat
-butchered by Junio C Hamano <junkio@cox.net>
+butchered by Junio C Hamano <junkio@cox.net>.  Later significantly
+updated by Shawn Pearce <spearce@spearce.org>.
 
 Documentation
 --------------
diff --git a/Documentation/git-diff-stages.txt b/Documentation/git-diff-stages.txt
deleted file mode 100644 (file)
index 3273918..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-git-diff-stages(1)
-==================
-
-NAME
-----
-git-diff-stages - Compares content and mode of blobs between stages in an unmerged index file
-
-
-SYNOPSIS
---------
-'git-diff-stages' [<common diff options>] <stage1> <stage2> [<path>...]
-
-DESCRIPTION
------------
-Compares the content and mode of the blobs in two stages in an
-unmerged index file.
-
-OPTIONS
--------
-include::diff-options.txt[]
-
-<stage1>,<stage2>::
-       The stage number to be compared.
-
-Output format
--------------
-include::diff-format.txt[]
-
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by Junio C Hamano.
-
-GIT
----
-Part of the gitlink:git[7] suite
index f7e8ff2968d6c443220ea9e9667255ebdd29b602..5d6e9dc751aef6e30582b399ae09f81c043e6de1 100644 (file)
@@ -73,10 +73,7 @@ separated with a single space are given.
        This flag causes "git-diff-tree --stdin" to also show
        the commit message before the differences.
 
---pretty[=(raw|medium|short)]::
-       This is used to control "pretty printing" format of the
-       commit message.  Without "=<style>", it defaults to
-       medium.
+include::pretty-formats.txt[]
 
 --no-commit-id::
        git-diff-tree outputs a line with the commit ID when
index 228c4d95bd41d40bbb32cfcb6b9a7dc648280fa5..6a098df26b1c1639f6ac88233b5642a2be9039fa 100644 (file)
@@ -8,36 +8,57 @@ git-diff - Show changes between commits, commit and working tree, etc
 
 SYNOPSIS
 --------
-'git-diff' [ --diff-options ] <tree-ish>{0,2} [<path>...]
+'git-diff' [ --diff-options ] <commit>{0,2} [--] [<path>...]
 
 DESCRIPTION
 -----------
 Show changes between two trees, a tree and the working tree, a
 tree and the index file, or the index file and the working tree.
-The combination of what is compared with what is determined by
-the number of trees given to the command.
 
-* When no <tree-ish> is given, the working tree and the index
-  file are compared, using `git-diff-files`.
+'git-diff' [--options] [--] [<path>...]::
 
-* When one <tree-ish> is given, the working tree and the named
-  tree are compared, using `git-diff-index`.  The option
-  `--cached` can be given to compare the index file and
-  the named tree.
+       This form is to view the changes you made relative to
+       the index (staging area for the next commit).  In other
+       words, the differences are what you _could_ tell git to
+       further add to the index but you still haven't.  You can
+       stage these changes by using gitlink:git-add[1].
+
+'git-diff' [--options] --cached [<commit>] [--] [<path>...]::
+
+       This form is to view the changes you staged for the next
+       commit relative to the named <commit>.  Typically you
+       would want comparison with the latest commit, so if you
+       do not give <commit>, it defaults to HEAD.
+
+'git-diff' [--options] <commit> [--] [<path>...]::
+
+       This form is to view the changes you have in your
+       working tree relative to the named <commit>.  You can
+       use HEAD to compare it with the latest commit, or a
+       branch name to compare with the tip of a different
+       branch.
+
+'git-diff' [--options] <commit> <commit> [--] [<path>...]::
+
+       This form is to view the changes between two <commit>,
+       for example, tips of two branches.
+
+Just in case if you are doing something exotic, it should be
+noted that all of the <commit> in the above description can be
+any <tree-ish>.
+
+For a more complete list of ways to spell <commit>, see
+"SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
 
-* When two <tree-ish>s are given, these two trees are compared
-  using `git-diff-tree`.
 
 OPTIONS
 -------
---diff-options::
-       '--diff-options' are passed to the `git-diff-files`,
-       `git-diff-index`, and `git-diff-tree` commands.  See the
-       documentation for these commands for description.
+include::diff-options.txt[]
 
 <path>...::
-       The <path> arguments are also passed to `git-diff-\*`
-       commands.
+       The <paths> parameters, when given, are used to limit
+       the diff to the named paths (you can give directory
+       names and get diff for all files under them).
 
 
 EXAMPLES
@@ -51,7 +72,7 @@ $ git diff --cached   <2>
 $ git diff HEAD       <3>
 ------------
 +
-<1> changes in the working tree since your last git-update-index.
+<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
diff --git a/Documentation/git-fast-import.txt b/Documentation/git-fast-import.txt
new file mode 100644 (file)
index 0000000..445f6b8
--- /dev/null
@@ -0,0 +1,901 @@
+git-fast-import(1)
+==================
+
+NAME
+----
+git-fast-import - Backend for fast Git data importers.
+
+
+SYNOPSIS
+--------
+frontend | 'git-fast-import' [options]
+
+DESCRIPTION
+-----------
+This program is usually not what the end user wants to run directly.
+Most end users want to use one of the existing frontend programs,
+which parses a specific type of foreign source and feeds the contents
+stored there to git-fast-import.
+
+fast-import reads a mixed command/data stream from standard input and
+writes one or more packfiles directly into the current repository.
+When EOF is received on standard input, fast import writes out
+updated branch and tag refs, fully updating the current repository
+with the newly imported data.
+
+The fast-import backend itself can import into an empty repository (one that
+has already been initialized by gitlink:git-init[1]) or incrementally
+update an existing populated repository.  Whether or not incremental
+imports are supported from a particular foreign source depends on
+the frontend program in use.
+
+
+OPTIONS
+-------
+--date-format=<fmt>::
+       Specify the type of dates the frontend will supply to
+       fast-import within `author`, `committer` and `tagger` commands.
+       See ``Date Formats'' below for details about which formats
+       are supported, and their syntax.
+
+--force::
+       Force updating modified existing branches, even if doing
+       so would cause commits to be lost (as the new commit does
+       not contain the old commit).
+
+--max-pack-size=<n>::
+       Maximum size of each output packfile, expressed in MiB.
+       The default is 4096 (4 GiB) as that is the maximum allowed
+       packfile size (due to file format limitations). Some
+       importers may wish to lower this, such as to ensure the
+       resulting packfiles fit on CDs.
+
+--depth=<n>::
+       Maximum delta depth, for blob and tree deltification.
+       Default is 10.
+
+--active-branches=<n>::
+       Maximum number of branches to maintain active at once.
+       See ``Memory Utilization'' below for details.  Default is 5.
+
+--export-marks=<file>::
+       Dumps the internal marks table to <file> when complete.
+       Marks are written one per line as `:markid SHA-1`.
+       Frontends can use this file to validate imports after they
+       have been completed.
+
+--export-pack-edges=<file>::
+       After creating a packfile, print a line of data to
+       <file> listing the filename of the packfile and the last
+       commit on each branch that was written to that packfile.
+       This information may be useful after importing projects
+       whose total object set exceeds the 4 GiB packfile limit,
+       as these commits can be used as edge points during calls
+       to gitlink:git-pack-objects[1].
+
+--quiet::
+       Disable all non-fatal output, making fast-import silent when it
+       is successful.  This option disables the output shown by
+       \--stats.
+
+--stats::
+       Display some basic statistics about the objects fast-import has
+       created, the packfiles they were stored into, and the
+       memory used by fast-import during this run.  Showing this output
+       is currently the default, but can be disabled with \--quiet.
+
+
+Performance
+-----------
+The design of fast-import allows it to import large projects in a minimum
+amount of memory usage and processing time.  Assuming the frontend
+is able to keep up with fast-import and feed it a constant stream of data,
+import times for projects holding 10+ years of history and containing
+100,000+ individual commits are generally completed in just 1-2
+hours on quite modest (~$2,000 USD) hardware.
+
+Most bottlenecks appear to be in foreign source data access (the
+source just cannot extract revisions fast enough) or disk IO (fast-import
+writes as fast as the disk will take the data).  Imports will run
+faster if the source data is stored on a different drive than the
+destination Git repository (due to less IO contention).
+
+
+Development Cost
+----------------
+A typical frontend for fast-import tends to weigh in at approximately 200
+lines of Perl/Python/Ruby code.  Most developers have been able to
+create working importers in just a couple of hours, even though it
+is their first exposure to fast-import, and sometimes even to Git.  This is
+an ideal situation, given that most conversion tools are throw-away
+(use once, and never look back).
+
+
+Parallel Operation
+------------------
+Like `git-push` or `git-fetch`, imports handled by fast-import are safe to
+run alongside parallel `git repack -a -d` or `git gc` invocations,
+or any other Git operation (including `git prune`, as loose objects
+are never used by fast-import).
+
+fast-import does not lock the branch or tag refs it is actively importing.
+After the import, during its ref update phase, fast-import tests each
+existing branch ref to verify the update will be a fast-forward
+update (the commit stored in the ref is contained in the new
+history of the commit to be written).  If the update is not a
+fast-forward update, fast-import will skip updating that ref and instead
+prints a warning message.  fast-import will always attempt to update all
+branch refs, and does not stop on the first failure.
+
+Branch updates can be forced with \--force, but its recommended that
+this only be used on an otherwise quiet repository.  Using \--force
+is not necessary for an initial import into an empty repository.
+
+
+Technical Discussion
+--------------------
+fast-import tracks a set of branches in memory.  Any branch can be created
+or modified at any point during the import process by sending a
+`commit` command on the input stream.  This design allows a frontend
+program to process an unlimited number of branches simultaneously,
+generating commits in the order they are available from the source
+data.  It also simplifies the frontend programs considerably.
+
+fast-import does not use or alter the current working directory, or any
+file within it.  (It does however update the current Git repository,
+as referenced by `GIT_DIR`.)  Therefore an import frontend may use
+the working directory for its own purposes, such as extracting file
+revisions from the foreign source.  This ignorance of the working
+directory also allows fast-import to run very quickly, as it does not
+need to perform any costly file update operations when switching
+between branches.
+
+Input Format
+------------
+With the exception of raw file data (which Git does not interpret)
+the fast-import input format is text (ASCII) based.  This text based
+format simplifies development and debugging of frontend programs,
+especially when a higher level language such as Perl, Python or
+Ruby is being used.
+
+fast-import is very strict about its input.  Where we say SP below we mean
+*exactly* one space.  Likewise LF means one (and only one) linefeed.
+Supplying additional whitespace characters will cause unexpected
+results, such as branch names or file names with leading or trailing
+spaces in their name, or early termination of fast-import when it encounters
+unexpected input.
+
+Date Formats
+~~~~~~~~~~~~
+The following date formats are supported.  A frontend should select
+the format it will use for this import by passing the format name
+in the \--date-format=<fmt> command line option.
+
+`raw`::
+       This is the Git native format and is `<time> SP <offutc>`.
+       It is also fast-import's default format, if \--date-format was
+       not specified.
++
+The time of the event is specified by `<time>` as the number of
+seconds since the UNIX epoch (midnight, Jan 1, 1970, UTC) and is
+written as an ASCII decimal integer.
++
+The local offset is specified by `<offutc>` as a positive or negative
+offset from UTC.  For example EST (which is 5 hours behind UTC)
+would be expressed in `<tz>` by ``-0500'' while UTC is ``+0000''.
+The local offset does not affect `<time>`; it is used only as an
+advisement to help formatting routines display the timestamp.
++
+If the local offset is not available in the source material, use
+``+0000'', or the most common local offset.  For example many
+organizations have a CVS repository which has only ever been accessed
+by users who are located in the same location and timezone.  In this
+case a reasonable offset from UTC could be assumed.
++
+Unlike the `rfc2822` format, this format is very strict.  Any
+variation in formatting will cause fast-import to reject the value.
+
+`rfc2822`::
+       This is the standard email format as described by RFC 2822.
++
+An example value is ``Tue Feb 6 11:22:18 2007 -0500''.  The Git
+parser is accurate, but a little on the lenient side.  It is the
+same parser used by gitlink:git-am[1] when applying patches
+received from email.
++
+Some malformed strings may be accepted as valid dates.  In some of
+these cases Git will still be able to obtain the correct date from
+the malformed string.  There are also some types of malformed
+strings which Git will parse wrong, and yet consider valid.
+Seriously malformed strings will be rejected.
++
+Unlike the `raw` format above, the timezone/UTC offset information
+contained in an RFC 2822 date string is used to adjust the date
+value to UTC prior to storage.  Therefore it is important that
+this information be as accurate as possible.
++
+If the source material uses RFC 2822 style dates,
+the frontend should let fast-import handle the parsing and conversion
+(rather than attempting to do it itself) as the Git parser has
+been well tested in the wild.
++
+Frontends should prefer the `raw` format if the source material
+already uses UNIX-epoch format, can be coaxed to give dates in that
+format, or its format is easiliy convertible to it, as there is no
+ambiguity in parsing.
+
+`now`::
+       Always use the current time and timezone.  The literal
+       `now` must always be supplied for `<when>`.
++
+This is a toy format.  The current time and timezone of this system
+is always copied into the identity string at the time it is being
+created by fast-import.  There is no way to specify a different time or
+timezone.
++
+This particular format is supplied as its short to implement and
+may be useful to a process that wants to create a new commit
+right now, without needing to use a working directory or
+gitlink:git-update-index[1].
++
+If separate `author` and `committer` commands are used in a `commit`
+the timestamps may not match, as the system clock will be polled
+twice (once for each command).  The only way to ensure that both
+author and committer identity information has the same timestamp
+is to omit `author` (thus copying from `committer`) or to use a
+date format other than `now`.
+
+Commands
+~~~~~~~~
+fast-import accepts several commands to update the current repository
+and control the current import process.  More detailed discussion
+(with examples) of each command follows later.
+
+`commit`::
+       Creates a new branch or updates an existing branch by
+       creating a new commit and updating the branch to point at
+       the newly created commit.
+
+`tag`::
+       Creates an annotated tag object from an existing commit or
+       branch.  Lightweight tags are not supported by this command,
+       as they are not recommended for recording meaningful points
+       in time.
+
+`reset`::
+       Reset an existing branch (or a new branch) to a specific
+       revision.  This command must be used to change a branch to
+       a specific revision without making a commit on it.
+
+`blob`::
+       Convert raw file data into a blob, for future use in a
+       `commit` command.  This command is optional and is not
+       needed to perform an import.
+
+`checkpoint`::
+       Forces fast-import to close the current packfile, generate its
+       unique SHA-1 checksum and index, and start a new packfile.
+       This command is optional and is not needed to perform
+       an import.
+
+`commit`
+~~~~~~~~
+Create or update a branch with a new commit, recording one logical
+change to the project.
+
+....
+       'commit' SP <ref> LF
+       mark?
+       ('author' SP <name> SP LT <email> GT SP <when> LF)?
+       'committer' SP <name> SP LT <email> GT SP <when> LF
+       data
+       ('from' SP <committish> LF)?
+       ('merge' SP <committish> LF)?
+       (filemodify | filedelete | filedeleteall)*
+       LF
+....
+
+where `<ref>` is the name of the branch to make the commit on.
+Typically branch names are prefixed with `refs/heads/` in
+Git, so importing the CVS branch symbol `RELENG-1_0` would use
+`refs/heads/RELENG-1_0` for the value of `<ref>`.  The value of
+`<ref>` must be a valid refname in Git.  As `LF` is not valid in
+a Git refname, no quoting or escaping syntax is supported here.
+
+A `mark` command may optionally appear, requesting fast-import to save a
+reference to the newly created commit for future use by the frontend
+(see below for format).  It is very common for frontends to mark
+every commit they create, thereby allowing future branch creation
+from any imported commit.
+
+The `data` command following `committer` must supply the commit
+message (see below for `data` command syntax).  To import an empty
+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
+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`
+wipes the branch clean (see below).
+
+`author`
+^^^^^^^^
+An `author` command may optionally appear, if the author information
+might differ from the committer information.  If `author` is omitted
+then fast-import will automatically use the committer's information for
+the author portion of the commit.  See below for a description of
+the fields in `author`, as they are identical to `committer`.
+
+`committer`
+^^^^^^^^^^^
+The `committer` command indicates who made this commit, and when
+they made it.
+
+Here `<name>` is the person's display name (for example
+``Com M Itter'') and `<email>` is the person's email address
+(``cm@example.com'').  `LT` and `GT` are the literal less-than (\x3c)
+and greater-than (\x3e) symbols.  These are required to delimit
+the email address from the other fields in the line.  Note that
+`<name>` is free-form and may contain any sequence of bytes, except
+`LT` and `LF`.  It is typically UTF-8 encoded.
+
+The time of the change is specified by `<when>` using the date format
+that was selected by the \--date-format=<fmt> command line option.
+See ``Date Formats'' above for the set of supported formats, and
+their syntax.
+
+`from`
+^^^^^^
+The `from` command is used to specify the commit to initialize
+this branch from.  This revision will be the first ancestor of the
+new commit.
+
+Omitting the `from` command in the first commit of a new branch
+will cause fast-import to create that commit with no ancestor. This
+tends to be desired only for the initial commit of a project.
+Omitting the `from` command on existing branches is usually desired,
+as the current commit on that branch is automatically assumed to
+be the first ancestor of the new commit.
+
+As `LF` is not valid in a Git refname or SHA-1 expression, no
+quoting or escaping syntax is supported within `<committish>`.
+
+Here `<committish>` is any of the following:
+
+* The name of an existing branch already in fast-import's internal branch
+  table.  If fast-import doesn't know the name, its treated as a SHA-1
+  expression.
+
+* A mark reference, `:<idnum>`, where `<idnum>` is the mark number.
++
+The reason fast-import uses `:` to denote a mark reference is this character
+is not legal in a Git branch name.  The leading `:` makes it easy
+to distingush between the mark 42 (`:42`) and the branch 42 (`42`
+or `refs/heads/42`), or an abbreviated SHA-1 which happened to
+consist only of base-10 digits.
++
+Marks must be declared (via `mark`) before they can be used.
+
+* A complete 40 byte or abbreviated commit SHA-1 in hex.
+
+* Any valid Git SHA-1 expression that resolves to a commit.  See
+  ``SPECIFYING REVISIONS'' in gitlink:git-rev-parse[1] for details.
+
+The special case of restarting an incremental import from the
+current branch value should be written as:
+----
+       from refs/heads/branch^0
+----
+The `{caret}0` suffix is necessary as fast-import does not permit a branch to
+start from itself, and the branch is created in memory before the
+`from` command is even read from the input.  Adding `{caret}0` will force
+fast-import to resolve the commit through Git's revision parsing library,
+rather than its internal branch table, thereby loading in the
+existing value of the branch.
+
+`merge`
+^^^^^^^
+Includes one additional ancestor commit, and makes the current
+commit a merge commit.  An unlimited number of `merge` commands per
+commit are permitted by fast-import, thereby establishing an n-way merge.
+However Git's other tools never create commits with more than 15
+additional ancestors (forming a 16-way merge).  For this reason
+it is suggested that frontends do not use more than 15 `merge`
+commands per commit.
+
+Here `<committish>` is any of the commit specification expressions
+also accepted by `from` (see above).
+
+`filemodify`
+^^^^^^^^^^^^
+Included in a `commit` command to add a new file or change the
+content of an existing file.  This command has two different means
+of specifying the content of the file.
+
+External data format::
+       The data content for the file was already supplied by a prior
+       `blob` command.  The frontend just needs to connect it.
++
+....
+       'M' SP <mode> SP <dataref> SP <path> LF
+....
++
+Here `<dataref>` can be either a mark reference (`:<idnum>`)
+set by a prior `blob` command, or a full 40-byte SHA-1 of an
+existing Git blob object.
+
+Inline data format::
+       The data content for the file has not been supplied yet.
+       The frontend wants to supply it as part of this modify
+       command.
++
+....
+       'M' SP <mode> SP 'inline' SP <path> LF
+       data
+....
++
+See below for a detailed description of the `data` command.
+
+In both formats `<mode>` is the type of file entry, specified
+in octal.  Git only supports the following modes:
+
+* `100644` or `644`: A normal (not-executable) file.  The majority
+  of files in most projects use this mode.  If in doubt, this is
+  what you want.
+* `100755` or `755`: A normal, but executable, file.
+* `120000`: A symlink, the content of the file will be the link target.
+
+In both formats `<path>` is the complete path of the file to be added
+(if not already existing) or modified (if already existing).
+
+A `<path>` string must use UNIX-style directory seperators (forward
+slash `/`), may contain any byte other than `LF`, and must not
+start with double quote (`"`).
+
+If an `LF` or double quote must be encoded into `<path>` shell-style
+quoting should be used, e.g. `"path/with\n and \" in it"`.
+
+The value of `<path>` must be in canoncial form. That is it must not:
+
+* contain an empty directory component (e.g. `foo//bar` is invalid),
+* end with a directory seperator (e.g. `foo/` is invalid),
+* start with a directory seperator (e.g. `/foo` is invalid),
+* contain the special component `.` or `..` (e.g. `foo/./bar` and
+  `foo/../bar` are invalid).
+
+It is recommended that `<path>` always be encoded using UTF-8.
+
+`filedelete`
+^^^^^^^^^^^^
+Included in a `commit` command to remove a file from the branch.
+If the file removal makes its directory empty, the directory will
+be automatically removed too.  This cascades up the tree until the
+first non-empty directory or the root is reached.
+
+....
+       'D' SP <path> LF
+....
+
+here `<path>` is the complete path of the file to be removed.
+See `filemodify` above for a detailed description of `<path>`.
+
+`filedeleteall`
+^^^^^^^^^^^^^^^
+Included in a `commit` command to remove all files (and also all
+directories) from the branch.  This command resets the internal
+branch structure to have no files in it, allowing the frontend
+to subsequently add all interesting files from scratch.
+
+....
+       'deleteall' LF
+....
+
+This command is extremely useful if the frontend does not know
+(or does not care to know) what files are currently on the branch,
+and therefore cannot generate the proper `filedelete` commands to
+update the content.
+
+Issuing a `filedeleteall` followed by the needed `filemodify`
+commands to set the correct content will produce the same results
+as sending only the needed `filemodify` and `filedelete` commands.
+The `filedeleteall` approach may however require fast-import to use slightly
+more memory per active branch (less than 1 MiB for even most large
+projects); so frontends that can easily obtain only the affected
+paths for a commit are encouraged to do so.
+
+`mark`
+~~~~~~
+Arranges for fast-import to save a reference to the current object, allowing
+the frontend to recall this object at a future point in time, without
+knowing its SHA-1.  Here the current object is the object creation
+command the `mark` command appears within.  This can be `commit`,
+`tag`, and `blob`, but `commit` is the most common usage.
+
+....
+       'mark' SP ':' <idnum> LF
+....
+
+where `<idnum>` is the number assigned by the frontend to this mark.
+The value of `<idnum>` is expressed as an ASCII decimal integer.
+The value 0 is reserved and cannot be used as
+a mark.  Only values greater than or equal to 1 may be used as marks.
+
+New marks are created automatically.  Existing marks can be moved
+to another object simply by reusing the same `<idnum>` in another
+`mark` command.
+
+`tag`
+~~~~~
+Creates an annotated tag referring to a specific commit.  To create
+lightweight (non-annotated) tags see the `reset` command below.
+
+....
+       'tag' SP <name> LF
+       'from' SP <committish> LF
+       'tagger' SP <name> SP LT <email> GT SP <when> LF
+       data
+       LF
+....
+
+where `<name>` is the name of the tag to create.
+
+Tag names are automatically prefixed with `refs/tags/` when stored
+in Git, so importing the CVS branch symbol `RELENG-1_0-FINAL` would
+use just `RELENG-1_0-FINAL` for `<name>`, and fast-import will write the
+corresponding ref as `refs/tags/RELENG-1_0-FINAL`.
+
+The value of `<name>` must be a valid refname in Git and therefore
+may contain forward slashes.  As `LF` is not valid in a Git refname,
+no quoting or escaping syntax is supported here.
+
+The `from` command is the same as in the `commit` command; see
+above for details.
+
+The `tagger` command uses the same format as `committer` within
+`commit`; again see above for details.
+
+The `data` command following `tagger` must supply the annotated tag
+message (see below for `data` command syntax).  To import an empty
+tag message use a 0 length data.  Tag 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.
+
+Signing annotated tags during import from within fast-import is not
+supported.  Trying to include your own PGP/GPG signature is not
+recommended, as the frontend does not (easily) have access to the
+complete set of bytes which normally goes into such a signature.
+If signing is required, create lightweight tags from within fast-import with
+`reset`, then create the annotated versions of those tags offline
+with the standard gitlink:git-tag[1] process.
+
+`reset`
+~~~~~~~
+Creates (or recreates) the named branch, optionally starting from
+a specific revision.  The reset command allows a frontend to issue
+a new `from` command for an existing branch, or to create a new
+branch from an existing commit without creating a new commit.
+
+....
+       'reset' SP <ref> LF
+       ('from' SP <committish> LF)?
+       LF
+....
+
+For a detailed description of `<ref>` and `<committish>` see above
+under `commit` and `from`.
+
+The `reset` command can also be used to create lightweight
+(non-annotated) tags.  For example:
+
+====
+       reset refs/tags/938
+       from :938
+====
+
+would create the lightweight tag `refs/tags/938` referring to
+whatever commit mark `:938` references.
+
+`blob`
+~~~~~~
+Requests writing one file revision to the packfile.  The revision
+is not connected to any commit; this connection must be formed in
+a subsequent `commit` command by referencing the blob through an
+assigned mark.
+
+....
+       'blob' LF
+       mark?
+       data
+....
+
+The mark command is optional here as some frontends have chosen
+to generate the Git SHA-1 for the blob on their own, and feed that
+directly to `commit`.  This is typically more work than its worth
+however, as marks are inexpensive to store and easy to use.
+
+`data`
+~~~~~~
+Supplies raw data (for use as blob/file content, commit messages, or
+annotated tag messages) to fast-import.  Data can be supplied using an exact
+byte count or delimited with a terminating line.  Real frontends
+intended for production-quality conversions should always use the
+exact byte count format, as it is more robust and performs better.
+The delimited format is intended primarily for testing fast-import.
+
+Exact byte count format::
+       The frontend must specify the number of bytes of data.
++
+....
+       'data' SP <count> LF
+       <raw> LF
+....
++
+where `<count>` is the exact number of bytes appearing within
+`<raw>`.  The value of `<count>` is expressed as an ASCII decimal
+integer.  The `LF` on either side of `<raw>` is not
+included in `<count>` and will not be included in the imported data.
+
+Delimited format::
+       A delimiter string is used to mark the end of the data.
+       fast-import will compute the length by searching for the delimiter.
+       This format is primarly useful for testing and is not
+       recommended for real data.
++
+....
+       'data' SP '<<' <delim> LF
+       <raw> LF
+       <delim> LF
+....
++
+where `<delim>` is the chosen delimiter string.  The string `<delim>`
+must not appear on a line by itself within `<raw>`, as otherwise
+fast-import will think the data ends earlier than it really does.  The `LF`
+immediately trailing `<raw>` is part of `<raw>`.  This is one of
+the limitations of the delimited format, it is impossible to supply
+a data chunk which does not have an LF as its last byte.
+
+`checkpoint`
+~~~~~~~~~~~~
+Forces fast-import to close the current packfile, start a new one, and to
+save out all current branch refs, tags and marks.
+
+....
+       'checkpoint' LF
+       LF
+....
+
+Note that fast-import automatically switches packfiles when the current
+packfile reaches \--max-pack-size, or 4 GiB, whichever limit is
+smaller.  During an automatic packfile switch fast-import does not update
+the branch refs, tags or marks.
+
+As a `checkpoint` can require a significant amount of CPU time and
+disk IO (to compute the overall pack SHA-1 checksum, generate the
+corresponding index file, and update the refs) it can easily take
+several minutes for a single `checkpoint` command to complete.
+
+Frontends may choose to issue checkpoints during extremely large
+and long running imports, or when they need to allow another Git
+process access to a branch.  However given that a 30 GiB Subversion
+repository can be loaded into Git through fast-import in about 3 hours,
+explicit checkpointing may not be necessary.
+
+
+Tips and Tricks
+---------------
+The following tips and tricks have been collected from various
+users of fast-import, and are offered here as suggestions.
+
+Use One Mark Per Commit
+~~~~~~~~~~~~~~~~~~~~~~~
+When doing a repository conversion, use a unique mark per commit
+(`mark :<n>`) and supply the \--export-marks option on the command
+line.  fast-import will dump a file which lists every mark and the Git
+object SHA-1 that corresponds to it.  If the frontend can tie
+the marks back to the source repository, it is easy to verify the
+accuracy and completeness of the import by comparing each Git
+commit to the corresponding source revision.
+
+Coming from a system such as Perforce or Subversion this should be
+quite simple, as the fast-import mark can also be the Perforce changeset
+number or the Subversion revision number.
+
+Freely Skip Around Branches
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Don't bother trying to optimize the frontend to stick to one branch
+at a time during an import.  Although doing so might be slightly
+faster for fast-import, it tends to increase the complexity of the frontend
+code considerably.
+
+The branch LRU builtin to fast-import tends to behave very well, and the
+cost of activating an inactive branch is so low that bouncing around
+between branches has virtually no impact on import performance.
+
+Handling Renames
+~~~~~~~~~~~~~~~~
+When importing a renamed file or directory, simply delete the old
+name(s) and modify the new name(s) during the corresponding commit.
+Git performs rename detection after-the-fact, rather than explicitly
+during a commit.
+
+Use Tag Fixup Branches
+~~~~~~~~~~~~~~~~~~~~~~
+Some other SCM systems let the user create a tag from multiple
+files which are not from the same commit/changeset.  Or to create
+tags which are a subset of the files available in the repository.
+
+Importing these tags as-is in Git is impossible without making at
+least one commit which ``fixes up'' the files to match the content
+of the tag.  Use fast-import's `reset` command to reset a dummy branch
+outside of your normal branch space to the base commit for the tag,
+then commit one or more file fixup commits, and finally tag the
+dummy branch.
+
+For example since all normal branches are stored under `refs/heads/`
+name the tag fixup branch `TAG_FIXUP`.  This way it is impossible for
+the fixup branch used by the importer to have namespace conflicts
+with real branches imported from the source (the name `TAG_FIXUP`
+is not `refs/heads/TAG_FIXUP`).
+
+When committing fixups, consider using `merge` to connect the
+commit(s) which are supplying file revisions to the fixup branch.
+Doing so will allow tools such as gitlink:git-blame[1] to track
+through the real commit history and properly annotate the source
+files.
+
+After fast-import terminates the frontend will need to do `rm .git/TAG_FIXUP`
+to remove the dummy branch.
+
+Import Now, Repack Later
+~~~~~~~~~~~~~~~~~~~~~~~~
+As soon as fast-import completes the Git repository is completely valid
+and ready for use.  Typicallly this takes only a very short time,
+even for considerably large projects (100,000+ commits).
+
+However repacking the repository is necessary to improve data
+locality and access performance.  It can also take hours on extremely
+large projects (especially if -f and a large \--window parameter is
+used).  Since repacking is safe to run alongside readers and writers,
+run the repack in the background and let it finish when it finishes.
+There is no reason to wait to explore your new Git project!
+
+If you choose to wait for the repack, don't try to run benchmarks
+or performance tests until repacking is completed.  fast-import outputs
+suboptimal packfiles that are simply never seen in real use
+situations.
+
+Repacking Historical Data
+~~~~~~~~~~~~~~~~~~~~~~~~~
+If you are repacking very old imported data (e.g. older than the
+last year), consider expending some extra CPU time and supplying
+\--window=50 (or higher) when you run gitlink:git-repack[1].
+This will take longer, but will also produce a smaller packfile.
+You only need to expend the effort once, and everyone using your
+project will benefit from the smaller repository.
+
+
+Packfile Optimization
+---------------------
+When packing a blob fast-import always attempts to deltify against the last
+blob written.  Unless specifically arranged for by the frontend,
+this will probably not be a prior version of the same file, so the
+generated delta will not be the smallest possible.  The resulting
+packfile will be compressed, but will not be optimal.
+
+Frontends which have efficient access to all revisions of a
+single file (for example reading an RCS/CVS ,v file) can choose
+to supply all revisions of that file as a sequence of consecutive
+`blob` commands.  This allows fast-import to deltify the different file
+revisions against each other, saving space in the final packfile.
+Marks can be used to later identify individual file revisions during
+a sequence of `commit` commands.
+
+The packfile(s) created by fast-import do not encourage good disk access
+patterns.  This is caused by fast-import writing the data in the order
+it is received on standard input, while Git typically organizes
+data within packfiles to make the most recent (current tip) data
+appear before historical data.  Git also clusters commits together,
+speeding up revision traversal through better cache locality.
+
+For this reason it is strongly recommended that users repack the
+repository with `git repack -a -d` after fast-import completes, allowing
+Git to reorganize the packfiles for faster data access.  If blob
+deltas are suboptimal (see above) then also adding the `-f` option
+to force recomputation of all deltas can significantly reduce the
+final packfile size (30-50% smaller can be quite typical).
+
+
+Memory Utilization
+------------------
+There are a number of factors which affect how much memory fast-import
+requires to perform an import.  Like critical sections of core
+Git, fast-import uses its own memory allocators to ammortize any overheads
+associated with malloc.  In practice fast-import tends to ammoritize any
+malloc overheads to 0, due to its use of large block allocations.
+
+per object
+~~~~~~~~~~
+fast-import maintains an in-memory structure for every object written in
+this execution.  On a 32 bit system the structure is 32 bytes,
+on a 64 bit system the structure is 40 bytes (due to the larger
+pointer sizes).  Objects in the table are not deallocated until
+fast-import terminates.  Importing 2 million objects on a 32 bit system
+will require approximately 64 MiB of memory.
+
+The object table is actually a hashtable keyed on the object name
+(the unique SHA-1).  This storage configuration allows fast-import to reuse
+an existing or already written object and avoid writing duplicates
+to the output packfile.  Duplicate blobs are surprisingly common
+in an import, typically due to branch merges in the source.
+
+per mark
+~~~~~~~~
+Marks are stored in a sparse array, using 1 pointer (4 bytes or 8
+bytes, depending on pointer size) per mark.  Although the array
+is sparse, frontends are still strongly encouraged to use marks
+between 1 and n, where n is the total number of marks required for
+this import.
+
+per branch
+~~~~~~~~~~
+Branches are classified as active and inactive.  The memory usage
+of the two classes is significantly different.
+
+Inactive branches are stored in a structure which uses 96 or 120
+bytes (32 bit or 64 bit systems, respectively), plus the length of
+the branch name (typically under 200 bytes), per branch.  fast-import will
+easily handle as many as 10,000 inactive branches in under 2 MiB
+of memory.
+
+Active branches have the same overhead as inactive branches, but
+also contain copies of every tree that has been recently modified on
+that branch.  If subtree `include` has not been modified since the
+branch became active, its contents will not be loaded into memory,
+but if subtree `src` has been modified by a commit since the branch
+became active, then its contents will be loaded in memory.
+
+As active branches store metadata about the files contained on that
+branch, their in-memory storage size can grow to a considerable size
+(see below).
+
+fast-import automatically moves active branches to inactive status based on
+a simple least-recently-used algorithm.  The LRU chain is updated on
+each `commit` command.  The maximum number of active branches can be
+increased or decreased on the command line with \--active-branches=.
+
+per active tree
+~~~~~~~~~~~~~~~
+Trees (aka directories) use just 12 bytes of memory on top of the
+memory required for their entries (see ``per active file'' below).
+The cost of a tree is virtually 0, as its overhead ammortizes out
+over the individual file entries.
+
+per active file entry
+~~~~~~~~~~~~~~~~~~~~~
+Files (and pointers to subtrees) within active trees require 52 or 64
+bytes (32/64 bit platforms) per entry.  To conserve space, file and
+tree names are pooled in a common string table, allowing the filename
+``Makefile'' to use just 16 bytes (after including the string header
+overhead) no matter how many times it occurs within the project.
+
+The active branch LRU, when coupled with the filename string pool
+and lazy loading of subtrees, allows fast-import to efficiently import
+projects with 2,000+ branches and 45,114+ files in a very limited
+memory footprint (less than 2.7 MiB per active branch).
+
+
+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 3e6cd880b0e6aeda3056da1361ffcae52b89b531..105d76b0ba85ecc4bd2af6dd560ae28310a5c7be 100644 (file)
@@ -8,10 +8,13 @@ git-fetch-pack - Receive missing objects from another repository
 
 SYNOPSIS
 --------
-'git-fetch-pack' [-q] [-k] [--exec=<git-upload-pack>] [<host>:]<directory> [<refs>...]
+'git-fetch-pack' [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [-v] [<host>:]<directory> [<refs>...]
 
 DESCRIPTION
 -----------
+Usually you would want to use gitlink:git-fetch[1] which is a
+higher level wrapper of this command instead.
+
 Invokes 'git-upload-pack' on a potentially remote repository,
 and asks it to send objects missing from this repository, to
 update the named heads.  The list of commits available locally
@@ -25,17 +28,24 @@ have a common ancestor commit.
 
 OPTIONS
 -------
--q::
+\--all::
+       Fetch all remote refs.
+
+\--quiet, \-q::
        Pass '-q' flag to 'git-unpack-objects'; this makes the
        cloning process less verbose.
 
--k::
+\--keep, \-k::
        Do not invoke 'git-unpack-objects' on received data, but
        create a single packfile out of it instead, and store it
        in the object database. If provided twice then the pack is
        locked against repacking.
 
---exec=<git-upload-pack>::
+\--thin::
+       Spend extra cycles to minimize the number of objects to be sent.
+       Use it on slower connection.
+
+\--upload-pack=<git-upload-pack>::
        Use this to specify the path to 'git-upload-pack' on the
        remote side, if is not found on your $PATH.
        Installations of sshd ignores the user's environment
@@ -47,6 +57,15 @@ OPTIONS
        shells by having a lean .bashrc file (they set most of
        the things up in .bash_profile).
 
+\--exec=<git-upload-pack>::
+       Same as \--upload-pack=<git-upload-pack>.
+
+\--depth=<n>::
+       Limit fetching to ancestor-chains not longer than n.
+
+\-v::
+       Run verbosely.
+
 <host>::
        A remote host that houses the repository.  When this
        part is specified, 'git-upload-pack' is invoked via
index a9e86fd26b0df6fe7a84ebdd030b9ae4d46f597e..5fbeab76b7214f871865eaefd5dbe744036676b0 100644 (file)
@@ -3,7 +3,7 @@ git-fetch(1)
 
 NAME
 ----
-git-fetch - Download objects and a head from another repository
+git-fetch - Download objects and refs from another repository
 
 
 SYNOPSIS
@@ -20,6 +20,14 @@ The ref names and their object names of fetched refs are stored
 in `.git/FETCH_HEAD`.  This information is left for a later merge
 operation done by "git merge".
 
+When <refspec> stores the fetched result in tracking branches,
+the tags that point at these branches are automatically
+followed.  This is done by first fetching from the remote using
+the given <refspec>s, and if the repository has objects that are
+pointed by remote tags that it does not yet have, then fetch
+those missing tags.  If the other end has tags that point at
+branches you are not interested in, you will not get them.
+
 
 OPTIONS
 -------
index 2bf6aef73522e3ba7e5212d3eb1bce02a257e069..f49b0d944c613a9d5f4a6c5b6713d5c556f0390e 100644 (file)
@@ -7,7 +7,7 @@ git-for-each-ref - Output information on each ref
 
 SYNOPSIS
 --------
-'git-for-each-ref' [--count=<count>]\* [--shell|--perl|--python] [--sort=<key>]\* [--format=<format>] [<pattern>]
+'git-for-each-ref' [--count=<count>]\* [--shell|--perl|--python|--tcl] [--sort=<key>]\* [--format=<format>] [<pattern>]
 
 DESCRIPTION
 -----------
@@ -15,7 +15,7 @@ DESCRIPTION
 Iterate over all refs that match `<pattern>` and show them
 according to the given `<format>`, after sorting them according
 to the given set of `<key>`.  If `<max>` is given, stop after
-showing that many refs.  The interporated values in `<format>`
+showing that many refs.  The interpolated values in `<format>`
 can optionally be quoted as string literals in the specified
 host language allowing their direct evaluation in that language.
 
@@ -49,7 +49,7 @@ OPTIONS
        using fnmatch(3).  Refs that do not match the pattern
        are not shown.
 
---shell, --perl, --python::
+--shell, --perl, --python, --tcl::
        If given, strings that substitute `%(fieldname)`
        placeholders are quoted as string literals suitable for
        the specified host language.  This is meant to produce
@@ -66,7 +66,7 @@ keys.
 For all objects, the following names can be used:
 
 refname::
-       The name of the ref (the part after $GIT_DIR/refs/).
+       The name of the ref (the part after $GIT_DIR/).
 
 objecttype::
        The type of the object (`blob`, `tree`, `commit`, `tag`).
index 67425dc0359ee9774200e1a2e12913d570117e7c..59f34b9f0d2f4fe74fa5d90bb53393c88d36476b 100644 (file)
@@ -11,7 +11,8 @@ SYNOPSIS
 [verse]
 'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach] [--thread]
                   [-s | --signoff] [--diff-options] [--start-number <n>]
-                  [--in-reply-to=Message-Id]
+                  [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+                  [--ignore-if-in-upstream]
                   <since>[..<until>]
 
 DESCRIPTION
@@ -20,7 +21,9 @@ DESCRIPTION
 Prepare each commit between <since> and <until> with its patch in
 one file per commit, formatted to resemble UNIX mailbox format.
 If ..<until> is not specified, the head of the current working
-tree is implied.
+tree is implied.  For a more complete list of ways to spell
+<since> and <until>, see "SPECIFYING REVISIONS" section in
+gitlink:git-rev-parse[1].
 
 The output of this command is convenient for e-mail submission or
 for use with gitlink:git-am[1].
@@ -78,13 +81,34 @@ OPTIONS
        reply to the given Message-Id, which avoids breaking threads to
        provide a new patch series.
 
+--ignore-if-in-upstream::
+       Do not include a patch that matches a commit in
+       <until>..<since>.  This will examine all patches reachable
+       from <since> but not from <until> and compare them with the
+       patches being generated, and any patch that matches is
+       ignored.
+
+--suffix=.<sfx>::
+       Instead of using `.patch` as the suffix for generated
+       filenames, use specifed suffix.  A common alternative is
+       `--suffix=.txt`.
++
+Note that you would need to include the leading dot `.` if you
+want a filename like `0001-description-of-my-change.patch`, and
+the first letter does not have to be a dot.  Leaving it empty would
+not add any suffix.
+
 CONFIGURATION
 -------------
 You can specify extra mail header lines to be added to each
-message in the repository configuration as follows:
+message in the repository configuration.  Also you can specify
+the default suffix different from the built-in one:
 
+------------
 [format]
         headers = "Organization: git-foo\n"
+        suffix = .txt
+------------
 
 
 EXAMPLES
@@ -109,6 +133,9 @@ git-format-patch -M -B origin::
        understand renaming patches, so use it only when you know
        the recipient uses git to apply your patch.
 
+git-format-patch -3::
+       Extract three topmost commits from the current branch
+       and format them as e-mailable patches.
 
 See Also
 --------
index d0af99d3512d3794f9907612cef1d30616120064..f21061ecfe56a438238cc84761174f836bd0035e 100644 (file)
@@ -8,132 +8,10 @@ git-fsck-objects - Verifies the connectivity and validity of the objects in the
 
 SYNOPSIS
 --------
-[verse]
-'git-fsck-objects' [--tags] [--root] [--unreachable] [--cache]
-                [--full] [--strict] [<object>*]
+'git-fsck-objects' ...
 
 DESCRIPTION
 -----------
-Verifies the connectivity and validity of the objects in the database.
-
-OPTIONS
--------
-<object>::
-       An object to treat as the head of an unreachability trace.
-+
-If no objects are given, git-fsck-objects defaults to using the
-index file and all SHA1 references in .git/refs/* as heads.
-
---unreachable::
-       Print out objects that exist but that aren't readable from any
-       of the reference nodes.
-
---root::
-       Report root nodes.
-
---tags::
-       Report tags.
-
---cache::
-       Consider any object recorded in the index also as a head node for
-       an unreachability trace.
-
---full::
-       Check not just objects in GIT_OBJECT_DIRECTORY
-       ($GIT_DIR/objects), but also the ones found in alternate
-       object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES
-       or $GIT_DIR/objects/info/alternates,
-       and in packed git archives found in $GIT_DIR/objects/pack
-       and corresponding pack subdirectories in alternate
-       object pools.
-
---strict::
-       Enable more strict checking, namely to catch a file mode
-       recorded with g+w bit set, which was created by older
-       versions of git.  Existing repositories, including the
-       Linux kernel, git itself, and sparse repository have old
-       objects that triggers this check, but it is recommended
-       to check new projects with this flag.
-
-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
-'--unreachable' flag it will also print out objects that exist but
-that aren't readable from any of the specified head nodes.
-
-So for example
-
-       git-fsck-objects --unreachable HEAD $(cat .git/refs/heads/*)
-
-will do quite a _lot_ of verification on the tree. There are a few
-extra validity tests to be added (make sure that tree objects are
-sorted properly etc), but on the whole if "git-fsck-objects" is happy, you
-do have a valid tree.
-
-Any corrupt objects you will have to find in backups or other archives
-(i.e., you can just remove them and do an "rsync" with some other site in
-the hopes that somebody else has the object you have corrupted).
-
-Of course, "valid tree" doesn't mean that it wasn't generated by some
-evil person, and the end result might be crap. git is a revision
-tracking system, not a quality assurance system ;)
-
-Extracted Diagnostics
----------------------
-
-expect dangling commits - potential heads - due to lack of head information::
-       You haven't specified any nodes as heads so it won't be
-       possible to differentiate between un-parented commits and
-       root nodes.
-
-missing sha1 directory '<dir>'::
-       The directory holding the sha1 objects is missing.
-
-unreachable <type> <object>::
-       The <type> object <object>, isn't actually referred to directly
-       or indirectly in any of the trees or commits seen. This can
-       mean that there's another root node that you're not specifying
-       or that the tree is corrupt. If you haven't missed a root node
-       then you might as well delete unreachable nodes since they
-       can't be used.
-
-missing <type> <object>::
-       The <type> object <object>, is referred to but isn't present in
-       the database.
-
-dangling <type> <object>::
-       The <type> object <object>, is present in the database but never
-       'directly' used. A dangling commit could be a root node.
-
-warning: git-fsck-objects: tree <tree> has full pathnames in it::
-       And it shouldn't...
-
-sha1 mismatch <object>::
-       The database has an object who's sha1 doesn't match the
-       database value.
-       This indicates a serious data integrity problem.
-
-Environment Variables
----------------------
-
-GIT_OBJECT_DIRECTORY::
-       used to specify the object database root (usually $GIT_DIR/objects)
-
-GIT_INDEX_FILE::
-       used to specify the index file of the index
-
-GIT_ALTERNATE_OBJECT_DIRECTORIES::
-       used to specify additional object database roots (usually unset)
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
 
+This is a synonym for gitlink:git-fsck[1].  Please refer to the
+documentation of that command.
diff --git a/Documentation/git-fsck.txt b/Documentation/git-fsck.txt
new file mode 100644 (file)
index 0000000..058009d
--- /dev/null
@@ -0,0 +1,139 @@
+git-fsck(1)
+===========
+
+NAME
+----
+git-fsck - Verifies the connectivity and validity of the objects in the database
+
+
+SYNOPSIS
+--------
+[verse]
+'git-fsck' [--tags] [--root] [--unreachable] [--cache]
+                [--full] [--strict] [<object>*]
+
+DESCRIPTION
+-----------
+Verifies the connectivity and validity of the objects in the database.
+
+OPTIONS
+-------
+<object>::
+       An object to treat as the head of an unreachability trace.
++
+If no objects are given, git-fsck defaults to using the
+index file and all SHA1 references in .git/refs/* as heads.
+
+--unreachable::
+       Print out objects that exist but that aren't readable from any
+       of the reference nodes.
+
+--root::
+       Report root nodes.
+
+--tags::
+       Report tags.
+
+--cache::
+       Consider any object recorded in the index also as a head node for
+       an unreachability trace.
+
+--full::
+       Check not just objects in GIT_OBJECT_DIRECTORY
+       ($GIT_DIR/objects), but also the ones found in alternate
+       object pools listed in GIT_ALTERNATE_OBJECT_DIRECTORIES
+       or $GIT_DIR/objects/info/alternates,
+       and in packed git archives found in $GIT_DIR/objects/pack
+       and corresponding pack subdirectories in alternate
+       object pools.
+
+--strict::
+       Enable more strict checking, namely to catch a file mode
+       recorded with g+w bit set, which was created by older
+       versions of git.  Existing repositories, including the
+       Linux kernel, git itself, and sparse repository have old
+       objects that triggers this check, but it is recommended
+       to check new projects with this flag.
+
+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
+'--unreachable' flag it will also print out objects that exist but
+that aren't readable from any of the specified head nodes.
+
+So for example
+
+       git-fsck --unreachable HEAD $(cat .git/refs/heads/*)
+
+will do quite a _lot_ of verification on the tree. There are a few
+extra validity tests to be added (make sure that tree objects are
+sorted properly etc), but on the whole if "git-fsck" is happy, you
+do have a valid tree.
+
+Any corrupt objects you will have to find in backups or other archives
+(i.e., you can just remove them and do an "rsync" with some other site in
+the hopes that somebody else has the object you have corrupted).
+
+Of course, "valid tree" doesn't mean that it wasn't generated by some
+evil person, and the end result might be crap. git is a revision
+tracking system, not a quality assurance system ;)
+
+Extracted Diagnostics
+---------------------
+
+expect dangling commits - potential heads - due to lack of head information::
+       You haven't specified any nodes as heads so it won't be
+       possible to differentiate between un-parented commits and
+       root nodes.
+
+missing sha1 directory '<dir>'::
+       The directory holding the sha1 objects is missing.
+
+unreachable <type> <object>::
+       The <type> object <object>, isn't actually referred to directly
+       or indirectly in any of the trees or commits seen. This can
+       mean that there's another root node that you're not specifying
+       or that the tree is corrupt. If you haven't missed a root node
+       then you might as well delete unreachable nodes since they
+       can't be used.
+
+missing <type> <object>::
+       The <type> object <object>, is referred to but isn't present in
+       the database.
+
+dangling <type> <object>::
+       The <type> object <object>, is present in the database but never
+       'directly' used. A dangling commit could be a root node.
+
+warning: git-fsck: tree <tree> has full pathnames in it::
+       And it shouldn't...
+
+sha1 mismatch <object>::
+       The database has an object who's sha1 doesn't match the
+       database value.
+       This indicates a serious data integrity problem.
+
+Environment Variables
+---------------------
+
+GIT_OBJECT_DIRECTORY::
+       used to specify the object database root (usually $GIT_DIR/objects)
+
+GIT_INDEX_FILE::
+       used to specify the index file of the index
+
+GIT_ALTERNATE_OBJECT_DIRECTORIES::
+       used to specify additional object database roots (usually unset)
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt
new file mode 100644 (file)
index 0000000..bc16584
--- /dev/null
@@ -0,0 +1,83 @@
+git-gc(1)
+=========
+
+NAME
+----
+git-gc - Cleanup unnecessary files and optimize the local repository
+
+
+SYNOPSIS
+--------
+'git-gc' [--prune]
+
+DESCRIPTION
+-----------
+Runs a number of housekeeping tasks within the current repository,
+such as compressing file revisions (to reduce disk space and increase
+performance) and removing unreachable objects which may have been
+created from prior invocations of gitlink:git-add[1].
+
+Users are encouraged to run this task on a regular basis within
+each repository to maintain good disk space utilization and good
+operating performance.
+
+OPTIONS
+-------
+
+--prune::
+       Usually `git-gc` packs refs, expires old reflog entries,
+       packs loose objects,
+       and removes old 'rerere' records.  Removal
+       of unreferenced loose objects is an unsafe operation
+       while other git operations are in progress, so it is not
+       done by default.  Pass this option if you want it, and only
+       when you know nobody else is creating new objects in the
+       repository at the same time (e.g. never use this option
+       in a cron script).
+
+
+Configuration
+-------------
+
+The optional configuration variable 'gc.reflogExpire' can be
+set to indicate how long historical entries within each branch's
+reflog should remain available in this repository.  The setting is
+expressed as a length of time, for example '90 days' or '3 months'.
+It defaults to '90 days'.
+
+The optional configuration variable 'gc.reflogExpireUnreachable'
+can be set to indicate how long historical reflog entries which
+are not part of the current branch should remain available in
+this repository.  These types of entries are generally created as
+a result of using `git commit \--amend` or `git rebase` and are the
+commits prior to the amend or rebase occurring.  Since these changes
+are not part of the current project most users will want to expire
+them sooner.  This option defaults to '30 days'.
+
+The optional configuration variable 'gc.rerereresolved' indicates
+how long records of conflicted merge you resolved earlier are
+kept.  This defaults to 60 days.
+
+The optional configuration variable 'gc.rerereunresolved' indicates
+how long records of conflicted merge you have not resolved are
+kept.  This defaults to 15 days.
+
+The optional configuration variable 'gc.packrefs' determines if
+`git gc` runs `git-pack-refs`.  Without the configuration, `git-pack-refs`
+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.
+
+See Also
+--------
+gitlink:git-prune[1]
+gitlink:git-reflog[1]
+gitlink:git-repack[1]
+gitlink:git-rerere[1]
+
+Author
+------
+Written by Shawn O. Pearce <spearce@spearce.org>
+
+GIT
+---
+Part of the gitlink:git[7] suite
index bfbece9864a3c6d8b38dd526e2483fea0fb0b58f..0140c8e3580c7be90648f5ea0186a5c925ee1584 100644 (file)
@@ -91,7 +91,7 @@ OPTIONS
        combined by 'or'.
 
 --and | --or | --not | ( | )::
-       Specify how multiple patterns are combined using boolean
+       Specify how multiple patterns are combined using Boolean
        expressions.  `--or` is the default operator.  `--and` has
        higher precedence than `--or`.  `-e` has to be used for all
        patterns.
index 04e8d004363a163930a4b8f583ab5ec997dbfe3c..5edc36f060f7cf6444dd016cbb7e073af923f5db 100644 (file)
@@ -3,7 +3,7 @@ git-hash-object(1)
 
 NAME
 ----
-git-hash-object - Computes object ID and optionally creates a blob from a file
+git-hash-object - Compute object ID and optionally creates a blob from a file
 
 
 SYNOPSIS
index 3d508094afc6a79d40b84ce0a3380f487a40d5e8..7dc2df3044e82e20fbb99ae86f0a150c08cd027d 100644 (file)
@@ -3,7 +3,7 @@ git-http-fetch(1)
 
 NAME
 ----
-git-http-fetch - downloads a remote git repository via HTTP
+git-http-fetch - Download from a remote git repository via HTTP
 
 
 SYNOPSIS
index c2485c6e9cdc2c6ec8c6fd876ed19758d6c5bb84..4b4a46169c79b90e22f5bde0b26888761c28c9c4 100644 (file)
@@ -3,7 +3,7 @@ git-http-push(1)
 
 NAME
 ----
-git-http-push - Push missing objects using HTTP/DAV
+git-http-push - Push objects over HTTP/DAV to another repository
 
 
 SYNOPSIS
index ca7d09dc0a8563a1d8005bc83a89d19c785c1a3d..5412135d763af3a8c68e18441b774654f07eadfd 100644 (file)
@@ -11,95 +11,9 @@ SYNOPSIS
 'git-init-db' [--template=<template_directory>] [--shared[=<permissions>]]
 
 
-OPTIONS
--------
-
---
-
---template=<template_directory>::
-
-Provide the directory from which templates will be used.  The default template
-directory is `/usr/share/git-core/templates`.
-
-When specified, `<template_directory>` is used as the source of the template
-files rather than the default.  The template files include some directory
-structure, some suggested "exclude patterns", and copies of non-executing
-"hook" files.  The suggested patterns and hook files are all modifiable and
-extensible.
-
---shared[={false|true|umask|group|all|world|everybody}]::
-
-Specify that the git repository is to be shared amongst several users.  This
-allows users belonging to the same group to push into that
-repository.  When specified, the config variable "core.sharedRepository" is
-set so that files and directories under `$GIT_DIR` are created with the
-requested permissions.  When not specified, git will use permissions reported
-by umask(2).
-
-The option can have the following values, defaulting to 'group' if no value
-is given:
-
- - 'umask' (or 'false'): Use permissions reported by umask(2). The default,
-   when `--shared` is not specified.
-
- - 'group' (or 'true'): Make the repository group-writable, (and g+sx, since
-   the git group may be not the primary group of all users).
-
- - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository
-   readable by all users.
-
-By default, the configuration flag receive.denyNonFastforward is enabled
-in shared repositories, so that you cannot force a non fast-forwarding push
-into it.
-
---
-
-
 DESCRIPTION
 -----------
-This command creates an empty git repository - basically a `.git` directory
-with subdirectories for `objects`, `refs/heads`, `refs/tags`, and
-template files.
-An initial `HEAD` file that references the HEAD of the master branch
-is also created.
-
-If the `$GIT_DIR` environment variable is set then it specifies a path
-to use instead of `./.git` for the base of the repository.
-
-If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY`
-environment variable then the sha1 directories are created underneath -
-otherwise the default `$GIT_DIR/objects` directory is used.
-
-Running `git-init-db` in an existing repository is safe. It will not overwrite
-things that are already there. The primary reason for rerunning `git-init-db`
-is to pick up newly added templates.
-
-
-
-EXAMPLES
---------
-
-Start a new git repository for an existing code base::
-+
-----------------
-$ cd /path/to/my/codebase
-$ git-init-db   <1>
-$ git-add .     <2>
-----------------
-+
-<1> prepare /path/to/my/codebase/.git directory
-<2> add all existing file to the index
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
 
-GIT
----
-Part of the gitlink:git[7] suite
+This is a synonym for gitlink:git-init[1].  Please refer to the
+documentation of that command.
 
diff --git a/Documentation/git-init.txt b/Documentation/git-init.txt
new file mode 100644 (file)
index 0000000..1b64d3a
--- /dev/null
@@ -0,0 +1,111 @@
+git-init(1)
+===========
+
+NAME
+----
+git-init - Create an empty git repository or reinitialize an existing one
+
+
+SYNOPSIS
+--------
+'git-init' [--template=<template_directory>] [--shared[=<permissions>]]
+
+
+OPTIONS
+-------
+
+--
+
+--template=<template_directory>::
+
+Provide the directory from which templates will be used.  The default template
+directory is `/usr/share/git-core/templates`.
+
+When specified, `<template_directory>` is used as the source of the template
+files rather than the default.  The template files include some directory
+structure, some suggested "exclude patterns", and copies of non-executing
+"hook" files.  The suggested patterns and hook files are all modifiable and
+extensible.
+
+--shared[={false|true|umask|group|all|world|everybody}]::
+
+Specify that the git repository is to be shared amongst several users.  This
+allows users belonging to the same group to push into that
+repository.  When specified, the config variable "core.sharedRepository" is
+set so that files and directories under `$GIT_DIR` are created with the
+requested permissions.  When not specified, git will use permissions reported
+by umask(2).
+
+The option can have the following values, defaulting to 'group' if no value
+is given:
+
+ - 'umask' (or 'false'): Use permissions reported by umask(2). The default,
+   when `--shared` is not specified.
+
+ - 'group' (or 'true'): Make the repository group-writable, (and g+sx, since
+   the git group may be not the primary group of all users).
+
+ - 'all' (or 'world' or 'everybody'): Same as 'group', but make the repository
+   readable by all users.
+
+By default, the configuration flag receive.denyNonFastforward is enabled
+in shared repositories, so that you cannot force a non fast-forwarding push
+into it.
+
+--
+
+
+DESCRIPTION
+-----------
+This command creates an empty git repository - basically a `.git` directory
+with subdirectories for `objects`, `refs/heads`, `refs/tags`, and
+template files.
+An initial `HEAD` file that references the HEAD of the master branch
+is also created.
+
+If the `$GIT_DIR` environment variable is set then it specifies a path
+to use instead of `./.git` for the base of the repository.
+
+If the object storage directory is specified via the `$GIT_OBJECT_DIRECTORY`
+environment variable then the sha1 directories are created underneath -
+otherwise the default `$GIT_DIR/objects` directory is used.
+
+Running `git-init` in an existing repository is safe. It will not overwrite
+things that are already there. The primary reason for rerunning `git-init`
+is to pick up newly added templates.
+
+Note that `git-init` is the same as `git-init-db`.  The command
+was primarily meant to initialize the object database, but over
+time it has become responsible for setting up the other aspects
+of the repository, such as installing the default hooks and
+setting the configuration variables.  The old name is retained
+for backward compatibility reasons.
+
+
+EXAMPLES
+--------
+
+Start a new git repository for an existing code base::
++
+----------------
+$ cd /path/to/my/codebase
+$ git-init      <1>
+$ git-add .     <2>
+----------------
++
+<1> prepare /path/to/my/codebase/.git directory
+<2> add all existing file to the index
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 7dd393b97f6a70642fe713fa780c1caed2ead840..52a6aa6e82cda3fa0c606fd57c4ea67c4396a268 100644 (file)
@@ -3,7 +3,7 @@ git-instaweb(1)
 
 NAME
 ----
-git-instaweb - instantly browse your working repository in gitweb
+git-instaweb - Instantly browse your working repository in gitweb
 
 SYNOPSIS
 --------
index 2fbdfe086a4c974d19c3d8f4c6a1722728b0114a..22048d82bdbff1f8a9d5f9ba312ad15b719afc03 100644 (file)
@@ -3,7 +3,7 @@ git-local-fetch(1)
 
 NAME
 ----
-git-local-fetch - Duplicates another git repository on a local system
+git-local-fetch - Duplicate another git repository on a local system
 
 
 SYNOPSIS
index c9ffff734c256d7db1470381f0eb74a8d7f81873..361eaec700bf6401f89c7e631f53414a399affe6 100644 (file)
@@ -16,7 +16,7 @@ Shows the commit logs.
 
 The command takes options applicable to the gitlink:git-rev-list[1]
 command to control what is shown and how, and options applicable to
-the gitlink:git-diff-tree[1] commands to control how the change
+the gitlink:git-diff-tree[1] commands to control how the changes
 each commit introduces are shown.
 
 This manual page describes only the most frequently used options.
@@ -24,14 +24,19 @@ This manual page describes only the most frequently used options.
 
 OPTIONS
 -------
---pretty=<format>::
-       Controls the way the commit log is formatted.
 
---max-count=<n>::
+include::pretty-formats.txt[]
+
+-<n>::
        Limits the number of commits to show.
 
 <since>..<until>::
-       Show only commits between the named two commits.
+       Show only commits between the named two commits.  When
+       either <since> or <until> is omitted, it defaults to
+       `HEAD`, i.e. the tip of the current branch.
+       For a more complete list of ways to spell <since>
+       and <until>, see "SPECIFYING REVISIONS" section in
+       gitlink:git-rev-parse[1].
 
 -p::
        Show the change the commit introduces in a patch form.
@@ -63,6 +68,12 @@ git log -r --name-status release..test::
        in the "release" branch, along with the list of paths
        each commit modifies.
 
+Discussion
+----------
+
+include::i18n.txt[]
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index 8520b971111e8b015ac3d08b207a3ab37505ccca..79e0b7b71a126537091fdb159a1141036215fb63 100644 (file)
@@ -3,7 +3,7 @@ git-ls-files(1)
 
 NAME
 ----
-git-ls-files - Information about files in the index/working directory
+git-ls-files - Show information about files in the index and the working tree
 
 
 SYNOPSIS
index c8a4c5a4d9d2bead8686f1b582e985531a7f3591..c254005ca39e6872b8ec7b729246d18757df31d1 100644 (file)
@@ -29,7 +29,7 @@ OPTIONS
 -u <exec>, --upload-pack=<exec>::
        Specify the full path of gitlink:git-upload-pack[1] on the remote
        host. This allows listing references from repositories accessed via
-       SSH and where the SSH deamon does not use the PATH configured by the
+       SSH and where the SSH daemon does not use the PATH configured by the
        user. Also see the '--exec' option for gitlink:git-peek-remote[1].
 
 <repository>::
index f283bacb6557fe7c1313912402f60e3a1560d04a..78993940811c96752e87f7b9e7c1381066056950 100644 (file)
@@ -3,7 +3,7 @@ git-ls-tree(1)
 
 NAME
 ----
-git-ls-tree - Lists the contents of a tree object
+git-ls-tree - List the contents of a tree object
 
 
 SYNOPSIS
index ea0a06557f933ce48374ddbb751a64945ff65955..ba18133ead23b0974b8198e25c28ccc746e99954 100644 (file)
@@ -3,7 +3,7 @@ git-mailinfo(1)
 
 NAME
 ----
-git-mailinfo - Extracts patch from a single e-mail message
+git-mailinfo - Extracts patch and authorship from a single e-mail message
 
 
 SYNOPSIS
@@ -18,7 +18,7 @@ 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
 to create a commit.  It is usually not necessary to use this
-command directly.
+command directly.  See gitlink:git-am[1] instead.
 
 
 OPTIONS
@@ -33,15 +33,13 @@ OPTIONS
        format-patch --mbox' output.
 
 -u::
-       By default, the commit log message, author name and
-       author email are taken from the e-mail without any
-       charset conversion, after minimally decoding MIME
-       transfer encoding.  This flag causes the resulting
-       commit to be encoded in the encoding specified by
-       i18n.commitencoding configuration (defaults to utf-8) by
-       transliterating them. 
-       Note that the patch is always used as is without charset
-       conversion, even with this flag.
+       The commit log message, author name and author email are
+       taken from the e-mail, and after minimally decoding MIME
+       transfer encoding, re-coded in UTF-8 by transliterating
+       them.  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.
 
 --encoding=<encoding>::
        Similar to -u but if the local convention is different
index 5a17801f6a8d4d307f55e62745a400ad498ebdb9..c11d6a530f67857bb28a5b3cee48bc5b01f5cb92 100644 (file)
@@ -3,7 +3,7 @@ git-mailsplit(1)
 
 NAME
 ----
-git-mailsplit - Totally braindamaged mbox splitter program
+git-mailsplit - Simple UNIX mbox splitter program
 
 SYNOPSIS
 --------
index 6099be2add74c2eb82ea5b1af844174a4dd896b5..3190aed10885b5f3471dfc30922c141835d6c4f1 100644 (file)
@@ -3,7 +3,7 @@ git-merge-base(1)
 
 NAME
 ----
-git-merge-base - Finds as good a common ancestor as possible for a merge
+git-merge-base - Find as good common ancestors as possible for a merge
 
 
 SYNOPSIS
diff --git a/Documentation/git-merge-file.txt b/Documentation/git-merge-file.txt
new file mode 100644 (file)
index 0000000..31882ab
--- /dev/null
@@ -0,0 +1,92 @@
+git-merge-file(1)
+=================
+
+NAME
+----
+git-merge-file - Run a three-way file merge
+
+
+SYNOPSIS
+--------
+[verse]
+'git-merge-file' [-L <current-name> [-L <base-name> [-L <other-name>]]]
+       [-p|--stdout] [-q|--quiet] <current-file> <base-file> <other-file>
+
+
+DESCRIPTION
+-----------
+git-file-merge incorporates all changes that lead from the `<base-file>`
+to `<other-file>` into `<current-file>`. The result ordinarily goes into
+`<current-file>`. git-merge-file is useful for combining separate changes
+to an original. Suppose `<base-file>` is the original, and both
+`<current-file>` and `<other-file>` are modifications of `<base-file>`.
+Then git-merge-file combines both changes.
+
+A conflict occurs if both `<current-file>` and `<other-file>` have changes
+in a common segment of lines. If a conflict is found, git-merge-file
+normally outputs a warning and brackets the conflict with <<<<<<< and
+>>>>>>> lines. A typical conflict will look like this:
+
+       <<<<<<< A
+       lines in file A
+       =======
+       lines in file B
+       >>>>>>> B
+
+If there are conflicts, the user should edit the result and delete one of
+the alternatives.
+
+The exit value of this program is negative on error, and the number of
+conflicts otherwise. If the merge was clean, the exit value is 0.
+
+git-merge-file is designed to be a minimal clone of RCS merge, that is, it
+implements all of RCS merge's functionality which is needed by
+gitlink:git[1].
+
+
+OPTIONS
+-------
+
+-L <label>::
+       This option may be given up to three times, and
+       specifies labels to be used in place of the
+       corresponding file names in conflict reports. That is,
+       `git-merge-file -L x -L y -L z a b c` generates output that
+       looks like it came from files x, y and z instead of
+       from files a, b and c.
+
+-p::
+       Send results to standard output instead of overwriting
+       `<current-file>`.
+
+-q::
+       Quiet;  do  not  warn about conflicts.
+
+
+EXAMPLES
+--------
+
+git merge-file README.my README README.upstream::
+
+       combines the changes of README.my and README.upstream since README,
+       tries to merge them and writes the result into README.my.
+
+git merge-file -L a -L b -L c tmp/a123 tmp/b234 tmp/c345::
+
+       merges tmp/a123 and tmp/c345 with the base tmp/b234, but uses labels
+       `a` and `c` instead of `tmp/a123` and `tmp/c345`.
+
+
+Author
+------
+Written by Johannes Schindelin <johannes.schindelin@gmx.de>
+
+
+Documentation
+--------------
+Documentation by Johannes Schindelin and the git-list <git@vger.kernel.org>,
+with parts copied from the original documentation of RCS merge.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 6cd060108292733725aca28cc8f0b83aa7d0f56e..b8ee1ff2b0681ea9fbc83618b1d09d95f3a902ff 100644 (file)
@@ -3,7 +3,7 @@ git-merge-index(1)
 
 NAME
 ----
-git-merge-index - Runs a merge for files needing merging
+git-merge-index - Run a merge for files needing merging
 
 
 SYNOPSIS
@@ -40,8 +40,8 @@ If "git-merge-index" is called with multiple <file>s (or -a) then it
 processes them in turn only stopping if merge returns a non-zero exit
 code.
 
-Typically this is run with the a script calling the merge command from
-the RCS package.
+Typically this is run with the a script calling git's imitation of
+the merge command from the RCS package.
 
 A sample script called "git-merge-one-file" is included in the
 distribution.
index 86aad37c6acaf0c37995221456bffc61ed3bc883..f80ab3b8c479d787e116298f5446d1ff57ff2774 100644 (file)
@@ -3,7 +3,7 @@ git-merge-one-file(1)
 
 NAME
 ----
-git-merge-one-file - The standard helper program to use with "git-merge-index"
+git-merge-one-file - The standard helper program to use with git-merge-index
 
 
 SYNOPSIS
index bebf30ad3db951c0a0bad01ebee619821781eb39..e53ff4b4e7464079ac6e19f2bceafcb44044fb39 100644 (file)
@@ -3,17 +3,18 @@ git-merge(1)
 
 NAME
 ----
-git-merge - Grand Unified Merge Driver
+git-merge - Join two or more development histories together
 
 
 SYNOPSIS
 --------
-'git-merge' [-n] [--no-commit] [-s <strategy>]... <msg> <head> <remote> <remote>...
-
+[verse]
+'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]...
+       [-m <msg>] <remote> <remote>...
 
 DESCRIPTION
 -----------
-This is the top-level user interface to the merge machinery
+This is the top-level interface to the merge machinery
 which drives multiple merge strategy scripts.
 
 
@@ -27,10 +28,11 @@ include::merge-options.txt[]
        to give a good default for automated `git-merge` invocations.
 
 <head>::
-       our branch head commit.
+       Our branch head commit.  This has to be `HEAD`, so new
+       syntax does not require it
 
 <remote>::
-       other branch head merged into our branch.  You need at
+       Other branch head merged into our branch.  You need at
        least one <remote>.  Specifying more than one <remote>
        obviously means you are trying an Octopus.
 
index 207c43a631ba6be8eb85e93b67a48fc90819b770..6756b76bb11bc17279c3f20c16d7b4ba131f5832 100644 (file)
@@ -3,7 +3,7 @@ git-mv(1)
 
 NAME
 ----
-git-mv - Move or rename a file, directory or symlink
+git-mv - Move or rename a file, a directory, or a symlink
 
 
 SYNOPSIS
index ee9e8fa9090f88d1eb45ddbdb73866e0dc1fd53d..6edb9f12b87e2bc24fb69c8615e1798bb813e14e 100644 (file)
@@ -93,7 +93,7 @@ perforce branch into a branch named "jammy", like so:
 ------------
 $ mkdir -p /home/sean/import/jam
 $ cd /home/sean/import/jam
-$ git init-db
+$ git init
 $ git p4import //public/jam jammy
 ------------
 
index 7d54b17e37b1065444b9ce100013e4a505af218d..94bbea0db2caf32635cfa8fc199e8150b1cd05f2 100644 (file)
@@ -3,7 +3,7 @@ git-pack-redundant(1)
 
 NAME
 ----
-git-pack-redundant - Program used to find redundant pack files
+git-pack-redundant - Find redundant pack files
 
 
 SYNOPSIS
@@ -21,7 +21,7 @@ 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.
 
-git-fsck-objects --full --unreachable | cut -d ' ' -f3 | \
+git-fsck --full --unreachable | cut -d ' ' -f3 | \
 git-pack-redundant --all | xargs rm
 
 OPTIONS
index 5da51057713a5553db26d871cc561b0df7ca94ff..a20fc7de40083b1c40e30a1ab68de81e94d91737 100644 (file)
@@ -7,7 +7,7 @@ git-pack-refs - Pack heads and tags for efficient repository access
 
 SYNOPSIS
 --------
-'git-pack-refs' [--all] [--prune]
+'git-pack-refs' [--all] [--no-prune]
 
 DESCRIPTION
 -----------
@@ -29,21 +29,33 @@ file and used if found.
 Subsequent updates to branches always creates new file under
 `$GIT_DIR/refs` hierarchy.
 
+A recommended practice to deal with a repository with too many
+refs is to pack its refs with `--all --prune` once, and
+occasionally run `git-pack-refs \--prune`.  Tags are by
+definition stationary and are not expected to change.  Branch
+heads will be packed with the initial `pack-refs --all`, but
+only the currently active branch heads will become unpacked,
+and next `pack-refs` (without `--all`) will leave them
+unpacked.
+
+
 OPTIONS
 -------
 
 \--all::
 
-The command by default packs all tags and leaves branch tips
+The command by default packs all tags and refs that are already
+packed, and leaves other refs
 alone.  This is because branches are expected to be actively
 developed and packing their tips does not help performance.
 This option causes branch tips to be packed as well.  Useful for
 a repository with many branches of historical interests.
 
-\--prune::
+\--no-prune::
+
+The command usually removes loose refs under `$GIT_DIR/refs`
+hierarchy after packing them.  This option tells it not to.
 
-After packing the refs, remove loose refs under `$GIT_DIR/refs`
-hierarchy.  This should probably become default.
 
 Author
 ------
index fc27afe26d9d174c350e00804071630f879c663c..11b1f4d2e2b278eeb51cec2c2de0c2dbce452272 100644 (file)
@@ -3,7 +3,7 @@ git-parse-remote(1)
 
 NAME
 ----
-git-parse-remote - Routines to help parsing $GIT_DIR/remotes/
+git-parse-remote - Routines to help parsing remote repository access parameters
 
 
 SYNOPSIS
@@ -14,7 +14,8 @@ DESCRIPTION
 -----------
 This script is included in various scripts to supply
 routines to parse files under $GIT_DIR/remotes/ and
-$GIT_DIR/branches/.
+$GIT_DIR/branches/ and configuration variables that are related
+to fetching, pulling and pushing.
 
 The primary entry points are:
 
@@ -25,7 +26,8 @@ get_remote_refs_for_fetch::
        (e.g. `refs/heads/foo`).  When `<refspec>...` is empty
        the returned list of refs consists of the defaults
        for the given `<repo>`, if specified in
-       `$GIT_DIR/remotes/` or `$GIT_DIR/branches/`.
+       `$GIT_DIR/remotes/`, `$GIT_DIR/branches/`, or `remote.*.fetch`
+       configuration.
 
 get_remote_refs_for_push::
        Given the list of user-supplied `<repo> <refspec>...`,
index 5389097f73c03347263c3af44497011561c594f2..a7e9fd021a4cc1e467b2df7dd7e06d79a5182c7a 100644 (file)
@@ -3,7 +3,7 @@ git-patch-id(1)
 
 NAME
 ----
-git-patch-id - Generate a patch ID
+git-patch-id - Compute unique ID for a patch
 
 SYNOPSIS
 --------
index a00060c507516fa7a08eb40c1fde298538d5ef10..74f37bd9040200c0dbc82c67b45540e873d4649e 100644 (file)
@@ -3,12 +3,12 @@ git-peek-remote(1)
 
 NAME
 ----
-git-peek-remote - Lists the references in a remote repository
+git-peek-remote - List the references in a remote repository
 
 
 SYNOPSIS
 --------
-'git-peek-remote' [--exec=<git-upload-pack>] [<host>:]<directory>
+'git-peek-remote' [--upload-pack=<git-upload-pack>] [<host>:]<directory>
 
 DESCRIPTION
 -----------
@@ -17,7 +17,7 @@ stores them in the local repository under the same name.
 
 OPTIONS
 -------
---exec=<git-upload-pack>::
+\--upload-pack=<git-upload-pack>::
        Use this to specify the path to 'git-upload-pack' on the
        remote side, if it is not found on your $PATH. Some
        installations of sshd ignores the user's environment
@@ -29,6 +29,9 @@ OPTIONS
        shells, but prefer having a lean .bashrc file (they set most of
        the things up in .bash_profile).
 
+\--exec=<git-upload-pack>::
+       Same \--upload-pack=<git-upload-pack>.
+
 <host>::
        A remote host that houses the repository.  When this
        part is specified, 'git-upload-pack' is invoked via
index 234882685d923d632c4e09a38cb9ba18439dda4a..310033e4605d20b99c21a769e79b25b64abbb618 100644 (file)
@@ -3,13 +3,12 @@ git-prune-packed(1)
 
 NAME
 ----
-git-prune-packed - Program used to remove the extra object files that are now
-residing in a pack file.
+git-prune-packed - Remove extra objects that are already in pack files
 
 
 SYNOPSIS
 --------
-'git-prune-packed' [-n]
+'git-prune-packed' [-n] [-q]
 
 
 DESCRIPTION
@@ -32,6 +31,9 @@ OPTIONS
         Don't actually remove any objects, only show those that would have been
         removed.
 
+-q::
+       Squelch the progress indicator.
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org>
index a11e3030943ab3ffd10afa017640826795809d05..0b44f3015d1ce8c600eb274a0f74c53868949658 100644 (file)
@@ -13,7 +13,7 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-This runs `git-fsck-objects --unreachable` using all the refs
+This runs `git-fsck --unreachable` using all the refs
 available in `$GIT_DIR/refs`, optionally with additional set of
 objects specified on the command line, and prunes all
 objects unreachable from any of these head objects from the object database.
index 2a5aea73bafedd62f20cecb1fbf5069b515022bd..94478ed94d70c597c8a024afe79b456a66d40a35 100644 (file)
@@ -3,7 +3,7 @@ git-pull(1)
 
 NAME
 ----
-git-pull - Pull and merge from another repository or a local branch
+git-pull - Fetch from and merge with another repository or a local branch
 
 
 SYNOPSIS
@@ -33,21 +33,86 @@ include::urls.txt[]
 
 include::merge-strategies.txt[]
 
+DEFAULT BEHAVIOUR
+-----------------
+
+Often people use `git pull` without giving any parameter.
+Traditionally, this has been equivalent to saying `git pull
+origin`.  However, when configuration `branch.<name>.remote` is
+present while on branch `<name>`, that value is used instead of
+`origin`.
+
+In order to determine what URL to use to fetch from, the value
+of the configuration `remote.<origin>.url` is consulted
+and if there is not any such variable, the value on `URL: ` line
+in `$GIT_DIR/remotes/<origin>` file is used.
+
+In order to determine what remote branches to fetch (and
+optionally store in the tracking branches) when the command is
+run without any refspec parameters on the command line, values
+of the configuration variable `remote.<origin>.fetch` are
+consulted, and if there aren't any, `$GIT_DIR/remotes/<origin>`
+file is consulted and its `Pull: ` lines are used.
+In addition to the refspec formats described in the OPTIONS
+section, you can have a globbing refspec that looks like this:
+
+------------
+refs/heads/*:refs/remotes/origin/*
+------------
+
+A globbing refspec must have a non-empty RHS (i.e. must store
+what were fetched in tracking branches), and its LHS and RHS
+must end with `/*`.  The above specifies that all remote
+branches are tracked using tracking branches in
+`refs/remotes/origin/` hierarchy under the same name.
+
+The rule to determine which remote branch to merge after
+fetching is a bit involved, in order not to break backward
+compatibility.
+
+If explicit refspecs were given on the command
+line of `git pull`, they are all merged.
+
+When no refspec was given on the command line, then `git pull`
+uses the refspec from the configuration or
+`$GIT_DIR/remotes/<origin>`.  In such cases, the following
+rules apply:
+
+. If `branch.<name>.merge` configuration for the current
+  branch `<name>` exists, that is the name of the branch at the
+  remote site that is merged.
+
+. If the refspec is a globbing one, nothing is merged.
+
+. Otherwise the remote branch of the first refspec is merged.
+
+
 EXAMPLES
 --------
 
 git pull, git pull origin::
-       Fetch the default head from the repository you cloned
-       from and merge it into your current branch.
+       Update the remote-tracking branches for the repository
+       you cloned from, then merge one of them into your
+       current branch.  Normally the branch merged in is
+       the HEAD of the remote repository, but the choice is
+       determined by the branch.<name>.remote and
+       branch.<name>.merge options; see gitlink:git-config[1]
+       for details.
+
+git pull origin next::
+       Merge into the current branch the remote branch `next`;
+       leaves a copy of `next` temporarily in FETCH_HEAD, but
+       does not update any remote-tracking branches.
+
+git pull . fixes enhancements::
+       Bundle local branch `fixes` and `enhancements` on top of
+       the current branch, making an Octopus merge.  This `git pull .`
+       syntax is equivalent to `git merge`.
 
 git pull -s ours . obsolete::
        Merge local branch `obsolete` into the current branch,
        using `ours` merge strategy.
 
-git pull . fixes enhancements::
-       Bundle local branch `fixes` and `enhancements` on top of
-       the current branch, making an Octopus merge.
-
 git pull --no-commit . maint::
        Merge local branch `maint` into the current branch, but
        do not make a commit automatically.  This can be used
@@ -61,48 +126,19 @@ release/version name would be acceptable.
 Command line pull of multiple branches from one repository::
 +
 ------------------------------------------------
-$ cat .git/remotes/origin
-URL: git://git.kernel.org/pub/scm/git/git.git
-Pull: master:origin
-
 $ git checkout master
-$ git fetch origin master:origin +pu:pu maint:maint
-$ git pull . origin
+$ git fetch origin +pu:pu maint:tmp
+$ git pull . tmp
 ------------------------------------------------
 +
-Here, a typical `.git/remotes/origin` file from a
-`git-clone` operation is used in combination with
-command line options to `git-fetch` to first update
-multiple branches of the local repository and then
-to merge the remote `origin` branch into the local
-`master` branch.  The local `pu` branch is updated
-even if it does not result in a fast forward update.
-Here, the pull can obtain its objects from the local
-repository using `.`, as the previous `git-fetch` is
-known to have already obtained and made available
-all the necessary objects.
-
-
-Pull of multiple branches from one repository using `.git/remotes` file::
+This updates (or creates, as necessary) branches `pu` and `tmp`
+in the local repository by fetching from the branches
+(respectively) `pu` and `maint` from the remote repository.
 +
-------------------------------------------------
-$ cat .git/remotes/origin
-URL: git://git.kernel.org/pub/scm/git/git.git
-Pull: master:origin
-Pull: +pu:pu
-Pull: maint:maint
-
-$ git checkout master
-$ git pull origin
-------------------------------------------------
+The `pu` branch will be updated even if it is does not
+fast-forward; the others will not be.
 +
-Here, a typical `.git/remotes/origin` file from a
-`git-clone` operation has been hand-modified to include
-the branch-mapping of additional remote and local
-heads directly.  A single `git-pull` operation while
-in the `master` branch will fetch multiple heads and
-merge the remote `origin` head into the current,
-local `master` branch.
+The final command then merges the newly fetched `tmp` into master.
 
 
 If you tried a pull which resulted in a complex conflicts and
@@ -112,7 +148,7 @@ gitlink:git-reset[1].
 
 SEE ALSO
 --------
-gitlink:git-fetch[1], gitlink:git-merge[1]
+gitlink:git-fetch[1], gitlink:git-merge[1], gitlink:git-config[1]
 
 
 Author
index d4ae99fa530a50fd354dc54004a7342afd693947..f8cc2b5432b448dcf3686ecee68cba796064bbe4 100644 (file)
@@ -8,7 +8,7 @@ git-push - Update remote refs along with associated objects
 
 SYNOPSIS
 --------
-'git-push' [--all] [--tags] [-f | --force] <repository> <refspec>...
+'git-push' [--all] [--tags] [--receive-pack=<git-receive-pack>] [--repo=all] [-f | --force] [-v] [<repository> <refspec>...]
 
 DESCRIPTION
 -----------
@@ -49,12 +49,14 @@ corresponding remotes file---see below), then all the
 refs that exist both on the local side and on the remote
 side are updated.
 +
-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>`.
-* 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 is equivalent to
+<ref>`:`<ref>, hence updates <ref> in the destination from <ref>
+in the source.
++
+Pushing an empty <src> allows you to delete the <dst> ref from
+the remote repository.
 
 \--all::
        Instead of naming each ref to push, specifies that all
@@ -65,17 +67,39 @@ Some short-cut notations are also supported.
        addition to refspecs explicitly listed on the command
        line.
 
+\--receive-pack=<git-receive-pack>::
+       Path to the 'git-receive-pack' program on the remote
+       end.  Sometimes useful when pushing to a remote
+       repository over ssh, and you do not have the program in
+       a directory on the default $PATH.
+
+\--exec=<git-receive-pack>::
+       Same as \--receive-pack=<git-receive-pack>.
+
 -f, \--force::
        Usually, the command refuses to update a remote ref that is
        not a descendant 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.
 
+\--repo=<repo>::
+       When no repository is specified the command defaults to
+       "origin"; this overrides it.
+
+\--thin, \--no-thin::
+       These options are passed to `git-send-pack`.  Thin
+       transfer spends extra cycles to minimize the number of
+       objects to be sent and meant to be used on slower connection.
+
+-v::
+       Run verbosely.
+
 include::urls.txt[]
 
 Author
 ------
-Written by Junio C Hamano <junkio@cox.net>
+Written by Junio C Hamano <junkio@cox.net>, later rewritten in C
+by Linus Torvalds <torvalds@osdl.org>
 
 Documentation
 --------------
index 11bd9c0adc5517ef1aadc76abdb8dfb0827c27b0..0ff2890c7fb76155e08eaf9e361bf1eb6cf50f25 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]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
 
 
 DESCRIPTION
@@ -71,6 +71,20 @@ OPTIONS
        directory.  Note that the `<prefix>/` value must end
        with a slash.
 
+--exclude-per-directory=<gitignore>::
+       When running the command with `-u` and `-m` options, the
+       merge result may need to overwrite paths that are not
+       tracked in the current branch.  The command usually
+       refuses to proceed with the merge to avoid losing such a
+       path.  However this safety valve sometimes gets in the
+       way.  For example, it often happens that the other
+       branch added a file that used to be a generated file in
+       your branch, and the safety valve triggers when you try
+       to switch to that branch after you ran `make` but before
+       running `make clean` to remove the generated file.  This
+       option tells the command to read per-directory exclude
+       file (usually '.gitignore') and allows such an untracked
+       but explicitly ignored file to be overwritten.
 
 <tree-ish#>::
        The id of the tree object(s) to be read/merged.
index 03e867a403a034e49e63fc0123970b44a199e8b5..f2ef1f7dc093432e8b6c5644cbac5c5e857cb0ca 100644 (file)
@@ -3,11 +3,11 @@ git-rebase(1)
 
 NAME
 ----
-git-rebase - Rebase local commits to a new head
+git-rebase - Forward-port local commits to the updated upstream head
 
 SYNOPSIS
 --------
-'git-rebase' [-v] [--merge] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-v] [--merge] [-C<n>] [--onto <newbase>] <upstream> [<branch>]
 
 'git-rebase' --continue | --skip | --abort
 
@@ -114,6 +114,27 @@ would result in:
 
 This is useful when topicB does not depend on topicA.
 
+A range of commits could also be removed with rebase.  If we have
+the following situation:
+
+------------
+    E---F---G---H---I---J  topicA
+------------
+
+then the command
+
+    git-rebase --onto topicA~5 topicA~2 topicA
+
+would result in the removal of commits F and G:
+
+------------
+    E---H'---I'---J'  topicA
+------------
+
+This is useful if F and G were flawed in some way, or should not be
+part of topicA.  Note that the argument to --onto and the <upstream>
+parameter can be any valid commit-ish.
+
 In case of conflict, git-rebase will stop at the first problematic commit
 and leave conflict markers in the tree.  You can use git diff to locate
 the markers (<<<<<<) and make edits to resolve the conflict.  For each
@@ -141,10 +162,12 @@ OPTIONS
 <newbase>::
        Starting point at which to create the new commits. If the
        --onto option is not specified, the starting point is
-       <upstream>.
+       <upstream>.  May be any valid commit, and not just an
+       existing branch name.
 
 <upstream>::
-       Upstream branch to compare against.
+       Upstream branch to compare against.  May be any valid commit,
+       not just an existing branch name.
 
 <branch>::
        Working branch; defaults to HEAD.
@@ -173,6 +196,12 @@ OPTIONS
 -v, \--verbose::
        Display a diffstat of what changed upstream since the last rebase.
 
+-C<n>::
+       Ensure at least <n> lines of surrounding context match before
+       and after each change.  When fewer lines of surrounding
+       context exist they all must match.  By default no context is
+       ever ignored.
+
 include::merge-strategies.txt[]
 
 NOTES
index 0dfadc2a32a44ef1e026ae95c8cc5906ec4d4d5c..10e8c46c4c0787e6a66304ca583890a438d78bb3 100644 (file)
@@ -3,7 +3,7 @@ git-receive-pack(1)
 
 NAME
 ----
-git-receive-pack - Receive what is pushed into it
+git-receive-pack - Receive what is pushed into the repository
 
 
 SYNOPSIS
diff --git a/Documentation/git-reflog.txt b/Documentation/git-reflog.txt
new file mode 100644 (file)
index 0000000..1e343bc
--- /dev/null
@@ -0,0 +1,68 @@
+git-reflog(1)
+=============
+
+NAME
+----
+git-reflog - Manage reflog information
+
+
+SYNOPSIS
+--------
+'git reflog' <subcommand> <options>
+
+DESCRIPTION
+-----------
+The command takes various subcommands, and different options
+depending on the subcommand:
+
+[verse]
+git reflog expire [--dry-run] [--stale-fix]
+       [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...
+
+git reflog [show] [log-options]
+
+Reflog is a mechanism to record when the tip of branches are
+updated.  This command is to manage the information recorded in it.
+
+The subcommand "expire" is used to prune older reflog entries.
+Entries older than `expire` time, or entries older than
+`expire-unreachable` time and are not reachable from the current
+tip, are removed from the reflog.  This is typically not used
+directly by the end users -- instead, see gitlink:git-gc[1].
+
+The subcommand "show" (which is also the default, in the absense of any
+subcommands) will take all the normal log options, and show the log of
+the current branch. It is basically an alias for 'git log -g --abbrev-commit
+--pretty=oneline', see gitlink:git-log[1].
+
+
+OPTIONS
+-------
+
+--expire=<time>::
+       Entries older than this time are pruned.  Without the
+       option it is taken from configuration `gc.reflogExpire`,
+       which in turn defaults to 90 days.
+
+--expire-unreachable=<time>::
+       Entries older than this time and are not reachable from
+       the current tip of the branch are pruned.  Without the
+       option it is taken from configuration
+       `gc.reflogExpireUnreachable`, which in turn defaults to
+       30 days.
+
+--all::
+       Instead of listing <refs> explicitly, prune all refs.
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+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-remote.txt b/Documentation/git-remote.txt
new file mode 100644 (file)
index 0000000..a60c31a
--- /dev/null
@@ -0,0 +1,96 @@
+git-remote(1)
+============
+
+NAME
+----
+git-remote - manage set of tracked repositories
+
+
+SYNOPSIS
+--------
+[verse]
+'git-remote'
+'git-remote' add <name> <url>
+'git-remote' show <name>
+'git-remote' prune <name>
+
+DESCRIPTION
+-----------
+
+Manage the set of repositories ("remotes") whose branches you track.
+
+
+COMMANDS
+--------
+
+With no arguments, shows a list of existing remotes.  Several
+subcommands are available to perform operations on the remotes.
+
+'add'::
+
+Adds a remote named <name> for the repository at
+<url>.  The command `git fetch <name>` can then be used to create and
+update remote-tracking branches <name>/<branch>.
+
+'show'::
+
+Gives some information about the remote <name>.
+
+'prune'::
+
+Deletes all stale tracking branches under <name>.
+These stale branches have already been removed from the remote repository
+referenced by <name>, but are still locally available in "remotes/<name>".
+
+
+DISCUSSION
+----------
+
+The remote configuration is achieved using the `remote.origin.url` and
+`remote.origin.fetch` configuration variables.  (See
+gitlink:git-config[1]).
+
+Examples
+--------
+
+Add a new remote, fetch, and check out a branch from it:
+
+------------
+$ git remote
+origin
+$ git branch -r
+origin/master
+$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
+$ git remote
+linux-nfs
+origin
+$ git fetch
+* refs/remotes/linux-nfs/master: storing branch 'master' ...
+  commit: bf81b46
+$ git branch -r
+origin/master
+linux-nfs/master
+$ git checkout -b nfs linux-nfs/master
+...
+------------
+
+See Also
+--------
+gitlink:git-fetch[1]
+gitlink:git-branch[1]
+gitlink:git-config[1]
+
+Author
+------
+Written by Junio Hamano
+
+
+Documentation
+--------------
+Documentation by J. Bruce Fields and the git-list <git@vger.kernel.org>.
+
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
index 0fa47e3b018815ffa9de23725737fe2b599eaead..d39abc126dec70108625af13d11541b771c8e85d 100644 (file)
@@ -3,8 +3,7 @@ git-repack(1)
 
 NAME
 ----
-git-repack - Script used to pack a repository from a collection of
-objects into pack files.
+git-repack - Pack unpacked objects in a repository
 
 
 SYNOPSIS
@@ -31,9 +30,9 @@ OPTIONS
        Instead of incrementally packing the unpacked objects,
        pack everything available into a single pack.
        Especially useful when packing a repository that is used
-       for a private development and there no need to worry
-       about people fetching via dumb protocols from it.  Use
-       with '-d'.
+       for private development and there is no need to worry
+       about people fetching via dumb file transfer protocols
+       from it.  Use with '-d'.
 
 -d::
        After packing, if the newly created packs make some
index 8199615dde6630124c155bb4a296603dc8829c83..2deba31763eb516a34d419cee5a98f7413f7f588 100644 (file)
@@ -3,204 +3,16 @@ git-repo-config(1)
 
 NAME
 ----
-git-repo-config - Get and set repository or global options.
+git-repo-config - Get and set repository or global options
 
 
 SYNOPSIS
 --------
-[verse]
-'git-repo-config' [--global] [type] name [value [value_regex]]
-'git-repo-config' [--global] [type] --replace-all name [value [value_regex]]
-'git-repo-config' [--global] [type] --get name [value_regex]
-'git-repo-config' [--global] [type] --get-all name [value_regex]
-'git-repo-config' [--global] [type] --unset name [value_regex]
-'git-repo-config' [--global] [type] --unset-all name [value_regex]
-'git-repo-config' [--global] -l | --list
+'git-repo-config' ...
 
-DESCRIPTION
------------
-You can query/set/replace/unset options with this command. The name is
-actually the section and the key separated by a dot, and the value will be
-escaped.
-
-If you want to set/unset an option which can occur on multiple
-lines, a POSIX regexp `value_regex` needs to be given.  Only the
-existing values that match the regexp are updated or unset.  If
-you want to handle the lines that do *not* match the regex, just
-prepend a single exclamation mark in front (see EXAMPLES).
-
-The type specifier can be either '--int' or '--bool', which will make
-'git-repo-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). If no type specifier is passed,
-no checks or transformations are performed on the value.
-
-This command will fail if:
-
-. The .git/config file is invalid,
-. Can not write to .git/config,
-. no section was provided,
-. the section or key is invalid,
-. you try to unset an option which does not exist,
-. you try to unset/set an option for which multiple lines match, or
-. you use --global option without $HOME being properly set.
-
-
-OPTIONS
--------
-
---replace-all::
-       Default behavior is to replace at most one line. This replaces
-       all lines matching the key (and optionally the value_regex).
-
---get::
-       Get the value for a given key (optionally filtered by a regex
-       matching the value). Returns error code 1 if the key was not
-       found and error code 2 if multiple key values were found.
-
---get-all::
-       Like get, but does not fail if the number of values for the key
-       is not exactly one.
-
---get-regexp::
-       Like --get-all, but interprets the name as a regular expression.
-
---global::
-       Use global ~/.gitconfig file rather than the repository .git/config.
-
---unset::
-       Remove the line matching the key from config file.
 
---unset-all::
-       Remove all matching lines from config file.
-
--l, --list::
-       List all variables set in config file.
-
-
-ENVIRONMENT
+DESCRIPTION
 -----------
 
-GIT_CONFIG::
-       Take the configuration from the given file instead of .git/config.
-       Using the "--global" option forces this to ~/.gitconfig.
-
-GIT_CONFIG_LOCAL::
-       Currently the same as $GIT_CONFIG; when Git will support global
-       configuration files, this will cause it to take the configuration
-       from the global configuration file in addition to the given file.
-
-
-EXAMPLE
--------
-
-Given a .git/config like this:
-
-       #
-       # This is the config file, and
-       # a '#' or ';' character indicates
-       # a comment
-       #
-
-       ; core variables
-       [core]
-               ; Don't trust file modes
-               filemode = false
-
-       ; Our diff algorithm
-       [diff]
-               external = "/usr/local/bin/gnu-diff -u"
-               renames = true
-
-       ; 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
-
-------------
-% git repo-config core.filemode true
-------------
-
-The hypothetical proxy command entries actually have a postfix to discern
-what URL they apply to. Here is how to change the entry for kernel.org
-to "ssh".
-
-------------
-% git repo-config core.gitproxy '"ssh" for kernel.org' 'for kernel.org$'
-------------
-
-This makes sure that only the key/value pair for kernel.org is replaced.
-
-To delete the entry for renames, do
-
-------------
-% git repo-config --unset diff.renames
-------------
-
-If you want to delete an entry for a multivar (like core.gitproxy above),
-you have to provide a regex matching the value of exactly one line.
-
-To query the value for a given key, do
-
-------------
-% git repo-config --get core.filemode
-------------
-
-or
-
-------------
-% git repo-config core.filemode
-------------
-
-or, to query a multivar:
-
-------------
-% git repo-config --get core.gitproxy "for kernel.org$"
-------------
-
-If you want to know all the values for a multivar, do:
-
-------------
-% git repo-config --get-all core.gitproxy
-------------
-
-If you like to live dangerous, you can replace *all* core.gitproxy by a
-new one with
-
-------------
-% git repo-config --replace-all core.gitproxy ssh
-------------
-
-However, if you really only want to replace the line for the default proxy,
-i.e. the one without a "for ..." postfix, do something like this:
-
-------------
-% git repo-config core.gitproxy ssh '! for '
-------------
-
-To actually match only values with an exclamation mark, you have to
-
-------------
-% git repo-config section.key value '[!]'
-------------
-
-
-include::config.txt[]
-
-
-Author
-------
-Written by Johannes Schindelin <Johannes.Schindelin@gmx.de>
-
-Documentation
---------------
-Documentation by Johannes Schindelin, Petr Baudis and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
-
+This is a synonym for gitlink:git-config[1].  Please refer to the
+documentation of that command.
index 8b6b6512371fd67fbb56d06cf29e826d3ef69d56..139b6eb7733aaf246af8ce77f6483fa15e5de2b0 100644 (file)
@@ -3,12 +3,11 @@ git-rerere(1)
 
 NAME
 ----
-git-rerere - Reuse recorded resolve
+git-rerere - Reuse recorded resolution of conflicted merges
 
 SYNOPSIS
 --------
-'git-rerere'
-
+'git-rerere' [clear|diff|status|gc]
 
 DESCRIPTION
 -----------
@@ -27,6 +26,42 @@ results and applying the previously recorded hand resolution.
 You need to create `$GIT_DIR/rr-cache` directory to enable this
 command.
 
+
+COMMANDS
+--------
+
+Normally, git-rerere is run without arguments or user-intervention.
+However, it has several commands that allow it to interact with
+its working state.
+
+'clear'::
+
+This resets the metadata used by rerere if a merge resolution is to be
+is aborted.  Calling gitlink:git-am[1] --skip or gitlink:git-rebase[1]
+[--skip|--abort] will automatically invoke this command.
+
+'diff'::
+
+This displays diffs for the current state of the resolution.  It is
+useful for tracking what has changed while the user is resolving
+conflicts.  Additional arguments are passed directly to the system
+diff(1) command installed in PATH.
+
+'status'::
+
+Like diff, but this only prints the filenames that will be tracked
+for resolutions.
+
+'gc'::
+
+This command is used to prune records of conflicted merge that
+occurred long time ago.  By default, conflicts older than 15
+days that you have not recorded their resolution, and conflicts
+older than 60 days, are pruned.  These are controlled with
+`gc.rerereunresolved` and `gc.rerereresolved` configuration
+variables.
+
+
 DISCUSSION
 ----------
 
@@ -46,7 +81,7 @@ One way to do it is to pull master into the topic branch:
 
 ------------
        $ git checkout topic
-       $ git pull . master
+       $ git merge master
 
               o---*---o---+ topic
              /           /
@@ -68,10 +103,10 @@ in which case the final commit graph would look like this:
 
 ------------
        $ git checkout topic
-       $ git pull . master
+       $ git merge master
        $ ... work on both topic and master branches
        $ git checkout master
-       $ git pull . topic
+       $ git merge topic
 
               o---*---o---+---o---o topic
              /           /         \
@@ -91,11 +126,11 @@ top of the tip before the test merge:
 
 ------------
        $ git checkout topic
-       $ git pull . master
+       $ git merge master
        $ git reset --hard HEAD^ ;# rewind the test merge
        $ ... work on both topic and master branches
        $ git checkout master
-       $ git pull . topic
+       $ git merge topic
 
               o---*---o-------o---o topic
              /                     \
index 73a0ffc41085e87fbc996fab4baa25ace1951460..04475a92168feb98e0de1e386476ca702fb6c41b 100644 (file)
@@ -7,7 +7,9 @@ git-reset - Reset current HEAD to the specified state
 
 SYNOPSIS
 --------
-'git-reset' [--mixed | --soft | --hard] [<commit-ish>]
+[verse]
+'git-reset' [--mixed | --soft | --hard] [<commit>]
+'git-reset' [--mixed] <commit> [--] <paths>...
 
 DESCRIPTION
 -----------
@@ -21,6 +23,10 @@ the undo in the history.
 If you want to undo a commit other than the latest on a branch,
 gitlink:git-revert[1] is your friend.
 
+The second form with 'paths' is used to revert selected paths in
+the index from a given commit, without moving HEAD.
+
+
 OPTIONS
 -------
 --mixed::
@@ -31,15 +37,15 @@ OPTIONS
 --soft::
        Does not touch the index file nor the working tree at all, but
        requires them to be in a good order. This leaves all your changed
-       files "Updated but not checked in", as gitlink:git-status[1] would
+       files "Added but not yet committed", as gitlink:git-status[1] would
        put it.
 
 --hard::
        Matches the working tree and index to that of the tree being
        switched to. Any changes to tracked files in the working tree
-       since <commit-ish> are lost.
+       since <commit> are lost.
 
-<commit-ish>::
+<commit>::
        Commit to make the current HEAD.
 
 Examples
@@ -115,10 +121,6 @@ Undo a merge or pull::
 +
 ------------
 $ git pull                         <1>
-Trying really trivial in-index merge...
-fatal: Merge requires file-level merging
-Nope.
-...
 Auto-merging nitfol
 CONFLICT (content): Merge conflict in nitfol
 Automatic merge failed/prevented; fix up by hand
diff --git a/Documentation/git-resolve.txt b/Documentation/git-resolve.txt
deleted file mode 100644 (file)
index 4e57c2b..0000000
+++ /dev/null
@@ -1,36 +0,0 @@
-git-resolve(1)
-==============
-
-NAME
-----
-git-resolve - Merge two commits
-
-
-SYNOPSIS
---------
-'git-resolve' <current> <merged> <message>
-
-DESCRIPTION
------------
-Given two commits and a merge message, merge the <merged> commit
-into <current> commit, with the commit log message <message>.
-
-When <current> is a descendant of <merged>, or <current> is an
-ancestor of <merged>, no new commit is created and the <message>
-is ignored.  The former is informally called "already up to
-date", and the latter is often called "fast forward".
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org> and
-Dan Holmsand <holmsand@gmail.com>.
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
-
index 00a95e249fe82f2eb3d53dcc541559dceb3e8709..c742117595cd8ae5ab371a7ba21513a419af7996 100644 (file)
@@ -10,6 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-rev-list' [ \--max-count=number ]
+            [ \--skip=number ]
             [ \--max-age=timestamp ]
             [ \--min-age=timestamp ]
             [ \--sparse ]
@@ -20,11 +21,13 @@ SYNOPSIS
             [ \--stdin ]
             [ \--topo-order ]
             [ \--parents ]
+            [ \--encoding[=<encoding>] ]
             [ \--(author|committer|grep)=<pattern> ]
             [ [\--objects | \--objects-edge] [ \--unpacked ] ]
             [ \--pretty | \--header ]
             [ \--bisect ]
             [ \--merge ]
+            [ \--walk-reflogs ]
             <commit>... [ \-- <paths>... ]
 
 DESCRIPTION
@@ -79,11 +82,7 @@ Using these options, gitlink:git-rev-list[1] will act similar to the
 more specialized family of commit log tools: gitlink:git-log[1],
 gitlink:git-show[1], and gitlink:git-whatchanged[1]
 
---pretty[='<format>']::
-
-       Pretty print the contents of the commit logs in a given format,
-       where '<format>' can be one of 'raw', 'medium', 'short', 'full',
-       and 'oneline'. When left out the format default to 'medium'.
+include::pretty-formats.txt[]
 
 --relative-date::
 
@@ -143,6 +142,10 @@ limiting may be applied.
 
        Limit the number of commits output.
 
+--skip='number'::
+
+       Skip 'number' commits before starting to show the commit output.
+
 --since='date', --after='date'::
 
        Show commits more recent than a specific date.
@@ -188,6 +191,22 @@ limiting may be applied.
        In addition to the '<commit>' listed on the command
        line, read them from the standard input.
 
+-g, --walk-reflogs::
+
+       Instead of walking the commit ancestry chain, walk
+       reflog entries from the most recent one to older ones.
+       When this option is used you cannot specify commits to
+       exclude (that is, '{caret}commit', 'commit1..commit2',
+       nor 'commit1...commit2' notations cannot be used).
++
+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
+used in the output.  When the starting commit is specified as
+'commit@{now}', output also uses 'commit@{timestamp}' notation
+instead.  Under '\--pretty=oneline', the commit message is
+prefixed with this information on the same line.
+
 --merge::
 
        After a failed merge, show refs that touch files having a
index 4eaf5a0d1ea26798298c4f572c29d90af9dc7cf1..4041a1607081ee6e8d93065c67fc72a513c20b17 100644 (file)
@@ -152,6 +152,18 @@ blobs contained in a commit.
   used immediately following a ref name and the ref must have an
   existing log ($GIT_DIR/logs/<ref>).
 
+* A ref followed by the suffix '@' with an ordinal specification
+  enclosed in a brace pair (e.g. '\{1\}', '\{15\}') to specify
+  the n-th prior value of that ref.  For example 'master@\{1\}'
+  is the immediate prior value of 'master' while 'master@\{5\}'
+  is the 5th prior value of 'master'. This suffix may only be used
+  immediately following a ref name and the ref must have an existing
+  log ($GIT_DIR/logs/<ref>).
+
+* You can use the '@' construct with an empty ref part to get at a
+  reflog of the current branch. For example, if you are on the
+  branch 'blabla', then '@\{1\}' means the same as 'blabla@\{1\}'.
+
 * A suffix '{caret}' to a revision parameter means the first parent of
   that commit object.  '{caret}<n>' means the <n>th parent (i.e.
   'rev{caret}'
index 71f7815d65a38db70a5176a50989300b39684cda..8081bbaffa8d3710b644c744b9f81d459938a20e 100644 (file)
@@ -19,6 +19,8 @@ OPTIONS
 -------
 <commit>::
        Commit to revert.
+       For a more complete list of ways to spell commit names, see
+       "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
 
 -e|--edit::
        With this option, `git-revert` will let you edit the commit
index 66fc478f5736957d56de10ddc69c5c0fd9aad9c6..6feebc0400b86407a118840d9a6cc994a94715d6 100644 (file)
@@ -7,72 +7,71 @@ git-rm - Remove files from the working tree and from the index
 
 SYNOPSIS
 --------
-'git-rm' [-f] [-n] [-v] [--] <file>...
+'git-rm' [-f] [-n] [-r] [--cached] [--] <file>...
 
 DESCRIPTION
 -----------
-A convenience wrapper for git-update-index --remove. For those coming
-from cvs, git-rm provides an operation similar to "cvs rm" or "cvs
-remove".
+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).
 
 
 OPTIONS
 -------
 <file>...::
-       Files to remove from the index and optionally, from the
-       working tree as well.
+       Files to remove.  Fileglobs (e.g. `*.c`) can be given to
+       remove all matching files.  Also a leading directory name
+       (e.g. `dir` to add `dir/file1` and `dir/file2`) can be
+       given to remove all files in the directory, recursively,
+       but this requires `-r` option to be given for safety.
 
 -f::
-       Remove files from the working tree as well as from the index.
+       Override the up-to-date check.
 
 -n::
         Don't actually remove the file(s), just show if they exist in
         the index.
 
--v::
-        Be verbose.
+-r::
+        Allow recursive removal when a leading directory name is
+        given.
 
 \--::
        This option can be used to separate command-line options from
        the list of files, (useful when filenames might be mistaken
        for command-line options).
 
+\--cached::
+       This option can be used to tell the command to remove
+       the paths only from the index, leaving working tree
+       files.
+
 
 DISCUSSION
 ----------
 
-The list of <file> given to the command is fed to `git-ls-files`
-command to list files that are registered in the index and
-are not ignored/excluded by `$GIT_DIR/info/exclude` file or
-`.gitignore` file in each directory.  This means two things:
-
-. You can put the name of a directory on the command line, and the
-  command will remove all files in it and its subdirectories (the
-  directories themselves are never removed from the working tree);
-
-. Giving the name of a file that is not in the index does not
-  remove that file.
+The list of <file> given to the command can be exact pathnames,
+file glob patterns, or leading directory name.  The command
+removes only the paths that is known to git.  Giving the name of
+a file that you have not told git about does not remove that file.
 
 
 EXAMPLES
 --------
 git-rm Documentation/\\*.txt::
-
        Removes all `\*.txt` files from the index that are under the
-       `Documentation` directory and any of its subdirectories. The
-       files are not removed from the working tree.
+       `Documentation` directory and any of its subdirectories.
 +
 Note that the asterisk `\*` is quoted from the shell in this
 example; this lets the command include the files from
 subdirectories of `Documentation/` directory.
 
 git-rm -f git-*.sh::
-
-       Remove all git-*.sh scripts that are in the index. The files
-       are removed from the index, and (because of the -f option),
-       from the working tree as well. Because this example lets the
-       shell expand the asterisk (i.e. you are listing the files
-       explicitly), it does not remove `subdir/git-foo.sh`.
+       Remove all git-*.sh scripts that are in the index.
+       Because this example lets the shell expand the asterisk
+       (i.e. you are listing the files explicitly), it
+       does not remove `subdir/git-foo.sh`.
 
 See Also
 --------
index 5376f685486c6f19cbe6ea3b8391cac5b50c593b..205bfd2d259abd33873305d28281166a42285f6e 100644 (file)
@@ -3,38 +3,51 @@ git-send-pack(1)
 
 NAME
 ----
-git-send-pack - Push missing objects packed
+git-send-pack - Push objects over git protocol to another repository
 
 
 SYNOPSIS
 --------
-'git-send-pack' [--all] [--force] [--exec=<git-receive-pack>] [<host>:]<directory> [<ref>...]
+'git-send-pack' [--all] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]
 
 DESCRIPTION
 -----------
+Usually you would want to use gitlink:git-push[1] which is a
+higher level wrapper of this command instead.
+
 Invokes 'git-receive-pack' on a possibly remote repository, and
 updates it from the current repository, sending named refs.
 
 
 OPTIONS
 -------
---exec=<git-receive-pack>::
+\--receive-pack=<git-receive-pack>::
        Path to the 'git-receive-pack' program on the remote
        end.  Sometimes useful when pushing to a remote
        repository over ssh, and you do not have the program in
        a directory on the default $PATH.
 
---all::
+\--exec=<git-receive-pack>::
+       Same as \--receive-pack=<git-receive-pack>.
+
+\--all::
        Instead of explicitly specifying which refs to update,
        update all refs that locally exist.
 
---force::
+\--force::
        Usually, the command refuses to update a remote ref that
        is not an ancestor of the local ref used to overwrite it.
        This flag disables the check.  What this means is that
        the remote repository can lose commits; use it with
        care.
 
+\--verbose::
+       Run verbosely.
+
+\--thin::
+       Spend extra cycles to minimize the number of objects to be sent.
+       Use it on slower connection.
+
 <host>::
        A remote host to house the repository.  When this
        part is specified, 'git-receive-pack' is invoked via
index 79217d8a56193c6ba4a3406ec6cbd04abf19fec3..2b2abebd608be337db35a3de4b83d37d43061acb 100644 (file)
@@ -12,14 +12,51 @@ SYNOPSIS
 DESCRIPTION
 -----------
 
-Sets up the normal git environment variables and a few helper functions
-(currently just "die()"), and returns OK if it all looks like a git archive.
-So, to make the rest of the git scripts more careful and readable,
-use it as follows:
-
--------------------------------------------------
-. git-sh-setup || die "Not a git archive"
--------------------------------------------------
+This is not a command the end user would want to run.  Ever.
+This documentation is meant for people who are studying the
+Porcelain-ish scripts and/or are writing new ones.
+
+The `git-sh-setup` scriptlet is designed to be sourced (using
+`.`) by other shell scripts to set up some variables pointing at
+the normal git directories and a few helper shell functions.
+
+Before sourcing it, your script should set up a few variables;
+`USAGE` (and `LONG_USAGE`, if any) is used to define message
+given by `usage()` shell function.  `SUBDIRECTORY_OK` can be set
+if the script can run from a subdirectory of the working tree
+(some commands do not).
+
+The scriptlet sets `GIT_DIR` and `GIT_OBJECT_DIRECTORY` shell
+variables, but does *not* export them to the environment.
+
+FUNCTIONS
+---------
+
+die::
+       exit after emitting the supplied error message to the
+       standard error stream.
+
+usage::
+       die with the usage message.
+
+set_reflog_action::
+       set the message that will be recorded to describe the
+       end-user action in the reflog, when the script updates a
+       ref.
+
+is_bare_repository::
+       outputs `true` or `false` to the standard output stream
+       to indicate if the repository is a bare repository
+       (i.e. without an associated working tree).
+
+cd_to_toplevel::
+       runs chdir to the toplevel of the working tree.
+
+require_work_tree::
+       checks if the repository is a bare repository, and dies
+       if so.  Used by scripts that require working tree
+       (e.g. `checkout`).
+
 
 Author
 ------
index cc4266d83b529cd6f3d9446064cde37d89e7ad41..228b9f14f3530b9ec28e603a3ef2327c7039c440 100644 (file)
@@ -3,7 +3,7 @@ git-shell(1)
 
 NAME
 ----
-git-shell - Restricted login shell for GIT over SSH only
+git-shell - Restricted login shell for GIT-only SSH access
 
 
 SYNOPSIS
index d54fc3e5c6d7ba3be31fa2c1238e2d7039c92e74..b0df92e8192bc494e5d4db1707c233790560afb8 100644 (file)
@@ -8,6 +8,7 @@ git-shortlog - Summarize 'git log' output
 SYNOPSIS
 --------
 git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
+git-shortlog [-n|--number] [-s|--summary] [<committish>...]
 
 DESCRIPTION
 -----------
@@ -28,7 +29,7 @@ OPTIONS
        of author alphabetic order.
 
 -s::
-       Supress commit description and provide a commit count summary only.
+       Suppress commit description and provide a commit count summary only.
 
 FILES
 -----
index a2445a48fc98ddd4865bab8d3033880a854fca36..ba5313d51f8ba8380bd5519bf0c15a491a9bc83a 100644 (file)
@@ -8,9 +8,10 @@ git-show-branch - Show branches and their commits
 SYNOPSIS
 --------
 [verse]
-'git-show-branch' [--all] [--heads] [--tags] [--topo-order] [--current]
+'git-show-branch' [--all] [--remotes] [--topo-order] [--current]
                [--more=<n> | --list | --independent | --merge-base]
-               [--no-name | --sha1-name] [<rev> | <glob>]...
+               [--no-name | --sha1-name] [--topics] [<rev> | <glob>]...
+'git-show-branch' (-g|--reflog)[=<n>[,<base>]] [--list] [<ref>]
 
 DESCRIPTION
 -----------
@@ -37,9 +38,11 @@ OPTIONS
        branches under $GIT_DIR/refs/heads/topic, giving
        `topic/*` would show all of them.
 
---all --heads --tags::
-       Show all refs under $GIT_DIR/refs, $GIT_DIR/refs/heads,
-       and $GIT_DIR/refs/tags, respectively.
+-r|--remotes::
+       Show the remote-tracking branches.
+
+-a|--all::
+       Show both remote-tracking branches and local branches.
 
 --current::
        With this option, the command includes the current
@@ -86,6 +89,22 @@ OPTIONS
        of "master"), name them with the unique prefix of their
        object names.
 
+--topics::
+       Shows only commits that are NOT on the first branch given.
+       This helps track topic branches by hiding any commit that
+       is already in the main line of development.  When given
+       "git show-branch --topics master topic1 topic2", this
+       will show the revisions given by "git rev-list {caret}master
+       topic1 topic2"
+
+--reflog[=<n>[,<base>]] [<ref>]::
+       Shows <n> most recent ref-log entries for the given
+       ref.  If <base> is given, <n> entries going back from
+       that entry.  <base> can be specified as count or date.
+       `-g` can be used as a short-hand for this option.  When
+       no explicit <ref> parameter is given, it defaults to the
+       current branch (or `HEAD` if it is detached).
+
 Note that --more, --list, --independent and --merge-base options
 are mutually exclusive.
 
@@ -150,6 +169,13 @@ With this, `git show-branch` without extra parameters would show
 only the primary branches.  In addition, if you happen to be on
 your topic branch, it is shown as well.
 
+------------
+$ git show-branch --reflog='10,1 hour ago' --list master
+------------
+
+shows 10 reflog entries going back from the tip as of 1 hour ago.
+Without `--list`, the output also shows how these tips are
+topologically related with each other.
 
 
 Author
index 2b4df3f96f76dd023721ab03a149afd6ed8c5dd8..f56f16498398405a170c29543efc44a08e388c49 100644 (file)
@@ -3,38 +3,73 @@ git-show(1)
 
 NAME
 ----
-git-show - Show one commit with difference it introduces
+git-show - Show various types of objects
 
 
 SYNOPSIS
 --------
-'git-show' <option>...
+'git-show' [options] <object>...
 
 DESCRIPTION
 -----------
-Shows commit log and textual diff for a single commit.  The
-command internally invokes 'git-rev-list' piped to
-'git-diff-tree', and takes command line options for both of
-these commands. It also presents the merge commit in a special
-format as produced by 'git-diff-tree --cc'.
+Shows one or more objects (blobs, trees, tags and commits).
+
+For commits it shows the log message and textual diff. It also
+presents the merge commit in a special format as produced by
+'git-diff-tree --cc'.
+
+For tags, it shows the tag message and the referenced objects.
+
+For trees, it shows the names (equivalent to gitlink:git-ls-tree[1]
+with \--name-only).
+
+For plain blobs, it shows the plain contents.
+
+The command takes options applicable to the gitlink:git-diff-tree[1] command to
+control how the changes the commit introduces are shown.
 
 This manual page describes only the most frequently used options.
 
 
 OPTIONS
 -------
-<commitid>::
-       ID of the commit to show.
+<object>::
+       The name of the object to show.
+       For a more complete list of ways to spell object names, see
+       "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
+
+include::pretty-formats.txt[]
+
+
+EXAMPLES
+--------
+
+git show v1.0.0::
+       Shows the tag `v1.0.0`, along with the object the tags
+       points at.
+
+git show v1.0.0^{tree}::
+       Shows the tree pointed to by the tag `v1.0.0`.
+
+git show next~10:Documentation/README
+       Shows the contents of the file `Documentation/README` as
+       they were current in the 10th last commit of the branch
+       `next`.
+
+git show master:Makefile master:t/Makefile
+       Concatenates the contents of said Makefiles in the head
+       of the branch `master`.
+
+Discussion
+----------
 
---pretty=<format>::
-       Controls the output format for the commit logs.
-       <format> can be one of 'raw', 'medium', 'short', 'full',
-       and 'oneline'.
+include::i18n.txt[]
 
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org> and
-Junio C Hamano <junkio@cox.net>
+Junio C Hamano <junkio@cox.net>.  Significantly enhanced by
+Johannes Schindelin <Johannes.Schindelin@gmx.de>.
 
 
 Documentation
index b7116b30e0f47958d54c066776adbd318c84edb3..192b1f15a9ca75351623d32e4373d65c6501b465 100644 (file)
@@ -3,7 +3,7 @@ git-ssh-fetch(1)
 
 NAME
 ----
-git-ssh-fetch - Pulls from a remote repository over ssh connection
+git-ssh-fetch - Fetch from a remote repository over ssh connection
 
 
 
index 702674e45d17fbdb6dfc60797ffcb2dbedb841bf..a9b7e9f974c13252456309c7053537853dc7ea9b 100644 (file)
@@ -3,7 +3,7 @@ git-ssh-upload(1)
 
 NAME
 ----
-git-ssh-upload - Pushes to a remote repository over ssh connection
+git-ssh-upload - Push to a remote repository over ssh connection
 
 
 SYNOPSIS
index ce7857e5a93c212c609b2ef0bef56c3c42ed841c..03871e5d734dc295bba92e965721f6aa4142172f 100644 (file)
@@ -3,7 +3,7 @@ git-status(1)
 
 NAME
 ----
-git-status - Show working tree status
+git-status - Show the working tree status
 
 
 SYNOPSIS
@@ -34,6 +34,15 @@ The output from this command is designed to be used as a commit
 template comments, and all the output lines are prefixed with '#'.
 
 
+CONFIGURATION
+-------------
+
+The command honors `color.status` (or `status.color` -- they
+mean the same thing and the latter is kept for backward
+compatibility) and `color.status.<slot>` configuration variables
+to colorize its output.
+
+
 Author
 ------
 Written by Linus Torvalds <torvalds@osdl.org> and
index a764d1f8ee52742b85ce8edff22814a32be82dfb..6ce6a3944de5c1b815cd922b26f5eca2be08ed84 100644 (file)
@@ -3,7 +3,7 @@ git-svn(1)
 
 NAME
 ----
-git-svn - bidirectional operation between a single Subversion branch and git
+git-svn - Bidirectional operation between a single Subversion branch and git
 
 SYNOPSIS
 --------
@@ -11,24 +11,20 @@ SYNOPSIS
 
 DESCRIPTION
 -----------
-git-svn is a simple conduit for changesets between a single Subversion
-branch and git. It is not to be confused with gitlink:git-svnimport[1].
-They were designed with very different goals in mind.
+git-svn is a simple conduit for changesets between Subversion and git.
+It is not to be confused with gitlink:git-svnimport[1], which is
+read-only and geared towards tracking multiple branches.
 
-git-svn is designed for an individual developer who wants a
+git-svn was originally designed for an individual developer who wants a
 bidirectional flow of changesets between a single branch in Subversion
-and an arbitrary number of branches in git.  git-svnimport is designed
-for read-only operation on repositories that match a particular layout
-(albeit the recommended one by SVN developers).
+and an arbitrary number of branches in git.  Since its inception,
+git-svn has gained the ability to track multiple branches in a manner
+similar to git-svnimport; but it cannot (yet) automatically detect new
+branches and tags like git-svnimport does.
 
-For importing svn, git-svnimport is potentially more powerful when
-operating on repositories organized under the recommended
-trunk/branch/tags structure, and should be faster, too.
-
-git-svn mostly ignores the very limited view of branching that
-Subversion has.  This allows git-svn to be much easier to use,
-especially on repositories that are not organized in a manner that
-git-svnimport is designed for.
+git-svn is especially useful when it comes to tracking repositories
+not organized in the way Subversion developers recommend (trunk,
+branches, tags directories).
 
 COMMANDS
 --------
@@ -49,7 +45,7 @@ latest revision.
 
 Note: You should never attempt to modify the remotes/git-svn
 branch outside of git-svn.  Instead, create a branch from
-remotes/git-svn and work on that branch.  Use the 'commit'
+remotes/git-svn and work on that branch.  Use the 'dcommit'
 command (see below) to write git commits back to
 remotes/git-svn.
 
@@ -57,12 +53,16 @@ See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in
 manually joining branches on commit.
 
 'dcommit'::
-       Commit all diffs from the current HEAD directly to the SVN
+       Commit each diff from a specified head directly to the SVN
        repository, and then rebase or reset (depending on whether or
-       not there is a diff between SVN and HEAD).  It is recommended
-       that you run git-svn fetch and rebase (not pull) your commits
-       against the latest changes in the SVN repository.
-       This is advantageous over 'commit' (below) because it produces
+       not there is a diff between SVN and head).  This will create
+       a revision in SVN for each commit in git.
+       It is recommended that you run git-svn fetch and rebase (not
+       pull or merge) your commits against the latest changes in the
+       SVN repository.
+       An optional command-line argument may be specified as an
+       alternative to HEAD.
+       This is advantageous over 'set-tree' (below) because it produces
        cleaner, more linear history.
 
 'log'::
@@ -87,7 +87,7 @@ manually joining branches on commit.
 
        Any other arguments are passed directly to `git log'
 
-'commit'::
+'set-tree'::
        You should consider using 'dcommit' instead of this command.
        Commit specified commit or tree objects to SVN.  This relies on
        your imported fetch data being up-to-date.  This makes
@@ -113,7 +113,7 @@ manually joining branches on commit.
 
 'commit-diff'::
        Commits the diff of two tree-ish arguments from the
-       command-line.  This command is intended for interopability with
+       command-line.  This command is intended for interoperability with
        git-svnimport and does not rely on being inside an git-svn
        init-ed repository.  This command takes three arguments, (a) the
        original tree to diff against, (b) the new tree result, (c) the
@@ -133,12 +133,30 @@ manually joining branches on commit.
 
 'multi-init'::
        This command supports git-svnimport-like command-line syntax for
-       importing repositories that are layed out as recommended by the
+       importing repositories that are laid out as recommended by the
        SVN folks.  This is a bit more tolerant than the git-svnimport
        command-line syntax and doesn't require the user to figure out
        where the repository URL ends and where the repository path
        begins.
 
+-T<trunk_subdir>::
+--trunk=<trunk_subdir>::
+-t<tags_subdir>::
+--tags=<tags_subdir>::
+-b<branches_subdir>::
+--branches=<branches_subdir>::
+       These are the command-line options for multi-init.  Each of
+       these flags can point to a relative repository path
+       (--tags=project/tags') or a full url
+       (--tags=https://foo.org/project/tags)
+
+--prefix=<prefix>
+       This allows one to specify a prefix which is prepended to the
+       names of remotes.  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.
+
 'multi-fetch'::
        This runs fetch on all known SVN branches we're tracking.  This
        will NOT discover new branches (unlike git-svnimport), so
@@ -153,7 +171,7 @@ OPTIONS
 --shared::
 --template=<template_directory>::
        Only used with the 'init' command.
-       These are passed directly to gitlink:git-init-db[1].
+       These are passed directly to gitlink:git-init[1].
 
 -r <ARG>::
 --revision <ARG>::
@@ -170,7 +188,7 @@ This can allow you to make partial mirrors when running fetch.
 -::
 --stdin::
 
-Only used with the 'commit' command.
+Only used with the 'set-tree' command.
 
 Read a list of commits from stdin and commit them in reverse
 order.  Only the leading sha1 is read from each line, so
@@ -178,7 +196,7 @@ git-rev-list --pretty=oneline output can be used.
 
 --rmdir::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 Remove directories from the SVN tree if there are no files left
 behind.  SVN can version empty directories, and they are not
@@ -186,30 +204,30 @@ removed by default if there are no files left in them.  git
 cannot version empty directories.  Enabling this flag will make
 the commit to SVN act like git.
 
-repo-config key: svn.rmdir
+config key: svn.rmdir
 
 -e::
 --edit::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 Edit the commit message before committing to SVN.  This is off by
 default for objects that are commits, and forced on when committing
 tree objects.
 
-repo-config key: svn.edit
+config key: svn.edit
 
 -l<num>::
 --find-copies-harder::
 
-Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
+Only used with the 'dcommit', 'set-tree' and 'commit-diff' commands.
 
 They are both passed directly to git-diff-tree see
 gitlink:git-diff-tree[1] for more information.
 
 [verse]
-repo-config key: svn.l
-repo-config key: svn.findcopiesharder
+config key: svn.l
+config key: svn.findcopiesharder
 
 -A<filename>::
 --authors-file=<filename>::
@@ -227,12 +245,11 @@ will abort operation. The user will then have to add the
 appropriate entry.  Re-running the previous git-svn command
 after the authors-file is modified should continue operation.
 
-repo-config key: svn.authorsfile
+config key: svn.authorsfile
 
 -q::
 --quiet::
-       Make git-svn less verbose.  This only affects git-svn if you
-       have the SVN::* libraries installed and are using them.
+       Make git-svn less verbose.
 
 --repack[=<n>]::
 --repack-flags=<flags>
@@ -245,8 +262,8 @@ repo-config key: svn.authorsfile
 
        --repack-flags are passed directly to gitlink:git-repack[1].
 
-repo-config key: svn.repack
-repo-config key: svn.repackflags
+config key: svn.repack
+config key: svn.repackflags
 
 -m::
 --merge::
@@ -274,7 +291,7 @@ ADVANCED OPTIONS
 
 -b<refname>::
 --branch <refname>::
-Used with 'fetch' or 'commit'.
+Used with 'fetch', 'dcommit' or 'set-tree'.
 
 This can be used to join arbitrary git branches to remotes/git-svn
 on new commits where the tree object is equivalent.
@@ -287,7 +304,7 @@ used to track branches across multiple SVN _repositories_.
 This option may be specified multiple times, once for each
 branch.
 
-repo-config key: svn.branch
+config key: svn.branch
 
 -i<GIT_SVN_ID>::
 --id <GIT_SVN_ID>::
@@ -303,9 +320,7 @@ for more information on using GIT_SVN_ID.
        started tracking a branch and never tracked the trunk it was
        descended from.
 
-       This relies on the SVN::* libraries to work.
-
-repo-config key: svn.followparent
+config key: svn.followparent
 
 --no-metadata::
        This gets rid of the git-svn-id: lines at the end of every commit.
@@ -317,7 +332,7 @@ repo-config key: svn.followparent
        The 'git-svn log' command will not work on repositories using this,
        either.
 
-repo-config key: svn.nometadata
+config key: svn.nometadata
 
 --
 
@@ -332,25 +347,6 @@ Run this if you used an old version of git-svn that used
 "git-svn-HEAD" instead of "remotes/git-svn" as the branch
 for tracking the remote.
 
---no-ignore-externals::
-Only used with the 'fetch' and 'rebuild' command.
-
-This command has no effect when you are using the SVN::*
-libraries with git, svn:externals are always avoided.
-
-By default, git-svn passes --ignore-externals to svn to avoid
-fetching svn:external trees into git.  Pass this flag to enable
-externals tracking directly via git.
-
-Versions of svn that do not support --ignore-externals are
-automatically detected and this flag will be automatically
-enabled for them.
-
-Otherwise, do not enable this flag unless you know what you're
-doing.
-
-repo-config key: svn.noignoreexternals
-
 --ignore-nodate::
 Only used with the 'fetch' command.
 
@@ -368,62 +364,84 @@ SVN was very wrong.
 Basic Examples
 ~~~~~~~~~~~~~~
 
-Tracking and contributing to an Subversion managed-project:
+Tracking and contributing to a the trunk of a Subversion-managed project:
 
 ------------------------------------------------------------------------
-# Initialize a repo (like git init-db):
+# Initialize a repo (like git init):
        git-svn init http://svn.foo.org/project/trunk
 # Fetch remote revisions:
        git-svn fetch
 # Create your own branch to hack on:
        git checkout -b my-branch remotes/git-svn
-# Commit only the git commits you want to SVN:
-       git-svn commit <tree-ish> [<tree-ish_2> ...]
-# Commit all the git commits from my-branch that don't exist in SVN:
-       git-svn commit remotes/git-svn..my-branch
+# Do some work, and then commit your new changes to SVN, as well as
+# automatically updating your working HEAD:
+       git-svn dcommit
 # Something is committed to SVN, rebase the latest into your branch:
        git-svn fetch && git rebase remotes/git-svn
 # Append svn:ignore settings to the default git exclude file:
        git-svn show-ignore >> .git/info/exclude
 ------------------------------------------------------------------------
 
-REBASE VS. PULL
----------------
+Tracking and contributing to an entire Subversion-managed project
+(complete with a trunk, tags and branches):
+See also:
+'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>'
+
+------------------------------------------------------------------------
+# Initialize a repo (like git init):
+       git-svn multi-init http://svn.foo.org/project \
+               -T trunk -b branches -t tags
+# Fetch remote revisions:
+       git-svn multi-fetch
+# Create your own branch of trunk to hack on:
+       git checkout -b my-trunk remotes/trunk
+# Do some work, and then commit your new changes to SVN, as well as
+# automatically updating your working HEAD:
+       git-svn dcommit -i trunk
+# Something has been committed to trunk, rebase the latest into your branch:
+       git-svn multi-fetch && git rebase remotes/trunk
+# Append svn:ignore settings of trunk to the default git exclude file:
+       git-svn show-ignore -i trunk >> .git/info/exclude
+# Check for new branches and tags (no arguments are needed):
+       git-svn multi-init
+------------------------------------------------------------------------
+
+REBASE VS. PULL/MERGE
+---------------------
 
 Originally, git-svn recommended that the remotes/git-svn branch be
-pulled from.  This is because the author favored 'git-svn commit B'
-to commit a single head rather than the 'git-svn commit A..B' notation
-to commit multiple commits.
+pulled or merged from.  This is because the author favored
+'git-svn set-tree B' to commit a single head rather than the
+'git-svn set-tree A..B' notation to commit multiple commits.
 
-If you use 'git-svn commit A..B' to commit several diffs and you do not
-have the latest remotes/git-svn merged into my-branch, you should use
-'git rebase' to update your work branch instead of 'git pull'.  'pull'
-can cause non-linear history to be flattened when committing into SVN,
-which can lead to merge commits reversing previous commits in SVN.
+If you use 'git-svn set-tree A..B' to commit several diffs and you do
+not have the latest remotes/git-svn merged into my-branch, you should
+use 'git rebase' to update your work branch instead of 'git pull' or
+'git merge'.  'pull/merge' can cause non-linear history to be flattened
+when committing into SVN, which can lead to merge commits reversing
+previous commits in SVN.
 
 DESIGN PHILOSOPHY
 -----------------
 Merge tracking in Subversion is lacking and doing branched development
-with Subversion is cumbersome as a result.  git-svn completely forgoes
-any automated merge/branch tracking on the Subversion side and leaves it
-entirely up to the user on the git side.  It's simply not worth it to do
-a useful translation when the original signal is weak.
+with Subversion is cumbersome as a result.  git-svn does not do
+automated merge/branch tracking by default and leaves it entirely up to
+the user on the git side.
 
 [[tracking-multiple-repos]]
 TRACKING MULTIPLE REPOSITORIES OR BRANCHES
 ------------------------------------------
-This is for advanced users, most users should ignore this section.
-
 Because git-svn does not care about relationships between different
 branches or directories in a Subversion repository, git-svn has a simple
 hack to allow it to track an arbitrary number of related _or_ unrelated
-SVN repositories via one git repository.  Simply set the GIT_SVN_ID
-environment variable to a name other other than "git-svn" (the default)
-and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory
-and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that
-invocation.  The interface branch will be remotes/$GIT_SVN_ID, instead of
-remotes/git-svn.  Any remotes/$GIT_SVN_ID branch should never be modified
-by the user outside of git-svn commands.
+SVN repositories via one git repository.  Simply use the --id/-i flag or
+set the GIT_SVN_ID environment variable to a name other other than
+"git-svn" (the default) and git-svn will ignore the contents of the
+$GIT_DIR/svn/git-svn directory and instead do all of its work in
+$GIT_DIR/svn/$GIT_SVN_ID for that invocation.  The interface branch will
+be remotes/$GIT_SVN_ID, instead of remotes/git-svn.  Any
+remotes/$GIT_SVN_ID branch should never be modified by the user outside
+of git-svn commands.
 
 [[fetch-args]]
 ADDITIONAL FETCH ARGUMENTS
@@ -446,48 +464,18 @@ This allows you to tie unfetched SVN revision 375 to your current HEAD:
        git-svn fetch 375=$(git-rev-parse HEAD)
 ------------------------------------------------
 
-Advanced Example: Tracking a Reorganized Repository
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-Note: this example is now obsolete if you have SVN::* libraries
-installed.  Simply use --follow-parent when fetching.
-
 If you're tracking a directory that has moved, or otherwise been
 branched or tagged off of another directory in the repository and you
-care about the full history of the project, then you can read this
-section.
+care about the full history of the project, then you can use
+the --follow-parent option.
 
-This is how Yann Dirson tracked the trunk of the ufoai directory when
-the /trunk directory of his repository was moved to /ufoai/trunk and
-he needed to continue tracking /ufoai/trunk where /trunk left off.
-
-------------------------------------------------------------------------
-       # This log message shows when the repository was reorganized:
-       r166 | ydirson | 2006-03-02 01:36:55 +0100 (Thu, 02 Mar 2006) | 1 line
-       Changed paths:
-          D /trunk
-          A /ufoai/trunk (from /trunk:165)
-
-       # First we start tracking the old revisions:
-       GIT_SVN_ID=git-oldsvn git-svn init \
-                       https://svn.sourceforge.net/svnroot/ufoai/trunk
-       GIT_SVN_ID=git-oldsvn git-svn fetch -r1:165
-
-       # And now, we continue tracking the new revisions:
-       GIT_SVN_ID=git-newsvn git-svn init \
-             https://svn.sourceforge.net/svnroot/ufoai/ufoai/trunk
-       GIT_SVN_ID=git-newsvn git-svn fetch \
-             166=`git-rev-parse refs/remotes/git-oldsvn`
-------------------------------------------------------------------------
+------------------------------------------------
+       git-svn fetch --follow-parent
+------------------------------------------------
 
 BUGS
 ----
 
-If you are not using the SVN::* Perl libraries and somebody commits a
-conflicting changeset to SVN at a bad moment (right before you commit)
-causing a conflict and your commit to fail, your svn working tree
-($GIT_DIR/git-svn/tree) may be dirtied.  The easiest thing to do is
-probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'.
-
 We ignore all SVN properties except svn:executable.  Too difficult to
 map them since we rely heavily on git write-tree being _exactly_ the
 same on both the SVN and git working trees and I prefer not to clutter
index b1b87c2fcd9b60aef6fb937bd4d28572f1cb8463..b166cf3327380a8f386825a7fbce92f2447bb54e 100644 (file)
@@ -15,6 +15,7 @@ SYNOPSIS
                [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
                [ -s start_chg ] [ -m ] [ -r ] [ -M regex ]
                [ -I <ignorefile_name> ] [ -A <author_file> ]
+               [ -R <repack_each_revs>] [ -P <path_from_trunk> ]
                <SVN_repository_URL> [ <path> ]
 
 
@@ -103,9 +104,25 @@ repository without -A.
 
 -l <max_rev>::
        Specify a maximum revision number to pull.
++
+Formerly, this option controlled how many revisions to pull,
+due to SVN memory leaks. (These have been worked around.)
+
+-R <repack_each_revs>::
+       Specify how often git repository should be repacked.
++
+The default value is 1000. git-svnimport will do import in chunks of 1000
+revisions, after each chunk git repository will be repacked. To disable
+this behavior specify some big value here which is mote than number of
+revisions to import.
 
-       Formerly, this option controlled how many revisions to pull,
-       due to SVN memory leaks. (These have been worked around.)
+-P <path_from_trunk>::
+       Partial import of the SVN tree.
++
+By default, the whole tree on the SVN trunk (/trunk) is imported.
+'-P my/proj' will import starting only from '/trunk/my/proj'.
+This option is useful when you want to import one project from a
+svn repo which hosts multiple projects under the same trunk.
 
 -v::
        Verbosity: let 'svnimport' report what it is doing.
index 68ac6a65df304adf99f0af95698ffc901dda873c..a88f7228605ee35002573123f3dbe63c3e405dc6 100644 (file)
@@ -3,11 +3,11 @@ git-symbolic-ref(1)
 
 NAME
 ----
-git-symbolic-ref - read and modify symbolic refs
+git-symbolic-ref - Read and modify symbolic refs
 
 SYNOPSIS
 --------
-'git-symbolic-ref' <name> [<ref>]
+'git-symbolic-ref' [-q] [-m <reason>] <name> [<ref>]
 
 DESCRIPTION
 -----------
@@ -19,29 +19,38 @@ argument to see on which branch your working tree is on.
 Give two arguments, create or update a symbolic ref <name> to
 point at the given branch <ref>.
 
-Traditionally, `.git/HEAD` is a symlink pointing at
-`refs/heads/master`.  When we want to switch to another branch,
-we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we want
+A symbolic ref is a regular file that stores a string that
+begins with `ref: refs/`.  For example, your `.git/HEAD` is
+a regular file whose contents is `ref: refs/heads/master`.
+
+OPTIONS
+-------
+
+-q::
+       Do not issue an error message if the <name> is not a
+       symbolic ref but a detached HEAD; instead exit with
+       non-zero status silently.
+
+-m::
+       Update the reflog for <name> with <reason>.  This is valid only
+       when creating or updating a symbolic ref.
+
+NOTES
+-----
+In the past, `.git/HEAD` was a symbolic link pointing at
+`refs/heads/master`.  When we wanted to switch to another branch,
+we did `ln -sf refs/heads/newbranch .git/HEAD`, and when we wanted
 to find out which branch we are on, we did `readlink .git/HEAD`.
 This was fine, and internally that is what still happens by
 default, but on platforms that do not have working symlinks,
 or that do not have the `readlink(1)` command, this was a bit
 cumbersome.  On some platforms, `ln -sf` does not even work as
-advertised (horrors).
-
-A symbolic ref can be a regular file that stores a string that
-begins with `ref: refs/`.  For example, your `.git/HEAD` *can*
-be a regular file whose contents is `ref: refs/heads/master`.
-This can be used on a filesystem that does not support symbolic
-links.  Instead of doing `readlink .git/HEAD`, `git-symbolic-ref
-HEAD` can be used to find out which branch we are on.  To point
-the HEAD to `newbranch`, instead of `ln -sf refs/heads/newbranch
-.git/HEAD`, `git-symbolic-ref HEAD refs/heads/newbranch` can be
-used.
-
-Currently, .git/HEAD uses a regular file symbolic ref on Cygwin,
-and everywhere else it is implemented as a symlink.  This can be
-changed at compilation time.
+advertised (horrors).  Therefore symbolic links are now deprecated
+and symbolic refs are used by default.
+
+git-symbolic-ref will exit with status 0 if the contents of the
+symbolic ref were printed correctly, with status 1 if the requested
+name is not a symbolic ref, or 128 if another error occurs.
 
 Author
 ------
index 45476c2e415112347372f034276dec367f7a6a8d..70235e8ddb3e43805d699dd0bf264f585409697e 100644 (file)
@@ -3,13 +3,14 @@ git-tag(1)
 
 NAME
 ----
-git-tag - Create a tag object signed with GPG
+git-tag - Create, list, delete or verify a tag object signed with GPG
 
 
 SYNOPSIS
 --------
 [verse]
-'git-tag' [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <name> [<head>]
+'git-tag' [-a | -s | -u <key-id>] [-f | -v] [-m <msg> | -F <file>]  <name> [<head>]
+'git-tag' -d <name>...
 'git-tag' -l [<pattern>]
 
 DESCRIPTION
@@ -34,6 +35,8 @@ GnuPG key for signing.
 
 `-d <tag>` deletes the tag.
 
+`-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).
 
@@ -52,7 +55,10 @@ OPTIONS
        Replace an existing tag with the given name (instead of failing)
 
 -d::
-       Delete an existing tag with the given name
+       Delete existing tags with the given names.
+
+-v::
+       Verify the gpg signature of given the tag
 
 -l <pattern>::
        List tags that match the given pattern (or all if no pattern is given).
@@ -60,6 +66,150 @@ OPTIONS
 -m <msg>::
        Use the given tag message (instead of prompting)
 
+-F <file>::
+       Take the tag message from the given file.  Use '-' to
+       read the message from the standard input.
+
+CONFIGURATION
+-------------
+By default, git-tag in sign-with-default mode (-s) will use your
+committer identity (of the form "Your Name <your@email.address>") to
+find a key.  If you want to use a different default key, you can specify
+it in the repository configuration as follows:
+
+[user]
+    signingkey = <gpg-key-id>
+
+
+DISCUSSION
+----------
+
+On Re-tagging
+~~~~~~~~~~~~~
+
+What should you do when you tag a wrong commit and you would
+want to re-tag?
+
+If you never pushed anything out, just re-tag it. Use "-f" to
+replace the old one. And you're done.
+
+But if you have pushed things out (or others could just read
+your repository directly), then others will have already seen
+the old tag. In that case you can do one of two things:
+
+. The sane thing.
+Just admit you screwed up, and use a different name. Others have
+already seen one tag-name, and if you keep the same name, you
+may be in the situation that two people both have "version X",
+but they actually have 'different' "X"'s.  So just call it "X.1"
+and be done with it.
+
+. The insane thing.
+You really want to call the new version "X" too, 'even though'
+others have already seen the old one. So just use "git tag -f"
+again, as if you hadn't already published the old one.
+
+However, Git does *not* (and it should not)change tags behind
+users back. So if somebody already got the old tag, doing a "git
+pull" on your tree shouldn't just make them overwrite the old
+one.
+
+If somebody got a release tag from you, you cannot just change
+the tag for them by updating your own one. This is a big
+security issue, in that people MUST be able to trust their
+tag-names.  If you really want to do the insane thing, you need
+to just fess up to it, and tell people that you messed up. You
+can do that by making a very public announcement saying:
+
+------------
+Ok, I messed up, and I pushed out an earlier version tagged as X. I
+then fixed something, and retagged the *fixed* tree as X again.
+
+If you got the wrong tag, and want the new one, please delete
+the old one and fetch the new one by doing:
+
+       git tag -d X
+       git fetch origin tag X
+
+to get my updated tag.
+
+You can test which tag you have by doing
+
+       git rev-parse X
+
+which should return 0123456789abcdef.. if you have the new version.
+
+Sorry for inconvenience.
+------------
+
+Does this seem a bit complicated?  It *should* be. There is no
+way that it would be correct to just "fix" it behind peoples
+backs. People need to know that their tags might have been
+changed.
+
+
+On Automatic following
+~~~~~~~~~~~~~~~~~~~~~~
+
+If you are following somebody else's tree, you are most likely
+using tracking branches (`refs/heads/origin` in traditional
+layout, or `refs/remotes/origin/master` in the separate-remote
+layout).  You usually want the tags from the other end.
+
+On the other hand, if you are fetching because you would want a
+one-shot merge from somebody else, you typically do not want to
+get tags from there.  This happens more often for people near
+the toplevel but not limited to them.  Mere mortals when pulling
+from each other do not necessarily want to automatically get
+private anchor point tags from the other person.
+
+You would notice "please pull" messages on the mailing list says
+repo URL and branch name alone.  This is designed to be easily
+cut&pasted to "git fetch" command line:
+
+------------
+Linus, please pull from
+
+       git://git..../proj.git master
+
+to get the following updates...
+------------
+
+becomes:
+
+------------
+$ git pull git://git..../proj.git master
+------------
+
+In such a case, you do not want to automatically follow other's
+tags.
+
+One important aspect of git is it is distributed, and being
+distributed largely means there is no inherent "upstream" or
+"downstream" in the system.  On the face of it, the above
+example might seem to indicate that the tag namespace is owned
+by upper echelon of people and tags only flow downwards, but
+that is not the case.  It only shows that the usage pattern
+determines who are interested in whose tags.
+
+A one-shot pull is a sign that a commit history is now crossing
+the boundary between one circle of people (e.g. "people who are
+primarily interested in networking part of the kernel") who may
+have their own set of tags (e.g. "this is the third release
+candidate from the networking group to be proposed for general
+consumption with 2.6.21 release") to another circle of people
+(e.g. "people who integrate various subsystem improvements").
+The latter are usually not interested in the detailed tags used
+internally in the former group (that is what "internal" means).
+That is why it is desirable not to follow tags automatically in
+this case.
+
+It may well be that among networking people, they may want to
+exchange the tags internal to their group, but in that workflow
+they are most likely tracking with each other's progress by
+having tracking branches.  Again, the heuristic to automatically
+follow such tags is a good thing.
+
 
 Author
 ------
index 74a6fddd9a9f9d6831922fb163240c2a2c06994e..595940524e7d7f1112700899cf74e24e1c2e1e87 100644 (file)
@@ -3,7 +3,7 @@ git-tar-tree(1)
 
 NAME
 ----
-git-tar-tree - Creates a tar archive of the files in the named tree
+git-tar-tree - Create a tar archive of the files in the named tree object
 
 
 SYNOPSIS
@@ -50,7 +50,7 @@ repository configuration as follows :
         umask = 002    ;# group friendly
 
 The special umask value "user" indicates that the user's current umask
-will be used instead. The default value remains 0, which means world
+will be used instead.  The default value is 002, which means group
 readable/writable files and directories.
 
 EXAMPLES
index 0914cbb0ba528653aa50c6a2eb27815bd69d3508..10653ff898c2278f127f097ddf4a79db5615c802 100644 (file)
@@ -50,7 +50,7 @@ History Viewers
    gitview is a GTK based repository browser for git
 
 
-   - *gitweb* (ftp://ftp.kernel.org/pub/software/scm/gitweb/)
+   - *gitweb* (shipped with git-core)
 
    GITweb provides full-fledged web interface for GIT repositories.
 
@@ -63,12 +63,18 @@ History Viewers
    Currently it is the fastest and most feature rich among the git
    viewers and commit tools.
 
+   - *tig* (http://jonas.nitro.dk/tig/)
+
+   tig by Jonas Fonseca is a simple git repository browser
+   written using ncurses. Basically, it just acts as a front-end
+   for git-log and git-show/git-diff. Additionally, you can also
+   use it as a pager for git commands.
 
 
 Foreign SCM interface
 ---------------------
 
-   - *git-svn* (contrib/)
+   - *git-svn* (shipped with git-core)
 
    git-svn is a simple conduit for changesets between a single Subversion
    branch and git.
@@ -80,6 +86,14 @@ Foreign SCM interface
    series in git back and forth.
 
 
+   - *hg-to-git* (contrib/)
+
+   hg-to-git converts a Mercurial repository into a git one, and
+   preserves the full branch history in the process. hg-to-git can
+   also be used in an incremental way to keep the git repository
+   in sync with the master Mercurial repository.
+
+
 Others
 ------
 
@@ -95,3 +109,7 @@ Others
    This is an Emacs interface for git. The user interface is modeled on
    pcl-cvs. It has been developed on Emacs 21 and will probably need some
    tweaking to work on XEmacs.
+
+
+http://git.or.cz/gitwiki/InterfacesFrontendsAndTools has more
+comprehensive list.
index 0e0a3af1beca8d39d8e963636d18482f39cab011..b161c8b32b196b2e251ee65ee43e5aa0349b96b4 100644 (file)
@@ -3,7 +3,7 @@ git-update-index(1)
 
 NAME
 ----
-git-update-index - Modifies the index or directory cache
+git-update-index - Register file contents in the working tree to the index
 
 
 SYNOPSIS
@@ -289,7 +289,7 @@ Configuration
 
 The command honors `core.filemode` configuration variable.  If
 your repository is on an filesystem whose executable bits are
-unreliable, this should be set to 'false' (see gitlink:git-repo-config[1]).
+unreliable, this should be set to 'false' (see gitlink:git-config[1]).
 This causes the command to ignore differences in file modes recorded
 in the index and the file mode on the filesystem if they differ only on
 executable bit.   On such an unfortunate filesystem, you may
@@ -301,7 +301,7 @@ The command looks at `core.ignorestat` configuration variable.  See
 
 See Also
 --------
-gitlink:git-repo-config[1]
+gitlink:git-config[1]
 
 
 Author
index 71bcb7954f0c53a995ba56de6c99d9aebc6aa52a..9424feab320afcaa5a44bd2c2c6c86728bd8d004 100644 (file)
@@ -3,7 +3,7 @@ git-update-ref(1)
 
 NAME
 ----
-git-update-ref - update the object name stored in a ref safely
+git-update-ref - Update the object name stored in a ref safely
 
 SYNOPSIS
 --------
index 388bb53d297d559d21b2c9edb8c46c0e987c6b88..403871d7c6d0e8886cc61d60062d172d4fbda825 100644 (file)
@@ -3,7 +3,7 @@ git-upload-archive(1)
 
 NAME
 ----
-git-upload-archive - Send archive
+git-upload-archive - Send archive back to git-archive
 
 
 SYNOPSIS
index b2c9307661ebff26269cf3802a06490e0f1ba165..9da062d5c94aa631317fbcdc8b07443e58374bd8 100644 (file)
@@ -3,7 +3,7 @@ git-upload-pack(1)
 
 NAME
 ----
-git-upload-pack - Send missing objects packed
+git-upload-pack - Send objects packed back to git-fetch-pack
 
 
 SYNOPSIS
index a5b1a0dbab5e55a1c1fc2b0f8b35e234832808bb..9b0de1c111d7b58cb8d431d90814d1f1530f5e0d 100644 (file)
@@ -3,7 +3,7 @@ git-var(1)
 
 NAME
 ----
-git-var - Print the git users identity
+git-var - Show a git logical variable
 
 
 SYNOPSIS
@@ -20,7 +20,7 @@ OPTIONS
        Cause the logical variables to be listed. In addition, all the
        variables of the git configuration file .git/config are listed
        as well. (However, the configuration variables listing functionality
-       is deprecated in favor of `git-repo-config -l`.)
+       is deprecated in favor of `git-config -l`.)
 
 EXAMPLE
 --------
@@ -49,7 +49,7 @@ See Also
 --------
 gitlink:git-commit-tree[1]
 gitlink:git-tag[1]
-gitlink:git-repo-config[1]
+gitlink:git-config[1]
 
 Author
 ------
index e8f21d02f774a914e7e8840150f6db86a2884178..399bff3bbcab89be96039c373cf1120dcab0efe1 100644 (file)
@@ -27,7 +27,7 @@ OPTIONS
        output format that is useful only to tell the changed
        paths and their nature of changes.
 
---max-count=<n>::
+-<n>::
        Limit output to <n> commits.
 
 <since>..<until>::
index c85fa89c30e25ed026d2e95a2b537798f878e50f..96d5e07b112aa2a87eb6433f7cbeb8ce5f10205a 100644 (file)
@@ -3,7 +3,7 @@ git-write-tree(1)
 
 NAME
 ----
-git-write-tree - Creates a tree object from the current index
+git-write-tree - Create a tree object from the current index
 
 
 SYNOPSIS
index 619d65685eb51cdad2ba7faeaa16223ca55b2b81..3d8be5931cc7ade73198837bb360ba2a8c573d10 100644 (file)
@@ -22,9 +22,34 @@ See this link:tutorial.html[tutorial] to get started, then see
 link:everyday.html[Everyday Git] for a useful minimum set of commands, and
 "man git-commandname" for documentation of each command.  CVS users may
 also want to read link:cvs-migration.html[CVS migration].
+link:user-manual.html[Git User's Manual] is still work in
+progress, but when finished hopefully it will guide a new user
+in a coherent way to git enlightenment ;-).
 
 The COMMAND is either a name of a Git command (see below) or an alias
-as defined in the configuration file (see gitlink:git-repo-config[1]).
+as defined in the configuration file (see gitlink:git-config[1]).
+
+ifdef::stalenotes[]
+[NOTE]
+============
+You are reading the documentation for the latest version of git.
+Documentation for older releases are available here:
+
+* link:v1.5.0/git.html[documentation for release 1.5.0]
+
+* link:v1.5.0/RelNotes-1.5.0.txt[release notes for 1.5.0]
+
+* link:v1.4.4.4/git.html[documentation for release 1.4.4.4]
+
+* link:v1.3.3/git.html[documentation for release 1.3.3]
+
+* link:v1.2.6/git.html[documentation for release 1.2.6]
+
+* link:v1.0.13/git.html[documentation for release 1.0.13]
+
+============
+
+endif::stalenotes[]
 
 OPTIONS
 -------
@@ -81,238 +106,26 @@ ancillary user utilities.
 Main porcelain commands
 ~~~~~~~~~~~~~~~~~~~~~~~
 
-gitlink:git-add[1]::
-       Add paths to the index.
-
-gitlink:git-am[1]::
-       Apply patches from a mailbox, but cooler.
-
-gitlink:git-applymbox[1]::
-       Apply patches from a mailbox, original version by Linus.
-
-gitlink:git-archive[1]::
-       Creates an archive of files from a named tree.
-
-gitlink:git-bisect[1]::
-       Find the change that introduced a bug by binary search.
-
-gitlink:git-branch[1]::
-       Create and Show branches.
-
-gitlink:git-checkout[1]::
-       Checkout and switch to a branch.
-
-gitlink:git-cherry-pick[1]::
-       Cherry-pick the effect of an existing commit.
-
-gitlink:git-clean[1]::
-       Remove untracked files from the working tree.
-
-gitlink:git-clone[1]::
-       Clones a repository into a new directory.
-
-gitlink:git-commit[1]::
-       Record changes to the repository.
-
-gitlink:git-diff[1]::
-       Show changes between commits, commit and working tree, etc.
-
-gitlink:git-fetch[1]::
-       Download from a remote repository via various protocols.
-
-gitlink:git-format-patch[1]::
-       Prepare patches for e-mail submission.
-
-gitlink:git-grep[1]::
-       Print lines matching a pattern.
-
-gitlink:gitk[1]::
-       The git repository browser.
-
-gitlink:git-log[1]::
-       Shows commit logs.
-
-gitlink:git-ls-remote[1]::
-       Shows references in a remote or local repository.
-
-gitlink:git-merge[1]::
-       Grand unified merge driver.
-
-gitlink:git-mv[1]::
-       Move or rename a file, a directory, or a symlink.
-
-gitlink:git-pack-refs[1]::
-       Pack heads and tags for efficient repository access.
-
-gitlink:git-pull[1]::
-       Fetch from and merge with a remote repository or a local branch.
-
-gitlink:git-push[1]::
-       Update remote refs along with associated objects.
-
-gitlink:git-rebase[1]::
-       Rebase local commits to the updated upstream head.
-
-gitlink:git-repack[1]::
-       Pack unpacked objects in a repository.
-
-gitlink:git-rerere[1]::
-       Reuse recorded resolution of conflicted merges.
-
-gitlink:git-reset[1]::
-       Reset current HEAD to the specified state.
-
-gitlink:git-resolve[1]::
-       Merge two commits.
-
-gitlink:git-revert[1]::
-       Revert an existing commit.
-
-gitlink:git-rm[1]::
-       Remove files from the working tree and from the index.
-
-gitlink:git-shortlog[1]::
-       Summarizes 'git log' output.
-
-gitlink:git-show[1]::
-       Show one commit log and its diff.
-
-gitlink:git-show-branch[1]::
-       Show branches and their commits.
-
-gitlink:git-status[1]::
-       Shows the working tree status.
-
-gitlink:git-verify-tag[1]::
-       Check the GPG signature of tag.
-
-gitlink:git-whatchanged[1]::
-       Shows commit logs and differences they introduce.
-
+include::cmds-mainporcelain.txt[]
 
 Ancillary Commands
 ~~~~~~~~~~~~~~~~~~
 Manipulators:
 
-gitlink:git-applypatch[1]::
-       Apply one patch extracted from an e-mail.
-
-gitlink:git-archimport[1]::
-       Import an arch repository into git.
-
-gitlink:git-convert-objects[1]::
-       Converts old-style git repository.
-
-gitlink:git-cvsimport[1]::
-       Salvage your data out of another SCM people love to hate.
-
-gitlink:git-cvsexportcommit[1]::
-       Export a single commit to a CVS checkout.
-
-gitlink:git-cvsserver[1]::
-       A CVS server emulator for git.
-
-gitlink:git-lost-found[1]::
-       Recover lost refs that luckily have not yet been pruned.
-
-gitlink:git-merge-one-file[1]::
-       The standard helper program to use with `git-merge-index`.
-
-gitlink:git-prune[1]::
-       Prunes all unreachable objects from the object database.
-
-gitlink:git-quiltimport[1]::
-       Applies a quilt patchset onto the current branch.
-
-gitlink:git-relink[1]::
-       Hardlink common objects in local repositories.
-
-gitlink:git-svn[1]::
-       Bidirectional operation between a single Subversion branch and git.
-
-gitlink:git-svnimport[1]::
-       Import a SVN repository into git.
-
-gitlink:git-sh-setup[1]::
-       Common git shell script setup code.
-
-gitlink:git-symbolic-ref[1]::
-       Read and modify symbolic refs.
-
-gitlink:git-tag[1]::
-       An example script to create a tag object signed with GPG.
-
-gitlink:git-update-ref[1]::
-       Update the object name stored in a ref safely.
-
+include::cmds-ancillarymanipulators.txt[]
 
 Interrogators:
 
-gitlink:git-annotate[1]::
-       Annotate file lines with commit info.
-
-gitlink:git-blame[1]::
-       Find out where each line in a file came from.
-
-gitlink:git-check-ref-format[1]::
-       Make sure ref name is well formed.
-
-gitlink:git-cherry[1]::
-       Find commits not merged upstream.
-
-gitlink:git-count-objects[1]::
-       Count unpacked number of objects and their disk consumption.
-
-gitlink:git-daemon[1]::
-       A really simple server for git repositories.
-
-gitlink:git-fmt-merge-msg[1]::
-       Produce a merge commit message.
-
-gitlink:git-get-tar-commit-id[1]::
-       Extract commit ID from an archive created using git-tar-tree.
-
-gitlink:git-imap-send[1]::
-       Dump a mailbox from stdin into an imap folder.
-
-gitlink:git-instaweb[1]::
-       Instantly browse your working repository in gitweb.
-
-gitlink:git-mailinfo[1]::
-       Extracts patch and authorship information from a single
-       e-mail message, optionally transliterating the commit
-       message into utf-8.
-
-gitlink:git-mailsplit[1]::
-       A stupid program to split UNIX mbox format mailbox into
-       individual pieces of e-mail.
-
-gitlink:git-merge-tree[1]::
-       Show three-way merge without touching index.
-
-gitlink:git-patch-id[1]::
-       Compute unique ID for a patch.
-
-gitlink:git-parse-remote[1]::
-       Routines to help parsing `$GIT_DIR/remotes/` files.
+include::cmds-ancillaryinterrogators.txt[]
 
-gitlink:git-request-pull[1]::
-       git-request-pull.
 
-gitlink:git-rev-parse[1]::
-       Pick out and massage parameters.
-
-gitlink:git-runstatus[1]::
-       A helper for git-status and git-commit.
-
-gitlink:git-send-email[1]::
-       Send patch e-mails out of "format-patch --mbox" output.
+Interacting with Others
+~~~~~~~~~~~~~~~~~~~~~~~
 
-gitlink:git-symbolic-ref[1]::
-       Read and modify symbolic refs.
+These commands are to interact with foreign SCM and with other
+people via patch over e-mail.
 
-gitlink:git-stripspace[1]::
-       Filter out empty lines.
+include::cmds-foreignscminterface.txt[]
 
 
 Low-level commands (plumbing)
@@ -324,126 +137,30 @@ development of alternative porcelains.  Developers of such porcelains
 might start by reading about gitlink:git-update-index[1] and
 gitlink:git-read-tree[1].
 
-We divide the low-level commands into commands that manipulate objects (in
+The interface (input, output, set of options and the semantics)
+to these low-level commands are meant to be a lot more stable
+than Porcelain level commands, because these commands are
+primarily for scripted use.  The interface to Porcelain commands
+on the other hand are subject to change in order to improve the
+end user experience.
+
+The following description divides
+the low-level commands into commands that manipulate objects (in
 the repository, index, and working tree), commands that interrogate and
 compare objects, and commands that move objects and references between
 repositories.
 
+
 Manipulation commands
 ~~~~~~~~~~~~~~~~~~~~~
-gitlink:git-apply[1]::
-       Reads a "diff -up1" or git generated patch file and
-       applies it to the working tree.
-
-gitlink:git-checkout-index[1]::
-       Copy files from the index to the working tree.
-
-gitlink:git-commit-tree[1]::
-       Creates a new commit object.
-
-gitlink:git-hash-object[1]::
-       Computes the object ID from a file.
-
-gitlink:git-index-pack[1]::
-       Build pack idx file for an existing packed archive.
-
-gitlink:git-init-db[1]::
-       Creates an empty git object database, or reinitialize an
-       existing one.
-
-gitlink:git-merge-index[1]::
-       Runs a merge for files needing merging.
-
-gitlink:git-mktag[1]::
-       Creates a tag object.
-
-gitlink:git-mktree[1]::
-       Build a tree-object from ls-tree formatted text.
-
-gitlink:git-pack-objects[1]::
-       Creates a packed archive of objects.
-
-gitlink:git-prune-packed[1]::
-       Remove extra objects that are already in pack files.
 
-gitlink:git-read-tree[1]::
-       Reads tree information into the index.
-
-gitlink:git-repo-config[1]::
-       Get and set options in .git/config.
-
-gitlink:git-unpack-objects[1]::
-       Unpacks objects out of a packed archive.
-
-gitlink:git-update-index[1]::
-       Registers files in the working tree to the index.
-
-gitlink:git-write-tree[1]::
-       Creates a tree from the index.
+include::cmds-plumbingmanipulators.txt[]
 
 
 Interrogation commands
 ~~~~~~~~~~~~~~~~~~~~~~
 
-gitlink:git-cat-file[1]::
-       Provide content or type/size information for repository objects.
-
-gitlink:git-describe[1]::
-       Show the most recent tag that is reachable from a commit.
-
-gitlink:git-diff-index[1]::
-       Compares content and mode of blobs between the index and repository.
-
-gitlink:git-diff-files[1]::
-       Compares files in the working tree and the index.
-
-gitlink:git-diff-stages[1]::
-       Compares two "merge stages" in the index.
-
-gitlink:git-diff-tree[1]::
-       Compares the content and mode of blobs found via two tree objects.
-
-gitlink:git-for-each-ref[1]::
-       Output information on each ref.
-
-gitlink:git-fsck-objects[1]::
-       Verifies the connectivity and validity of the objects in the database.
-
-gitlink:git-ls-files[1]::
-       Information about files in the index and the working tree.
-
-gitlink:git-ls-tree[1]::
-       Displays a tree object in human readable form.
-
-gitlink:git-merge-base[1]::
-       Finds as good common ancestors as possible for a merge.
-
-gitlink:git-name-rev[1]::
-       Find symbolic names for given revs.
-
-gitlink:git-pack-redundant[1]::
-       Find redundant pack files.
-
-gitlink:git-rev-list[1]::
-       Lists commit objects in reverse chronological order.
-
-gitlink:git-show-index[1]::
-       Displays contents of a pack idx file.
-
-gitlink:git-show-ref[1]::
-       List references in a local repository.
-
-gitlink:git-tar-tree[1]::
-       Creates a tar archive of the files in the named tree object.
-
-gitlink:git-unpack-file[1]::
-       Creates a temporary file with a blob's contents.
-
-gitlink:git-var[1]::
-       Displays a git logical variable.
-
-gitlink:git-verify-pack[1]::
-       Validates packed git archive files.
+include::cmds-plumbinginterrogators.txt[]
 
 In general, the interrogate commands do not touch the files in
 the working tree.
@@ -452,52 +169,21 @@ the working tree.
 Synching repositories
 ~~~~~~~~~~~~~~~~~~~~~
 
-gitlink:git-fetch-pack[1]::
-       Updates from a remote repository (engine for ssh and
-       local transport).
+include::cmds-synchingrepositories.txt[]
 
-gitlink:git-http-fetch[1]::
-       Downloads a remote git repository via HTTP by walking
-       commit chain.
+The following are helper programs used by the above; end users
+typically do not use them directly.
 
-gitlink:git-local-fetch[1]::
-       Duplicates another git repository on a local system by
-       walking commit chain.
+include::cmds-synchelpers.txt[]
 
-gitlink:git-peek-remote[1]::
-       Lists references on a remote repository using
-       upload-pack protocol (engine for ssh and local
-       transport).
 
-gitlink:git-receive-pack[1]::
-       Invoked by 'git-send-pack' to receive what is pushed to it.
+Internal helper commands
+~~~~~~~~~~~~~~~~~~~~~~~~
 
-gitlink:git-send-pack[1]::
-       Pushes to a remote repository, intelligently.
+These are internal helper commands used by other commands; end
+users typically do not use them directly.
 
-gitlink:git-http-push[1]::
-       Push missing objects using HTTP/DAV.
-
-gitlink:git-shell[1]::
-       Restricted shell for GIT-only SSH access.
-
-gitlink:git-ssh-fetch[1]::
-       Pulls from a remote repository over ssh connection by
-       walking commit chain.
-
-gitlink:git-ssh-upload[1]::
-       Helper "server-side" program used by git-ssh-fetch.
-
-gitlink:git-update-server-info[1]::
-       Updates auxiliary information on a dumb server to help
-       clients discover references and packs on it.
-
-gitlink:git-upload-archive[1]::
-       Invoked by 'git-archive' to send a generated archive.
-
-gitlink:git-upload-pack[1]::
-       Invoked by 'git-fetch-pack' to push
-       what are asked for.
+include::cmds-purehelpers.txt[]
 
 
 Configuration Mechanism
@@ -639,11 +325,35 @@ git Commits
 git Diffs
 ~~~~~~~~~
 'GIT_DIFF_OPTS'::
+       Only valid setting is "--unified=??" or "-u??" to set the
+       number of context lines shown when a unified diff is created.
+       This takes precedence over any "-U" or "--unified" option
+       value passed on the git diff command line.
+
 'GIT_EXTERNAL_DIFF'::
-       see the "generating patches" section in :
-       gitlink:git-diff-index[1];
-       gitlink:git-diff-files[1];
-       gitlink:git-diff-tree[1]
+       When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
+       program named by it is called, instead of the diff invocation
+       described above.  For a path that is added, removed, or modified,
+        'GIT_EXTERNAL_DIFF' is called with 7 parameters:
+
+       path old-file old-hex old-mode new-file new-hex new-mode
++
+where:
+
+       <old|new>-file:: are files GIT_EXTERNAL_DIFF can use to read the
+                         contents of <old|new>,
+       <old|new>-hex:: are the 40-hexdigit SHA1 hashes,
+       <old|new>-mode:: are the octal representation of the file modes.
+
++
+The file parameters can point at the user's working file
+(e.g. `new-file` in "git-diff-files"), `/dev/null` (e.g. `old-file`
+when a new file is added), or a temporary file (e.g. `old-file` in the
+index).  'GIT_EXTERNAL_DIFF' should not worry about unlinking the
+temporary file --- it is removed when 'GIT_EXTERNAL_DIFF' exits.
++
+For a path that is unmerged, 'GIT_EXTERNAL_DIFF' is called with 1
+parameter, <path>.
 
 other
 ~~~~~
@@ -666,7 +376,7 @@ other
 
 Discussion[[Discussion]]
 ------------------------
-include::README[]
+include::core-intro.txt[]
 
 Authors
 -------
index f1aeb07f64e09735871f0a956b9ba412578a2a23..48c5894736ac3065e80a36e1a5230d86004bee2e 100644 (file)
@@ -3,7 +3,7 @@ gitk(1)
 
 NAME
 ----
-gitk - git repository browser
+gitk - The git repository browser
 
 SYNOPSIS
 --------
@@ -47,12 +47,14 @@ frequently used options.
        meaning show from the given revision and back, or it can be a range in
        the form "'<from>'..'<to>'" to show all revisions between '<from>' and
        back to '<to>'. Note, more advanced revision selection can be applied.
+       For a more complete list of ways to spell object names, see
+       "SPECIFYING REVISIONS" section in gitlink:git-rev-parse[1].
 
 <path>::
 
        Limit commits to the ones touching files in the given paths. Note, to
        avoid ambiguity wrt. revision names use "--" to separate the paths
-       from any preceeding options.
+       from any preceding options.
 
 Examples
 --------
index 894883d7b60c4c79263323fc007406c4f3720bac..d20eb6270cd795ab61b281154a7e24070519b7fb 100644 (file)
@@ -188,11 +188,12 @@ octopus::
        predator.
 
 origin::
-       The default upstream tracking branch. Most projects have at
+       The default upstream repository. Most projects have at
        least one upstream project which they track. By default
        'origin' is used for that purpose.  New upstream updates
-       will be fetched into this branch; you should never commit
-       to it yourself.
+       will be fetched into remote tracking branches named
+       origin/name-of-upstream-branch, which you can see using
+       "git branch -r".
 
 pack::
        A set of objects which have been compressed into one file (to save
@@ -234,8 +235,11 @@ push::
        local head, the push fails.
 
 reachable::
-       An object is reachable from a ref/commit/tree/tag, if there is a
-       chain leading from the latter to the former.
+       All of the ancestors of a given commit are said to be reachable from
+       that commit.  More generally, one object is reachable from another if
+       we can reach the one from the other by a chain that follows tags to
+       whatever they tag, commits to their parents or trees, and trees to the
+       trees or blobs that they contain.
 
 rebase::
        To clean a branch by starting from the head of the main line of
@@ -255,7 +259,7 @@ refspec::
        means "grab the master branch head from the $URL and store
        it as my origin branch head".
        And `git push $URL refs/heads/master:refs/heads/to-upstream`
-       means "publish my master branch head as to-upstream master head
+       means "publish my master branch head as to-upstream branch
        at $URL".   See also gitlink:git-push[1]
 
 repository::
@@ -282,6 +286,18 @@ SCM::
 SHA1::
        Synonym for object name.
 
+shallow repository::
+       A shallow repository has an incomplete history some of
+       whose commits have parents cauterized away (in other
+       words, git is told to pretend that these commits do not
+       have the parents, even though they are recorded in the
+       commit object).  This is sometimes useful when you are
+       interested only in the recent history of a project even
+       though the real history recorded in the upstream is
+       much larger.  A shallow repository is created by giving
+       `--depth` option to gitlink:git-clone[1], and its
+       history can be later deepened with gitlink:git-fetch[1].
+
 symref::
        Symbolic reference: instead of containing the SHA1 id itself, it
        is of the format 'ref: refs/some/thing' and when referenced, it
index 517f49b5cc5016a20b171789b89f48cb6ef0ecd5..b083290d120b36e882c2a6d190e58ae1ee07b02f 100644 (file)
@@ -3,7 +3,7 @@ Hooks used by git
 
 Hooks are little scripts you can place in `$GIT_DIR/hooks`
 directory to trigger action at certain points.  When
-`git-init-db` is run, a handful example hooks are copied in the
+`git-init` is run, a handful example hooks are copied in the
 `hooks` directory of the new repository, but by default they are
 all disabled.  To enable a hook, make it executable with `chmod +x`.
 
@@ -90,9 +90,6 @@ parameter, and is invoked after a commit is made.
 This hook is meant primarily for notification, and cannot affect
 the outcome of `git-commit`.
 
-The default 'post-commit' hook, when enabled, demonstrates how to
-send out a commit notification e-mail.
-
 update
 ------
 
@@ -126,10 +123,12 @@ Another use suggested on the mailing list is to use this hook to
 implement access control which is finer grained than the one
 based on filesystem group.
 
-The standard output of this hook is sent to `/dev/null`; if you
+The standard output of this hook is sent to `stderr`, so if you
 want to report something to the `git-send-pack` on the other end,
-you can redirect your output to your `stderr`.
+you can simply `echo` your messages.
 
+The default 'update' hook, when enabled, demonstrates how to
+send out a notification e-mail.
 
 post-update
 -----------
diff --git a/Documentation/howto/dangling-objects.txt b/Documentation/howto/dangling-objects.txt
new file mode 100644 (file)
index 0000000..e82ddae
--- /dev/null
@@ -0,0 +1,109 @@
+From: Linus Torvalds <torvalds@linux-foundation.org>
+Subject: Re: Question about fsck-objects output
+Date: Thu, 25 Jan 2007 12:01:06 -0800 (PST)
+Message-ID: <Pine.LNX.4.64.0701251144290.25027@woody.linux-foundation.org>
+Archived-At: <http://permalink.gmane.org/gmane.comp.version-control.git/37754>
+Abstract: Linus describes what dangling objects are, when they
+ are left behind, and how to view their relationship with branch
+ heads in gitk
+
+On Thu, 25 Jan 2007, Larry Streepy wrote:
+
+> Sorry to ask such a basic question, but I can't quite decipher the output of
+> fsck-objects.  When I run it, I get this:
+>
+>  git fsck-objects
+> dangling commit 2213f6d4dd39ca8baebd0427723723e63208521b
+> dangling commit f0d4e00196bd5ee54463e9ea7a0f0e8303da767f
+> dangling blob 6a6d0b01b3e96d49a8f2c7addd4ef8c3bd1f5761
+>
+>
+> Even after a "repack -a -d" they still exist.  The man page has a short
+> explanation, but, at least for me, it wasn't fully enlightening. :-)
+>
+> The man page says that dangling commits could be "root" commits, but since my
+> repo started as a clone of another repo, I don't see how I could have any root
+> commits.  Also, the page doesn't really describe what a dangling blob is.
+>
+> So, can someone explain what these artifacts are and if they are a problem
+> that I should be worried about?
+
+The most common situation is that you've rebased a branch (or you have
+pulled from somebody else who rebased a branch, like the "pu" branch in
+the git.git archive itself).
+
+What happens is that the old head of the original branch still exists, as
+does obviously everything it pointed to. The branch pointer itself just
+doesn't, since you replaced it with another one.
+
+However, there are certainly other situations too that cause dangling
+objects. For example, the "dangling blob" situation you have tends to be
+because you did a "git add" of a file, but then, before you actually
+committed it and made it part of the bigger picture, you changed something
+else in that file and committed that *updated* thing - the old state that
+you added originally ends up not being pointed to by any commit/tree, so
+it's now a dangling blob object.
+
+Similarly, when the "recursive" merge strategy runs, and finds that there
+are criss-cross merges and thus more than one merge base (which is fairly
+unusual, but it does happen), it will generate one temporary midway tree
+(or possibly even more, if you had lots of criss-crossing merges and
+more than two merge bases) as a temporary internal merge base, and again,
+those are real objects, but the end result will not end up pointing to
+them, so they end up "dangling" in your repository.
+
+Generally, dangling objects aren't anything to worry about. They can even
+be very useful: if you screw something up, the dangling objects can be how
+you recover your old tree (say, you did a rebase, and realized that you
+really didn't want to - you can look at what dangling objects you have,
+and decide to reset your head to some old dangling state).
+
+For commits, the most useful thing to do with dangling objects tends to be
+to do a simple
+
+       gitk <dangling-commit-sha-goes-here> --not --all
+
+which means exactly what it sounds like: it says that you want to see the
+commit history that is described by the dangling commit(s), but you do NOT
+want to see the history that is described by all your branches and tags
+(which are the things you normally reach). That basically shows you in a
+nice way what the danglign commit was (and notice that it might not be
+just one commit: we only report the "tip of the line" as being dangling,
+but there might be a whole deep and complex commit history that has gotten
+dropped - rebasing will do that).
+
+For blobs and trees, you can't do the same, but you can examine them. You
+can just do
+
+       git show <dangling-blob/tree-sha-goes-here>
+
+to show what the contents of the blob were (or, for a tree, basically what
+the "ls" for that directory was), and that may give you some idea of what
+the operation was that left that dangling object.
+
+Usually, dangling blobs and trees aren't very interesting. They're almost
+always the result of either being a half-way mergebase (the blob will
+often even have the conflict markers from a merge in it, if you have had
+conflicting merges that you fixed up by hand), or simply because you
+interrupted a "git fetch" with ^C or something like that, 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
+state, you can just prune all unreachable objects:
+
+       git prune
+
+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-objects" itself, btw - but since
+git-fsck-objects never actually *changes* the repository, it just reports
+on what it found, git-fsck-objects 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).
+
+                       Linus
+
index fcd64e9b9b6d53b46d31e0c797016e9881651604..3b3a5c2e69e59d4708171ba745d7d4009788102b 100644 (file)
@@ -106,7 +106,7 @@ prepare #2 and #3 for e-mail submission.
 
     $ git format-patch master^^ master
 
-This creates two files, 0001-XXXX.txt and 0002-XXXX.txt.  Send
+This creates two files, 0001-XXXX.patch and 0002-XXXX.patch.  Send
 them out "To: " your project maintainer and "Cc: " your mailing
 list.  You could use contributed script git-send-email if
 your host has necessary perl modules for this, but your usual
index d10476b56e7989d2c214a15491763e63e4b62c6b..d88ec23a97bc59f29a5f9734fc5ba0ef5a9ae6cf 100644 (file)
@@ -85,7 +85,7 @@ Fortunately I did not have to; what I have in the current branch
 
 ------------------------------------------------
 $ git checkout master
-$ git resolve master revert-c99 fast ;# this should be a fast forward
+$ git merge revert-c99 ;# this should be a fast forward
 Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
  cache.h        |    8 ++++----
  commit.c       |    2 +-
@@ -95,13 +95,6 @@ Updating from 10d781b9caa4f71495c7b34963bef137216f86a8 to e3a693c...
  5 files changed, 8 insertions(+), 8 deletions(-)
 ------------------------------------------------
 
-The 'fast' in the above 'git resolve' is not a magic.  I knew this
-'resolve' would result in a fast forward merge, and if not, there is
-something very wrong (so I would do 'git reset' on the 'master' branch
-and examine the situation).  When a fast forward merge is done, the
-message parameter to 'git resolve' is discarded, because no new commit
-is created.  You could have said 'junk' or 'nothing' there as well.
-
 There is no need to redo the test at this point.  We fast forwarded
 and we know 'master' matches 'revert-c99' exactly.  In fact:
 
index ba191569af9501117322e1881160226a74867007..8eadc2049402cc21fd80818ca9f29d15f4cd8462 100644 (file)
@@ -70,7 +70,7 @@ DocumentRoot /where/ever/httpd.conf" to find your root:
 Initialize a bare repository
 
     $ cd my-new-repo.git
-    $ git --bare init-db
+    $ git --bare init
 
 
 Change the ownership to your web-server's credentials. Use "grep ^User
@@ -205,7 +205,7 @@ To check whether all is OK, do:
 Now, add the remote in your existing repository which contains the project
 you want to export:
 
-   $ git-repo-config remote.upload.url \
+   $ git-config remote.upload.url \
        http://<username>@<servername>/my-new-repo.git/
 
 It is important to put the last '/'; Without it, the server will send
@@ -222,7 +222,7 @@ From your client repository, do
 
 This pushes branch 'master' (which is assumed to be the branch you
 want to export) to repository called 'upload', which we previously
-defined with git-repo-config.
+defined with git-config.
 
 
 Troubleshooting:
diff --git a/Documentation/i18n.txt b/Documentation/i18n.txt
new file mode 100644 (file)
index 0000000..b4cbb38
--- /dev/null
@@ -0,0 +1,57 @@
+At the core level, git is character encoding agnostic.
+
+ - The pathnames recorded in the index and in the tree objects
+   are treated as uninterpreted sequences of non-NUL bytes.
+   What readdir(2) returns are what are recorded and compared
+   with the data git keeps track of, which in turn are expected
+   to be what lstat(2) and creat(2) accepts.  There is no such
+   thing as pathname encoding translation.
+
+ - The contents of the blob objects are uninterpreted sequence
+   of bytes.  There is no encoding translation at the core
+   level.
+
+ - The commit log messages are uninterpreted sequence of non-NUL
+   bytes.
+
+Although we encourage that the commit log messages are encoded
+in UTF-8, both the core and git Porcelain are designed not to
+force UTF-8 on projects.  If all participants of a particular
+project find it more convenient to use legacy encodings, git
+does not forbid it.  However, there are a few things to keep in
+mind.
+
+. `git-commit-tree` (hence, `git-commit` which uses it) issues
+  an warning if the commit log message given to it does not look
+  like a valid UTF-8 string, unless you explicitly say your
+  project uses a legacy encoding.  The way to say this is to
+  have core.commitencoding in `.git/config` file, like this:
++
+------------
+[core]
+       commitencoding = ISO-8859-1
+------------
++
+Commit objects created with the above setting record the value
+of `core.commitencoding` in its `encoding` header.  This is to
+help other people who look at them later.  Lack of this header
+implies that the commit log message is encoded in UTF-8.
+
+. `git-log`, `git-show` and friends looks at the `encoding`
+  header of a commit object, and tries to re-code the log
+  message into UTF-8 unless otherwise specified.  You can
+  specify the desired output encoding with
+  `core.logoutputencoding` in `.git/config` file, like this:
++
+------------
+[core]
+       logoutputencoding = ISO-8859-1
+------------
++
+If you do not have this configuration variable, the value of
+`core.commitencoding` is used instead.
+
+Note that we deliberately chose not to re-code the commit log
+message when a commit is made to force UTF-8 at the commit
+object level, because re-coding to UTF-8 is not necessarily a
+reversible operation.
diff --git a/Documentation/install-doc-quick.sh b/Documentation/install-doc-quick.sh
new file mode 100755 (executable)
index 0000000..a640549
--- /dev/null
@@ -0,0 +1,31 @@
+#!/bin/sh
+# This requires a branch named in $head
+# (usually 'man' or 'html', provided by the git.git repository)
+set -e
+head="$1"
+mandir="$2"
+SUBDIRECTORY_OK=t
+USAGE='<refname> <target directory>'
+. git-sh-setup
+export GIT_DIR
+
+test -z "$mandir" && usage
+if ! git-rev-parse --verify "$head^0" >/dev/null; then
+       echo >&2 "head: $head does not exist in the current repository"
+       usage
+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"/
+
+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
+fi
+rm -f "$GIT_INDEX_FILE"
index 60211a5058da658a92d99bd1d970b742dddb4e61..b3981936e302dafb33e967b3cc17a3cab5b5659e 100755 (executable)
@@ -2,7 +2,7 @@
 
 T="$1"
 
-for h in *.html *.txt howto/*.txt howto/*.html
+for h in *.html *.txt howto/*.txt howto/*.html RelNotes-*.txt
 do
        if test -f "$T/$h" &&
           diff -u -I'Last updated [0-9][0-9]-[A-Z][a-z][a-z]-' "$T/$h" "$h"
diff --git a/Documentation/pretty-formats.txt b/Documentation/pretty-formats.txt
new file mode 100644 (file)
index 0000000..fb0b0b9
--- /dev/null
@@ -0,0 +1,85 @@
+--pretty[='<format>']::
+
+        Pretty-prints the details of a commit.  `--pretty`
+       without an explicit `=<format>` defaults to 'medium'.
+       If the commit is a merge, and if the pretty-format
+        is not 'oneline', 'email' or 'raw', an additional line is
+        inserted before the 'Author:' line.  This line begins with
+        "Merge: " and the sha1s of ancestral commits are printed,
+        separated by spaces.  Note that the listed commits may not
+        necessarily be the list of the *direct* parent commits if you
+        have limited your view of history: for example, if you are
+        only interested in changes related to a certain directory or
+        file.  Here are some additional details for each format:
+
+        * 'oneline'
+
+         <sha1> <title line>
++
+This is designed to be as compact as possible.
+
+        * 'short'
+
+         commit <sha1>
+         Author: <author>
+
+             <title line>
+
+        * 'medium'
+
+         commit <sha1>
+         Author: <author>
+         Date: <date>
+
+             <title line>
+
+             <full commit message>
+
+        * 'full'
+
+         commit <sha1>
+         Author: <author>
+         Commit: <committer>
+
+             <title line>
+
+             <full commit message>
+
+        * 'fuller'
+
+         commit <sha1>
+         Author: <author>
+         AuthorDate: <date & time>
+         Commit: <committer>
+         CommitDate: <date & time>
+
+              <title line>
+
+              <full commit message>
+
+
+        * 'email'
+
+         From <sha1> <date>
+         From: <author>
+         Date: <date & time>
+         Subject: [PATCH] <title line>
+
+         full commit message>
+
+
+       * 'raw'
++
+The 'raw' format shows the entire commit exactly as
+stored in the commit object.  Notably, the SHA1s are
+displayed in full, regardless of whether --abbrev or
+--no-abbrev are used, and 'parents' information show the
+true parent commits, without taking grafts nor history
+simplification into account.
+
+--encoding[=<encoding>]::
+       The commit objects record the encoding used for the log message
+       in their encoding header; this option can be used to tell the
+       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 e852f41a322933aa010bdf79247508e42ee3f661..8d4e950abc65c233268d785dcbfee67ab0522f4c 100644 (file)
@@ -39,10 +39,6 @@ checkout -b my-B remote-B`).  Run `git fetch` to keep track of
 the progress of the remote side, and when you see something new
 on the remote branch, merge it into your development branch with
 `git pull . remote-B`, while you are on `my-B` branch.
-The common `Pull: master:origin` mapping of a remote `master`
-branch to a local `origin` branch, which is then merged to a
-local development branch, again typically named `master`, is made
-when you run `git clone` for you to follow this pattern.
 +
 [NOTE]
 There is a difference between listing multiple <refspec>
index e20fb7e74c22390e810a392f2625a3ba6b325477..0459bd9ca1732beec1150e4354f1a03eafd53fe4 100644 (file)
@@ -18,6 +18,8 @@ could have only commit objects without associated blobs and
 trees this way, for example.  A repository with this kind of
 incomplete object store is not suitable to be published to the
 outside world but sometimes useful for private repository.
+. You also could have an incomplete but locally usable repository
+by cloning shallowly.  See gitlink:git-clone[1].
 . You can be using `objects/info/alternates` mechanism, or
 `$GIT_ALTERNATE_OBJECT_DIRECTORIES` mechanism to 'borrow'
 objects from other object stores.  A repository with this kind
@@ -32,7 +34,7 @@ objects/[0-9a-f][0-9a-f]::
        two letters from its object name to keep the number of
        directory entries `objects` directory itself needs to
        hold.  Objects found here are often called 'unpacked'
-       objects.
+       (or 'loose') objects.
 
 objects/pack::
        Packs (files that store many object in compressed form,
@@ -80,6 +82,15 @@ refs/tags/`name`::
        records any object name (not necessarily a commit
        object, or a tag object that points at a commit object).
 
+refs/remotes/`name`::
+       records tip-of-the-tree commit objects of branches copied
+       from a remote repository.
+
+packed-refs::
+       records the same information as refs/heads/, refs/tags/,
+       and friends record in a more efficient way.  See
+       gitlink:git-pack-refs[1].
+
 HEAD::
        A symref (see glossary) to the `refs/heads/` namespace
        describing the currently active branch.  It does not mean
@@ -91,6 +102,12 @@ HEAD::
        'name' does not (yet) exist.  In some legacy setups, it is
        a symbolic link instead of a symref that points at the current
        branch.
++
+HEAD can also record a specific commit directly, instead of
+being a symref to point at the current branch.  Such a state
+is often called 'detached HEAD', and almost all commands work
+identically as normal.  See gitlink:git-checkout[1] for
+details.
 
 branches::
        A slightly deprecated way to store shorthands to be used
@@ -102,7 +119,7 @@ branches::
 hooks::
        Hooks are customization scripts used by various git
        commands.  A handful of sample hooks are installed when
-       `git init-db` is run, but all of them are disabled by
+       `git init` is run, but all of them are disabled by
        default.  To enable, they need to be made executable.
        Read link:hooks.html[hooks] for more details about
        each hook.
@@ -116,14 +133,14 @@ info::
        in this directory.
 
 info/refs::
-       This file is to help dumb transports to discover what
-       refs are available in this repository.  Whenever you
-       create/delete a new branch or a new tag, `git
-       update-server-info` should be run to keep this file
-       up-to-date if the repository is published for dumb
-       transports.  The `git-receive-pack` command, which is
-       run on a remote repository when you `git push` into it,
-       runs `hooks/update` hook to help you achieve this.
+       This file helps dumb transports discover what refs are
+       available in this repository.  If the repository is
+       published for dumb transports, this file should be
+       regenerated by `git update-server-info` every time a tag
+       or branch is created or modified.  This is normally done
+       from the `hooks/update` hook, which is run by the
+       `git-receive-pack` command when you `git push` into the
+       repository.
 
 info/grafts::
        This file records fake commit ancestry information, to
@@ -156,3 +173,9 @@ logs/refs/heads/`name`::
 
 logs/refs/tags/`name`::
        Records all changes made to the tag named `name`.
+
+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 7597d041420ab2e84fc607bc3c844824c4528491..5030d9f2f831651f231d5c40d0e2110564646ef2 100644 (file)
@@ -4,7 +4,7 @@ Use of index and Racy git problem
 Background
 ----------
 
-The index is one of the most important data structure in git.
+The index is one of the most important data structures in git.
 It represents a virtual working tree state by recording list of
 paths and their object names and serves as a staging area to
 write out the next tree object to be committed.  The state is
@@ -16,7 +16,7 @@ virtual working tree state in the index and the files in the
 working tree.  The most obvious case is when the user asks `git
 diff` (or its low level implementation, `git diff-files`) or
 `git-ls-files --modified`.  In addition, git internally checks
-if the files in the working tree is different from what are
+if the files in the working tree are different from what are
 recorded in the index to avoid stomping on local changes in them
 during patch application, switching branches, and merging.
 
@@ -24,9 +24,9 @@ In order to speed up this comparison between the files in the
 working tree and the index entries, the index entries record the
 information obtained from the filesystem via `lstat(2)` system
 call when they were last updated.  When checking if they differ,
-git first runs `lstat(2)` on the files and compare the result
+git first runs `lstat(2)` on the files and compares the result
 with this information (this is what was originally done by the
-`ce_match_stat()` function, which the current code does in
+`ce_match_stat()` function, but the current code does it in
 `ce_match_stat_basic()` function).  If some of these "cached
 stat information" fields do not match, git can tell that the
 files are modified without even looking at their contents.
@@ -53,8 +53,9 @@ Racy git
 There is one slight problem with the optimization based on the
 cached stat information.  Consider this sequence:
 
+  : modify 'foo'
   $ git update-index 'foo'
-  : modify 'foo' in-place without changing its size
+  : modify 'foo' again, in-place, without changing its size
 
 The first `update-index` computes the object name of the
 contents of file `foo` and updates the index entry for `foo`
@@ -62,7 +63,8 @@ along with the `struct stat` information.  If the modification
 that follows it happens very fast so that the file's `st_mtime`
 timestamp does not change, after this sequence, the cached stat
 information the index entry records still exactly match what you
-can obtain from the filesystem, but the file `foo` is modified.
+would see in the filesystem, even though the file `foo` is now
+different.
 This way, git can incorrectly think files in the working tree
 are unmodified even though they actually are.  This is called
 the "racy git" problem (discovered by Pasky), and the entries
@@ -87,7 +89,7 @@ the stat information from updated paths, `st_mtime` timestamp of
 it is usually the same as or newer than any of the paths the
 index contains.  And no matter how quick the modification that
 follows `git update-index foo` finishes, the resulting
-`st_mtime` timestamp on `foo` cannot get the timestamp earlier
+`st_mtime` timestamp on `foo` cannot get a value earlier
 than the index file.  Therefore, index entries that can be
 racily clean are limited to the ones that have the same
 timestamp as the index file itself.
@@ -111,7 +113,7 @@ value, and falsely clean entry `foo` would not be caught by the
 timestamp comparison check done with the former logic anymore.
 The latter makes sure that the cached stat information for `foo`
 would never match with the file in the working tree, so later
-checks by `ce_match_stat_basic()` would report the index entry
+checks by `ce_match_stat_basic()` would report that the index entry
 does not match the file and git does not have to fall back on more
 expensive `ce_modified_check_fs()`.
 
@@ -155,17 +157,16 @@ of the cached stat information.
 Avoiding runtime penalty
 ------------------------
 
-In order to avoid the above runtime penalty, the recent "master"
-branch (post 1.4.2) has a code that makes sure the index file
-gets timestamp newer than the youngest files in the index when
+In order to avoid the above runtime penalty, post 1.4.2 git used
+to have a code that made sure the index file
+got timestamp newer than the youngest files in the index when
 there are many young files with the same timestamp as the
 resulting index file would otherwise would have by waiting
 before finishing writing the index file out.
 
-I suspect that in practice the situation where many paths in the
-index are all racily clean is quite rare.  The only code paths
-that can record recent timestamp for large number of paths I
-know of are:
+I suspected that in practice the situation where many paths in the
+index are all racily clean was quite rare.  The only code paths
+that can record recent timestamp for large number of paths are:
 
 . Initial `git add .` of a large project.
 
@@ -188,6 +189,7 @@ youngest file in the working tree.  This means that in these
 cases there actually will not be any racily clean entry in
 the resulting index.
 
-So in summary I think we should not worry about avoiding the
-runtime penalty and get rid of the "wait before finishing
-writing" code out.
+Based on this discussion, the current code does not use the
+"workaround" to avoid the runtime penalty that does not exist in
+practice anymore.  This was done with commit 0fc82cff on Aug 15,
+2006.
diff --git a/Documentation/technical/send-pack-pipeline.txt b/Documentation/technical/send-pack-pipeline.txt
new file mode 100644 (file)
index 0000000..681efe4
--- /dev/null
@@ -0,0 +1,63 @@
+git-send-pack
+=============
+
+Overall operation
+-----------------
+
+. Connects to the remote side and invokes git-receive-pack.
+
+. Learns what refs the remote has and what commit they point at.
+  Matches them to the refspecs we are pushing.
+
+. Checks if there are non-fast-forwards.  Unlike fetch-pack,
+  the repository send-pack runs in is supposed to be a superset
+  of the recipient in fast-forward cases, so there is no need
+  for want/have exchanges, and fast-forward check can be done
+  locally.  Tell the result to the other end.
+
+. Calls pack_objects() which generates a packfile and sends it
+  over to the other end.
+
+. If the remote side is new enough (v1.1.0 or later), wait for
+  the unpack and hook status from the other end.
+
+. Exit with appropriate error codes.
+
+
+Pack_objects pipeline
+---------------------
+
+This function gets one file descriptor (`fd`) which is either a
+socket (over the network) or a pipe (local).  What's written to
+this fd goes to git-receive-pack to be unpacked.
+
+    send-pack ---> fd ---> receive-pack
+
+The function pack_objects creates a pipe and then forks.  The
+forked child execs pack-objects with --revs to receive revision
+parameters from its standard input. This process will write the
+packfile to the other end.
+
+    send-pack
+       |
+       pack_objects() ---> fd ---> receive-pack
+          | ^ (pipe)
+         v |
+         (child)
+
+The child dup2's to arrange its standard output to go back to
+the other end, and read its standard input to come from the
+pipe.  After that it exec's pack-objects.  On the other hand,
+the parent process, before starting to feed the child pipeline,
+closes the reading side of the pipe and fd to receive-pack.
+
+    send-pack
+       |
+       pack_objects(parent)
+          |
+         v [0]
+         pack-objects [0] ---> receive-pack
+
+
+[jc: the pipeline was much more complex and needed documentation before
+ I understood an earlier bug, but now it is trivial and straightforward.]
index 42b6e7d7d2bbb24d96f29635967945f516b25e15..8d899927124104d2b472f1ce63f97346a84e2d2c 100644 (file)
@@ -17,18 +17,19 @@ Let's start a new project and create a small amount of history:
 ------------------------------------------------
 $ mkdir test-project
 $ cd test-project
-$ git init-db
-defaulting to local storage area
+$ git init
+Initialized empty Git repository in .git/
 $ echo 'hello world' > file.txt
 $ git add .
 $ git commit -a -m "initial commit"
-Committing initial tree 92b8b694ffb1675e5975148e1121810081dbdffe
+Created initial commit 54196cc2703dc165cbd373a65a4dcf22d50ae7f7
+ create mode 100644 file.txt
 $ echo 'hello world!' >file.txt
 $ git commit -a -m "add emphasis"
+Created commit c4d59f390b9cfd4318117afde11d601c1085f241
 ------------------------------------------------
 
-What are the 40 digits of hex that git responded to the first commit
-with?
+What are the 40 digits of hex that git responded to the commit with?
 
 We saw in part one of the tutorial that commits have names like this.
 It turns out that every object in the git history is stored under
@@ -38,13 +39,25 @@ the same data twice (since identical data is given an identical SHA1
 name), and that the contents of a git object will never change (since
 that would change the object's name as well).
 
+It is expected that the content of the commit object you created while
+following the example above generates a different SHA1 hash than
+the one shown above because the commit object records the time when
+it was created and the name of the person performing the commit.
+
 We can ask git about this particular object with the cat-file
-command--just cut-and-paste from the reply to the initial commit, to
-save yourself typing all 40 hex digits:
+command. Don't copy the 40 hex digits from this example but use those
+from your own version. Note that you can shorten it to only a few
+characters to save yourself typing all 40 hex digits:
 
 ------------------------------------------------
-$ git cat-file -t 92b8b694ffb1675e5975148e1121810081dbdffe
-tree
+$ git-cat-file -t 54196cc2
+commit
+$ git-cat-file commit 54196cc2
+tree 92b8b694ffb1675e5975148e1121810081dbdffe
+author J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+committer J. Bruce Fields <bfields@puzzle.fieldses.org> 1143414668 -0500
+
+initial commit
 ------------------------------------------------
 
 A tree can refer to one or more "blob" objects, each corresponding to
@@ -101,8 +114,7 @@ $ find .git/objects/
 
 and the contents of these files is just the compressed data plus a
 header identifying their length and their type.  The type is either a
-blob, a tree, a commit, or a tag.  We've seen a blob and a tree now,
-so next we should look at a commit.
+blob, a tree, a commit, or a tag.
 
 The simplest commit to find is the HEAD commit, which we can find
 from .git/HEAD:
@@ -331,8 +343,8 @@ And, as you can see with cat-file, this new entry refers to the
 current contents of the file:
 
 ------------------------------------------------
-$ git cat-file blob a6b11f7a
-goodbye, word
+$ git cat-file blob 8b9743b2
+goodbye, world
 ------------------------------------------------
 
 The "status" command is a useful way to get a quick summary of the
@@ -340,22 +352,21 @@ situation:
 
 ------------------------------------------------
 $ git status
-#
-# Updated but not checked in:
-#   (will commit)
+# On branch master
+# Changes to be committed:
+#   (use "git reset HEAD <file>..." to unstage)
 #
 #       new file: closing.txt
 #
-#
 # Changed but not updated:
-#   (use git-update-index to mark for commit)
+#   (use "git add <file>..." to update what will be committed)
 #
 #       modified: file.txt
 #
 ------------------------------------------------
 
 Since the current state of closing.txt is cached in the index file,
-it is listed as "updated but not checked in".  Since file.txt has
+it is listed as "Changes to be committed".  Since file.txt has
 changes in the working directory that aren't reflected in the index,
 it is marked "changed but not updated".  At this point, running "git
 commit" would create a commit that added closing.txt (with its new
index 1e4ddfbd114dde8949dc53d423715673a748854f..129c5c5f5b8657175db7efed2c06078affd05ab6 100644 (file)
@@ -11,6 +11,16 @@ diff" with:
 $ man git-diff
 ------------------------------------------------
 
+It is a good idea to introduce yourself to git with your name and
+public email address before doing any operation.  The easiest
+way to do so is:
+
+------------------------------------------------
+$ git config --global user.name "Your Name Comes Here"
+$ git config --global user.email you@yourdomain.example.com
+------------------------------------------------
+
+
 Importing a new project
 -----------------------
 
@@ -20,18 +30,18 @@ can place it under git revision control as follows.
 ------------------------------------------------
 $ tar xzf project.tar.gz
 $ cd project
-$ git init-db
+$ git init
 ------------------------------------------------
 
 Git will reply
 
 ------------------------------------------------
-defaulting to local storage area
+Initialized empty Git repository in .git/
 ------------------------------------------------
 
 You've now initialized the working directory--you may notice a new
 directory created, named ".git".  Tell git that you want it to track
-every file under the current directory with
+every file under the current directory (note the '.') with:
 
 ------------------------------------------------
 $ git add .
@@ -40,42 +50,90 @@ $ git add .
 Finally,
 
 ------------------------------------------------
-$ git commit -a
+$ git commit
 ------------------------------------------------
 
 will prompt you for a commit message, then record the current state
 of all the files to the repository.
 
+Making changes
+--------------
+
 Try modifying some files, then run
 
 ------------------------------------------------
 $ git diff
 ------------------------------------------------
 
-to review your changes.  When you're done,
+to review your changes.  When you're done, tell git that you
+want the updated contents of these files in the commit and then
+make a commit, like this:
+
+------------------------------------------------
+$ git add file1 file2 file3
+$ git commit
+------------------------------------------------
+
+This will again prompt your for a message describing the change, and then
+record the new versions of the files you listed.
+
+Alternatively, instead of running `git add` beforehand, you can use
 
 ------------------------------------------------
 $ git commit -a
 ------------------------------------------------
 
-will again prompt your for a message describing the change, and then
-record the new versions of the modified files.
+which will automatically notice modified (but not new) files.
 
 A note on commit messages: Though not required, it's a good idea to
 begin the commit message with a single short (less than 50 character)
 line summarizing the change, followed by a blank line and then a more
 thorough description.  Tools that turn commits into email, for
-example, use the first line on the Subject line and the rest of the
+example, use the first line on the Subject: line and the rest of the
 commit in the body.
 
-To add a new file, first create the file, then
 
-------------------------------------------------
-$ git add path/to/new/file
-------------------------------------------------
+Git tracks content not files
+----------------------------
+
+With git you have to explicitly "add" all the changed _content_ you
+want to commit together. This can be done in a few different ways:
+
+1) By using 'git add <file_spec>...'
+
+This can be performed multiple times before a commit.  Note that this
+is not only for adding new files.  Even modified files must be
+added to the set of changes about to be committed.  The "git status"
+command gives you a summary of what is included so far for the
+next commit.  When done you should use the 'git commit' command to
+make it real.
+
+Note: don't forget to 'add' a file again if you modified it after the
+first 'add' and before 'commit'. Otherwise only the previous added
+state of that file will be committed. This is because git tracks
+content, so what you're really 'add'ing to the commit is the *content*
+of the file in the state it is in when you 'add' it.
+
+2) By using 'git commit -a' directly
 
-then commit as usual.  No special command is required when removing a
-file; just remove it, then commit.
+This is a quick way to automatically 'add' the content from all files
+that were modified since the previous commit, and perform the actual
+commit without having to separately 'add' them beforehand.  This will
+not add content from new files i.e. files that were never added before.
+Those files still have to be added explicitly before performing a
+commit.
+
+But here's a twist. If you do 'git commit <file1> <file2> ...' then only
+the  changes belonging to those explicitly specified files will be
+committed, entirely bypassing the current "added" changes. Those "added"
+changes will still remain available for a subsequent commit though.
+
+However, for normal usage you only have to remember 'git add' + 'git commit'
+and/or 'git commit -a'.
+
+
+Viewing the changelog
+---------------------
 
 At any point you can view the history of your changes using
 
@@ -89,6 +147,13 @@ If you also want to see complete diffs at each step, use
 $ git log -p
 ------------------------------------------------
 
+Often the overview of the change is useful to get a feel of
+each step
+
+------------------------------------------------
+$ git log --stat --summary
+------------------------------------------------
+
 Managing branches
 -----------------
 
@@ -144,7 +209,7 @@ at this point the two branches have diverged, with different changes
 made in each.  To merge the changes made in experimental into master, run
 
 ------------------------------------------------
-$ git pull . experimental
+$ git merge experimental
 ------------------------------------------------
 
 If the changes don't conflict, you're done.  If there are conflicts,
@@ -169,6 +234,15 @@ $ gitk
 
 will show a nice graphical representation of the resulting history.
 
+At this point you could delete the experimental branch with
+
+------------------------------------------------
+$ git branch -d experimental
+------------------------------------------------
+
+This command ensures that the changes in the experimental branch are
+already in the current branch.
+
 If you develop on a branch crazy-idea, then regret it, you can always
 delete the branch with
 
@@ -209,47 +283,63 @@ at /home/bob/myrepo.  She does this with:
 
 ------------------------------------------------
 $ cd /home/alice/project
-$ git pull /home/bob/myrepo
+$ git pull /home/bob/myrepo master
 ------------------------------------------------
 
-This actually pulls changes from the branch in Bob's repository named
-"master".  Alice could request a different branch by adding the name
-of the branch to the end of the git pull command line.
+This merges the changes from Bob's "master" branch into Alice's
+current branch.  If Alice has made her own changes in the meantime,
+then she may need to manually fix any conflicts.  (Note that the
+"master" argument in the above command is actually unnecessary, as it
+is the default.)
+
+The "pull" command thus performs two operations: it fetches changes
+from a remote branch, then merges them into the current branch.
 
-This merges Bob's changes into her repository; "git log" will
-now show the new commits.  If Alice has made her own changes in the
-meantime, then Bob's changes will be merged in, and she will need to
-manually fix any conflicts.
+When you are working in a small closely knit group, it is not
+unusual to interact with the same repository over and over
+again.  By defining 'remote' repository shorthand, you can make
+it easier:
+
+------------------------------------------------
+$ git remote add bob /home/bob/myrepo
+------------------------------------------------
 
-A more cautious Alice might wish to examine Bob's changes before
-pulling them.  She can do this by creating a temporary branch just
-for the purpose of studying Bob's changes:
+With this, you can perform the first operation alone using the
+"git fetch" command without merging them with her own branch,
+using:
 
 -------------------------------------
-$ git fetch /home/bob/myrepo master:bob-incoming
+$ git fetch bob
 -------------------------------------
 
-which fetches the changes from Bob's master branch into a new branch
-named bob-incoming.  (Unlike git pull, git fetch just fetches a copy
-of Bob's line of development without doing any merging).  Then
+Unlike the longhand form, when Alice fetches from Bob using a
+remote repository shorthand set up with `git remote`, what was
+fetched is stored in a remote tracking branch, in this case
+`bob/master`.  So after this:
 
 -------------------------------------
-$ git log -p master..bob-incoming
+$ git log -p master..bob/master
 -------------------------------------
 
 shows a list of all the changes that Bob made since he branched from
 Alice's master branch.
 
-After examining those changes, and possibly fixing things, Alice can
-pull the changes into her master branch:
+After examining those changes, Alice
+could merge the changes into her master branch:
 
 -------------------------------------
-$ git checkout master
-$ git pull . bob-incoming
+$ git merge bob/master
+-------------------------------------
+
+This `merge` can also be done by 'pulling from her own remote
+tracking branch', like this:
+
+-------------------------------------
+$ git pull . remotes/bob/master
 -------------------------------------
 
-The last command is a pull from the "bob-incoming" branch in Alice's
-own repository.
+Note that git pull always merges into the current branch,
+regardless of what else is given on the commandline.
 
 Later, Bob can update his repo with Alice's latest changes using
 
@@ -259,20 +349,25 @@ $ git pull
 
 Note that he doesn't need to give the path to Alice's repository;
 when Bob cloned Alice's repository, git stored the location of her
-repository in the file .git/remotes/origin, and that location is used
-as the default for pulls.
-
-Bob may also notice a branch in his repository that he didn't create:
+repository in the repository configuration, and that location is
+used for pulls:
 
 -------------------------------------
-$ git branch
-* master
-  origin
+$ git config --get remote.origin.url
+/home/bob/myrepo
 -------------------------------------
 
-The "origin" branch, which was created automatically by "git clone",
-is a pristine copy of Alice's master branch; Bob should never commit
-to it.
+(The complete configuration created by git-clone is visible using
+"git config -l", and the gitlink:git-config[1] man page
+explains the meaning of each option.)
+
+Git also keeps a pristine copy of Alice's master branch under the
+name "origin/master":
+
+-------------------------------------
+$ git branch -r
+  origin/master
+-------------------------------------
 
 If Bob later decides to work from a different host, he can still
 perform clones and pulls using the ssh protocol:
@@ -312,7 +407,7 @@ commit.
 $ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7
 -------------------------------------
 
-But there other ways to refer to commits.  You can use any initial
+But there are other ways to refer to commits.  You can use any initial
 part of the name that is long enough to uniquely identify the commit:
 
 -------------------------------------
@@ -322,8 +417,8 @@ $ git show HEAD             # the tip of the current branch
 $ git show experimental        # the tip of the "experimental" branch
 -------------------------------------
 
-Every commit has at least one "parent" commit, which points to the
-previous state of the project:
+Every commit usually has one "parent" commit
+which points to the previous state of the project:
 
 -------------------------------------
 $ git show HEAD^  # to see the parent of HEAD
@@ -363,9 +458,11 @@ $ git reset --hard HEAD^ # reset your current branch and working
 Be careful with that last command: in addition to losing any changes
 in the working directory, it will also remove all later commits from
 this branch.  If this branch is the only branch containing those
-commits, they will be lost.  (Also, don't use "git reset" on a
-publicly-visible branch that other developers pull from, as git will
-be confused by history that disappears in this way.)
+commits, they will be lost.  Also, don't use "git reset" on a
+publicly-visible branch that other developers pull from, as it will
+force needless merges on other developers to clean up the history.
+If you need to undo changes that you have pushed, use gitlink:git-revert[1]
+instead.
 
 The git grep command can search for strings in any version of your
 project, so
@@ -441,10 +538,10 @@ of the file:
 $ git diff v2.5:Makefile HEAD:Makefile.in
 -------------------------------------
 
-You can also use "git cat-file -p" to see any such file:
+You can also use "git show" to see any such file:
 
 -------------------------------------
-$ git cat-file -p v2.5:Makefile
+$ git show v2.5:Makefile
 -------------------------------------
 
 Next Steps
index 670827c323fe276124ba1a84d59386771db868f1..745f9677d005b522f52496339abd5afc4267a815 100644 (file)
@@ -40,10 +40,13 @@ 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:`
@@ -54,10 +57,13 @@ 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>
+------------
+       [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
@@ -68,10 +74,15 @@ name of remote head (URL fragment notation).
 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>
+------------
diff --git a/Documentation/user-manual.conf b/Documentation/user-manual.conf
new file mode 100644 (file)
index 0000000..92b01ec
--- /dev/null
@@ -0,0 +1,21 @@
+[titles]
+       underlines="__","==","--","~~","^^"
+
+[attributes]
+caret=^
+startsb=&#91;
+endsb=&#93;
+tilde=&#126;
+
+[gitlink-inlinemacro]
+<ulink url="{target}.html">{target}{0?({0})}</ulink>
+
+ifdef::backend-docbook[]
+# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
+[listingblock]
+<example><title>{title}</title>
+<literallayout>
+|
+</literallayout>
+{title#}</example>
+endif::backend-docbook[]
diff --git a/Documentation/user-manual.txt b/Documentation/user-manual.txt
new file mode 100644 (file)
index 0000000..03736bb
--- /dev/null
@@ -0,0 +1,2961 @@
+Git User's Manual
+_________________
+
+This manual is designed to be readable by someone with basic unix
+commandline skills, but no previous knowledge of git.
+
+Chapter 1 gives a brief overview of git commands, without any
+explanation; you may prefer to skip to chapter 2 on a first reading.
+
+Chapters 2 and 3 explain how to fetch and study a project using
+git--the tools you'd need to build and test a particular version of a
+software project, to search for regressions, and so on.
+
+Chapter 4 explains how to do development with git, and chapter 5 how
+to share that development with others.
+
+Further chapters cover more specialized topics.
+
+Comprehensive reference documentation is available through the man
+pages.  For a command such as "git clone", just use
+
+------------------------------------------------
+$ man git-clone
+------------------------------------------------
+
+Git Quick Start
+===============
+
+This is a quick summary of the major commands; the following chapters
+will explain how these work in more detail.
+
+Creating a new repository
+-------------------------
+
+From a tarball:
+
+-----------------------------------------------
+$ tar xzf project.tar.gz
+$ cd project
+$ git init
+Initialized empty Git repository in .git/
+$ git add .
+$ git commit
+-----------------------------------------------
+
+From a remote repository:
+
+-----------------------------------------------
+$ git clone git://example.com/pub/project.git
+$ cd project
+-----------------------------------------------
+
+Managing branches
+-----------------
+
+-----------------------------------------------
+$ git branch        # list all branches in this repo
+$ git checkout test  # switch working directory to branch "test"
+$ git branch new     # create branch "new" starting at current HEAD
+$ git branch -d new  # delete branch "new"
+-----------------------------------------------
+
+Instead of basing new branch on current HEAD (the default), use:
+
+-----------------------------------------------
+$ git branch new test    # branch named "test"
+$ git branch new v2.6.15 # tag named v2.6.15
+$ git branch new HEAD^   # commit before the most recent
+$ git branch new HEAD^^  # commit before that
+$ git branch new test~10 # ten commits before tip of branch "test"
+-----------------------------------------------
+
+Create and switch to a new branch at the same time:
+
+-----------------------------------------------
+$ git checkout -b new v2.6.15
+-----------------------------------------------
+
+Update and examine branches from the repository you cloned from:
+
+-----------------------------------------------
+$ git fetch            # update
+$ git branch -r                # list
+  origin/master
+  origin/next
+  ...
+$ git branch checkout -b masterwork origin/master
+-----------------------------------------------
+
+Fetch a branch from a different repository, and give it a new
+name in your repository:
+
+-----------------------------------------------
+$ git fetch git://example.com/project.git theirbranch:mybranch
+$ git fetch git://example.com/project.git v2.6.15:mybranch
+-----------------------------------------------
+
+Keep a list of repositories you work with regularly:
+
+-----------------------------------------------
+$ git remote add example git://example.com/project.git
+$ git remote                   # list remote repositories
+example
+origin
+$ git remote show example      # get details
+* remote example
+  URL: git://example.com/project.git
+  Tracked remote branches
+    master next ...
+$ git fetch example            # update branches from example
+$ git branch -r                        # list all remote branches
+-----------------------------------------------
+
+
+Exploring history
+-----------------
+
+-----------------------------------------------
+$ gitk                     # visualize and browse history
+$ git log                  # list all commits
+$ git log src/             # ...modifying src/
+$ git log v2.6.15..v2.6.16  # ...in v2.6.16, not in v2.6.15
+$ git log master..test     # ...in branch test, not in branch master
+$ git log test..master     # ...in branch master, but not in test
+$ git log test...master            # ...in one branch, not in both
+$ git log -S'foo()'        # ...where difference contain "foo()"
+$ git log --since="2 weeks ago"
+$ git log -p               # show patches as well
+$ git show                 # most recent commit
+$ git diff v2.6.15..v2.6.16 # diff between two tagged versions
+$ git diff v2.6.15..HEAD    # diff with current head
+$ git grep "foo()"         # search working directory for "foo()"
+$ git grep v2.6.15 "foo()"  # search old tree for "foo()"
+$ git show v2.6.15:a.txt    # look at old version of a.txt
+-----------------------------------------------
+
+Search for regressions:
+
+-----------------------------------------------
+$ git bisect start
+$ git bisect bad               # current version is bad
+$ git bisect good v2.6.13-rc2  # last known good revision
+Bisecting: 675 revisions left to test after this
+                               # test here, then:
+$ git bisect good              # if this revision is good, or
+$ git bisect bad               # if this revision is bad.
+                               # repeat until done.
+-----------------------------------------------
+
+Making changes
+--------------
+
+Make sure git knows who to blame:
+
+------------------------------------------------
+$ cat >~/.gitconfig <<\EOF
+[user]
+name = Your Name Comes Here
+email = you@yourdomain.example.com
+EOF
+------------------------------------------------
+
+Select file contents to include in the next commit, then make the
+commit:
+
+-----------------------------------------------
+$ git add a.txt    # updated file
+$ git add b.txt    # new file
+$ git rm c.txt     # old file
+$ git commit
+-----------------------------------------------
+
+Or, prepare and create the commit in one step:
+
+-----------------------------------------------
+$ git commit d.txt # use latest content only of d.txt
+$ git commit -a           # use latest content of all tracked files
+-----------------------------------------------
+
+Merging
+-------
+
+-----------------------------------------------
+$ git merge test   # merge branch "test" into the current branch
+$ git pull git://example.com/project.git master
+                  # fetch and merge in remote branch
+$ git pull . test  # equivalent to git merge test
+-----------------------------------------------
+
+Sharing your changes
+--------------------
+
+Importing or exporting patches:
+
+-----------------------------------------------
+$ git format-patch origin..HEAD # format a patch for each commit
+                               # in HEAD but not in origin
+$ git-am mbox # import patches from the mailbox "mbox"
+-----------------------------------------------
+
+Fetch a branch in a different git repository, then merge into the
+current branch:
+
+-----------------------------------------------
+$ git pull git://example.com/project.git theirbranch
+-----------------------------------------------
+
+Store the fetched branch into a local branch before merging into the
+current branch:
+
+-----------------------------------------------
+$ git pull git://example.com/project.git theirbranch:mybranch
+-----------------------------------------------
+
+After creating commits on a local branch, update the remote
+branch with your commits:
+
+-----------------------------------------------
+$ git push ssh://example.com/project.git mybranch:theirbranch
+-----------------------------------------------
+
+When remote and local branch are both named "test":
+
+-----------------------------------------------
+$ git push ssh://example.com/project.git test
+-----------------------------------------------
+
+Shortcut version for a frequently used remote repository:
+
+-----------------------------------------------
+$ git remote add example ssh://example.com/project.git
+$ git push example test
+-----------------------------------------------
+
+Repository maintenance
+----------------------
+
+Check for corruption:
+
+-----------------------------------------------
+$ git fsck
+-----------------------------------------------
+
+Recompress, remove unused cruft:
+
+-----------------------------------------------
+$ git gc
+-----------------------------------------------
+
+Repositories and Branches
+=========================
+
+How to get a git repository
+---------------------------
+
+It will be useful to have a git repository to experiment with as you
+read this manual.
+
+The best way to get one is by using the gitlink:git-clone[1] command
+to download a copy of an existing repository for a project that you
+are interested in.  If you don't already have a project in mind, here
+are some interesting examples:
+
+------------------------------------------------
+       # git itself (approx. 10MB download):
+$ git clone git://git.kernel.org/pub/scm/git/git.git
+       # the linux kernel (approx. 150MB download):
+$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git
+------------------------------------------------
+
+The initial clone may be time-consuming for a large project, but you
+will only need to clone once.
+
+The clone command creates a new directory named after the project
+("git" or "linux-2.6" in the examples above).  After you cd into this
+directory, you will see that it contains a copy of the project files,
+together with a special top-level directory named ".git", which
+contains all the information about the history of the project.
+
+In most of the following, examples will be taken from one of the two
+repositories above.
+
+How to check out a different version of a project
+-------------------------------------------------
+
+Git is best thought of as a tool for storing the history of a
+collection of files.  It stores the history as a compressed
+collection of interrelated snapshots (versions) of the project's
+contents.
+
+A single git repository may contain multiple branches.  Each branch
+is a bookmark referencing a particular point in the project history.
+The gitlink:git-branch[1] command shows you the list of branches:
+
+------------------------------------------------
+$ git branch
+* master
+------------------------------------------------
+
+A freshly cloned repository contains a single branch, named "master",
+and the working directory contains the version of the project
+referred to by the master branch.
+
+Most projects also use tags.  Tags, like branches, are references
+into the project's history, and can be listed using the
+gitlink:git-tag[1] command:
+
+------------------------------------------------
+$ git tag -l
+v2.6.11
+v2.6.11-tree
+v2.6.12
+v2.6.12-rc2
+v2.6.12-rc3
+v2.6.12-rc4
+v2.6.12-rc5
+v2.6.12-rc6
+v2.6.13
+...
+------------------------------------------------
+
+Tags are expected to always point at the same version of a project,
+while branches are expected to advance as development progresses.
+
+Create a new branch pointing to one of these versions and check it
+out using gitlink:git-checkout[1]:
+
+------------------------------------------------
+$ git checkout -b new v2.6.13
+------------------------------------------------
+
+The working directory then reflects the contents that the project had
+when it was tagged v2.6.13, and gitlink:git-branch[1] shows two
+branches, with an asterisk marking the currently checked-out branch:
+
+------------------------------------------------
+$ git branch
+  master
+* new
+------------------------------------------------
+
+If you decide that you'd rather see version 2.6.17, you can modify
+the current branch to point at v2.6.17 instead, with
+
+------------------------------------------------
+$ git reset --hard v2.6.17
+------------------------------------------------
+
+Note that if the current branch was your only reference to a
+particular point in history, then resetting that branch may leave you
+with no way to find the history it used to point to; so use this
+command carefully.
+
+Understanding History: Commits
+------------------------------
+
+Every change in the history of a project is represented by a commit.
+The gitlink:git-show[1] command shows the most recent commit on the
+current branch:
+
+------------------------------------------------
+$ git show
+commit 2b5f6dcce5bf94b9b119e9ed8d537098ec61c3d2
+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>
+
+diff --git a/Documentation/networking/xfrm_sync.txt b/Documentation/networking/xfrm_sync.txt
+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;
+              __u32                           flags;
++             __u32                           reqid;
+    };
+...
+------------------------------------------------
+
+As you can see, a commit shows who made the latest change, what they
+did, and why.
+
+Every commit has a 40-hexdigit id, sometimes called the "object name"
+or the "SHA1 id", shown on the first line of the "git show" output.
+You can usually refer to a commit by a shorter name, such as a tag or a
+branch name, but this longer name can also be useful.  Most
+importantly, it is a globally unique name for this commit: so if you
+tell somebody else the object name (for example in email), then you are
+guaranteed that name will refer to the same commit in their repository
+that it does in yours (assuming their repository has that commit at
+all).
+
+Understanding history: commits, parents, and reachability
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Every commit (except the very first commit in a project) also has a
+parent commit which shows what happened before this commit.
+Following the chain of parents will eventually take you back to the
+beginning of the project.
+
+However, the commits do not form a simple list; git allows lines of
+development to diverge and then reconverge, and the point where two
+lines of development reconverge is called a "merge".  The commit
+representing a merge can therefore have more than one parent, with
+each parent representing the most recent commit on one of the lines
+of development leading to that point.
+
+The best way to see how this works is using the gitlink:gitk[1]
+command; running gitk now on a git repository and looking for merge
+commits will help understand how the git organizes history.
+
+In the following, we say that commit X is "reachable" from commit Y
+if commit X is an ancestor of commit Y.  Equivalently, you could say
+that Y is a descendent of X, or that there is a chain of parents
+leading from commit Y to commit X.
+
+Understanding history: History diagrams
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+We will sometimes represent git history using diagrams like the one
+below.  Commits are shown as "o", and the links between them with
+lines drawn with - / and \.  Time goes left to right:
+
+         o--o--o <-- Branch A
+        /
+ o--o--o <-- master
+        \
+         o--o--o <-- Branch B
+
+If we need to talk about a particular commit, the character "o" may
+be replaced with another letter or number.
+
+Understanding history: What is a branch?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Though we've been using the word "branch" to mean a kind of reference
+to a particular commit, the word branch is also commonly used to
+refer to the line of commits leading up to that point.  In the
+example above, git may think of the branch named "A" as just a
+pointer to one particular commit, but we may refer informally to the
+line of three commits leading up to that point as all being part of
+"branch A".
+
+If we need to make it clear that we're just talking about the most
+recent commit on the branch, we may refer to that commit as the
+"head" of the branch.
+
+Manipulating branches
+---------------------
+
+Creating, deleting, and modifying branches is quick and easy; here's
+a summary of the commands:
+
+git branch::
+       list all branches
+git branch <branch>::
+       create a new branch named <branch>, referencing the same
+       point in history as the current branch
+git branch <branch> <start-point>::
+       create a new branch named <branch>, referencing
+       <start-point>, which may be specified any way you like,
+       including using a branch name or a tag name
+git branch -d <branch>::
+       delete the branch <branch>; if the branch you are deleting
+       points to a commit which is not reachable from this branch,
+       this command will fail with a warning.
+git branch -D <branch>::
+       even if the branch points to a commit not reachable
+       from the current branch, you may know that that commit
+       is still reachable from some other branch or tag.  In that
+       case it is safe to use this command to force git to delete
+       the branch.
+git checkout <branch>::
+       make the current branch <branch>, updating the working
+       directory to reflect the version referenced by <branch>
+git checkout -b <new> <start-point>::
+       create a new branch <new> referencing <start-point>, and
+       check it out.
+
+It is also useful to know that the special symbol "HEAD" can always
+be used to refer to the current branch.
+
+Examining branches from a remote repository
+-------------------------------------------
+
+The "master" branch that was created at the time you cloned is a copy
+of the HEAD in the repository that you cloned from.  That repository
+may also have had other branches, though, and your local repository
+keeps branches which track each of those remote branches, which you
+can view using the "-r" option to gitlink:git-branch[1]:
+
+------------------------------------------------
+$ git branch -r
+  origin/HEAD
+  origin/html
+  origin/maint
+  origin/man
+  origin/master
+  origin/next
+  origin/pu
+  origin/todo
+------------------------------------------------
+
+You cannot check out these remote-tracking branches, but you can
+examine them on a branch of your own, just as you would a tag:
+
+------------------------------------------------
+$ git checkout -b my-todo-copy origin/todo
+------------------------------------------------
+
+Note that the name "origin" is just the name that git uses by default
+to refer to the repository that you cloned from.
+
+[[how-git-stores-references]]
+Naming branches, tags, and other references
+-------------------------------------------
+
+Branches, remote-tracking branches, and tags are all references to
+commits.  All references are named with a slash-separated path name
+starting with "refs"; the names we've been using so far are actually
+shorthand:
+
+       - The branch "test" is short for "refs/heads/test".
+       - The tag "v2.6.18" is short for "refs/tags/v2.6.18".
+       - "origin/master" is short for "refs/remotes/origin/master".
+
+The full name is occasionally useful if, for example, there ever
+exists a tag and a branch with the same name.
+
+As another useful shortcut, if the repository "origin" posesses only
+a single branch, you can refer to that branch as just "origin".
+
+More generally, if you have defined a remote repository named
+"example", you can refer to the branch in that repository as
+"example".  And for a repository with multiple branches, this will
+refer to the branch designated as the "HEAD" branch.
+
+For the complete list of paths which git checks for references, and
+the order it uses to decide which to choose when there are multiple
+references with the same shorthand name, see the "SPECIFYING
+REVISIONS" section of gitlink:git-rev-parse[1].
+
+[[Updating-a-repository-with-git-fetch]]
+Updating a repository with git fetch
+------------------------------------
+
+Eventually the developer cloned from will do additional work in her
+repository, creating new commits and advancing the branches to point
+at the new commits.
+
+The command "git fetch", with no arguments, will update all of the
+remote-tracking branches to the latest version found in her
+repository.  It will not touch any of your own branches--not even the
+"master" branch that was created for you on clone.
+
+Fetching branches from other repositories
+-----------------------------------------
+
+You can also track branches from repositories other than the one you
+cloned from, using gitlink:git-remote[1]:
+
+-------------------------------------------------
+$ git remote add linux-nfs git://linux-nfs.org/pub/nfs-2.6.git
+$ git fetch
+* refs/remotes/linux-nfs/master: storing branch 'master' ...
+  commit: bf81b46
+-------------------------------------------------
+
+New remote-tracking branches will be stored under the shorthand name
+that you gave "git remote add", in this case linux-nfs:
+
+-------------------------------------------------
+$ git branch -r
+linux-nfs/master
+origin/master
+-------------------------------------------------
+
+If you run "git fetch <remote>" later, the tracking branches for the
+named <remote> will be updated.
+
+If you examine the file .git/config, you will see that git has added
+a new stanza:
+
+-------------------------------------------------
+$ cat .git/config
+...
+[remote "linux-nfs"]
+        url = git://linux-nfs.org/~bfields/git.git
+       fetch = +refs/heads/*:refs/remotes/linux-nfs-read/*
+...
+-------------------------------------------------
+
+This is what causes git to track the remote's branches; you may modify
+or delete these configuration options by editing .git/config with a
+text editor.  (See the "CONFIGURATION FILE" section of
+gitlink:git-config[1] for details.)
+
+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 relationships between these snapshots.
+
+Git provides extremely flexible and fast tools for exploring the
+history of a project.
+
+We start with one specialized tool that is useful for finding the
+commit that introduced a bug into a project.
+
+How to use bisect to find a regression
+--------------------------------------
+
+Suppose version 2.6.18 of your project worked, but the version at
+"master" crashes.  Sometimes the best way to find the cause of such a
+regression is to perform a brute-force search through the project's
+history to find the particular commit that caused the problem.  The
+gitlink:git-bisect[1] command can help you do this:
+
+-------------------------------------------------
+$ git bisect start
+$ git bisect good v2.6.18
+$ git bisect bad master
+Bisecting: 3537 revisions left to test after this
+[65934a9a028b88e83e2b0f8b36618fe503349f8e] BLOCK: Make USB storage depend on SCSI rather than selecting it [try #6]
+-------------------------------------------------
+
+If you run "git branch" at this point, you'll see that git has
+temporarily moved you to a new branch named "bisect".  This branch
+points to a commit (with commit id 65934...) that is reachable from
+v2.6.19 but not from v2.6.18.  Compile and test it, and see whether
+it crashes.  Assume it does crash.  Then:
+
+-------------------------------------------------
+$ git bisect bad
+Bisecting: 1769 revisions left to test after this
+[7eff82c8b1511017ae605f0c99ac275a7e21b867] i2c-core: Drop useless bitmaskings
+-------------------------------------------------
+
+checks out an older version.  Continue like this, telling git at each
+stage whether the version it gives you is good or bad, and notice
+that the number of revisions left to test is cut approximately in
+half each time.
+
+After about 13 tests (in this case), it will output the commit id of
+the guilty commit.  You can then examine the commit with
+gitlink:git-show[1], find out who wrote it, and mail them your bug
+report with the commit id.  Finally, run
+
+-------------------------------------------------
+$ git bisect reset
+-------------------------------------------------
+
+to return you to the branch you were on before and delete the
+temporary "bisect" branch.
+
+Note that the version which git-bisect checks out for you at each
+point is just a suggestion, and you're free to try a different
+version if you think it would be a good idea.  For example,
+occasionally you may land on a commit that broke something unrelated;
+run
+
+-------------------------------------------------
+$ git bisect-visualize
+-------------------------------------------------
+
+which will run gitk and label the commit it chose with a marker that
+says "bisect".  Chose a safe-looking commit nearby, note its commit
+id, and check it out with:
+
+-------------------------------------------------
+$ git reset --hard fb47ddb2db...
+-------------------------------------------------
+
+then test, run "bisect good" or "bisect bad" as appropriate, and
+continue.
+
+Naming commits
+--------------
+
+We have seen several ways of naming commits already:
+
+       - 40-hexdigit object name
+       - branch name: refers to the commit at the head of the given
+         branch
+       - tag name: refers to the commit pointed to by the given tag
+         (we've seen branches and tags are special cases of
+         <<how-git-stores-references,references>>).
+       - HEAD: refers to the head of the current branch
+
+There are many more; see the "SPECIFYING REVISIONS" section of the
+gitlink:git-rev-parse[1] man page for the complete list of ways to
+name revisions.  Some examples:
+
+-------------------------------------------------
+$ git show fb47ddb2 # the first few characters of the object name
+                   # are usually enough to specify it uniquely
+$ git show HEAD^    # the parent of the HEAD commit
+$ git show HEAD^^   # the grandparent
+$ git show HEAD~4   # the great-great-grandparent
+-------------------------------------------------
+
+Recall that merge commits may have more than one parent; by default,
+^ and ~ follow the first parent listed in the commit, but you can
+also choose:
+
+-------------------------------------------------
+$ git show HEAD^1   # show the first parent of HEAD
+$ git show HEAD^2   # show the second parent of HEAD
+-------------------------------------------------
+
+In addition to HEAD, there are several other special names for
+commits:
+
+Merges (to be discussed later), as well as operations such as
+git-reset, which change the currently checked-out commit, generally
+set ORIG_HEAD to the value HEAD had before the current operation.
+
+The git-fetch operation always stores the head of the last fetched
+branch in FETCH_HEAD.  For example, if you run git fetch without
+specifying a local branch as the target of the operation
+
+-------------------------------------------------
+$ git fetch git://example.com/proj.git theirbranch
+-------------------------------------------------
+
+the fetched commits will still be available from FETCH_HEAD.
+
+When we discuss merges we'll also see the special name MERGE_HEAD,
+which refers to the other branch that we're merging in to the current
+branch.
+
+The gitlink:git-rev-parse[1] command is a low-level command that is
+occasionally useful for translating some name for a commit to the object
+name for that commit:
+
+-------------------------------------------------
+$ git rev-parse origin
+e05db0fd4f31dde7005f075a84f96b360d05984b
+-------------------------------------------------
+
+Creating tags
+-------------
+
+We can also create a tag to refer to a particular commit; after
+running
+
+-------------------------------------------------
+$ git-tag stable-1 1b2e1d63ff
+-------------------------------------------------
+
+You can use stable-1 to refer to the commit 1b2e1d63ff.
+
+This creates a "lightweight" tag.  If the tag is a tag you wish to
+share with others, and possibly sign cryptographically, then you
+should create a tag object instead; see the gitlink:git-tag[1] man
+page for details.
+
+Browsing revisions
+------------------
+
+The gitlink:git-log[1] command can show lists of commits.  On its
+own, it shows all commits reachable from the parent commit; but you
+can also make more specific requests:
+
+-------------------------------------------------
+$ git log v2.5..       # commits since (not reachable from) v2.5
+$ git log test..master # commits reachable from master but not test
+$ git log master..test # ...reachable from test but not master
+$ git log master...test        # ...reachable from either test or master,
+                       #    but not both
+$ git log --since="2 weeks ago" # commits from the last 2 weeks
+$ git log Makefile      # commits which modify Makefile
+$ git log fs/          # ... which modify any file under fs/
+$ git log -S'foo()'    # commits which add or remove any file data
+                       # matching the string 'foo()'
+-------------------------------------------------
+
+And of course you can combine all of these; the following finds
+commits since v2.5 which touch the Makefile or any file under fs:
+
+-------------------------------------------------
+$ git log v2.5.. Makefile fs/
+-------------------------------------------------
+
+You can also ask git log to show patches:
+
+-------------------------------------------------
+$ git log -p
+-------------------------------------------------
+
+See the "--pretty" option in the gitlink:git-log[1] man page for more
+display options.
+
+Note that git log starts with the most recent commit and works
+backwards through the parents; however, since git history can contain
+multiple independent lines of development, the particular order that
+commits are listed in may be somewhat arbitrary.
+
+Generating diffs
+----------------
+
+You can generate diffs between any two versions using
+gitlink:git-diff[1]:
+
+-------------------------------------------------
+$ git diff master..test
+-------------------------------------------------
+
+Sometimes what you want instead is a set of patches:
+
+-------------------------------------------------
+$ git format-patch master..test
+-------------------------------------------------
+
+will generate a file with a patch for each commit reachable from test
+but not from master.  Note that if master also has commits which are
+not reachable from test, then the combined result of these patches
+will not be the same as the diff produced by the git-diff example.
+
+Viewing old file versions
+-------------------------
+
+You can always view an old version of a file by just checking out the
+correct revision first.  But sometimes it is more convenient to be
+able to view an old version of a single file without checking
+anything out; this command does that:
+
+-------------------------------------------------
+$ git show v2.5:fs/locks.c
+-------------------------------------------------
+
+Before the colon may be anything that names a commit, and after it
+may be any path to a file tracked by git.
+
+Examples
+--------
+
+Check whether two branches point at the same history
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Suppose you want to check whether two branches point at the same point
+in history.
+
+-------------------------------------------------
+$ git diff origin..master
+-------------------------------------------------
+
+will tell you whether the contents of the project are the same at the
+two branches; in theory, however, it's possible that the same project
+contents could have been arrived at by two different historical
+routes.  You could compare the object names:
+
+-------------------------------------------------
+$ git rev-list origin
+e05db0fd4f31dde7005f075a84f96b360d05984b
+$ git rev-list master
+e05db0fd4f31dde7005f075a84f96b360d05984b
+-------------------------------------------------
+
+Or you could recall that the ... operator selects all commits
+contained reachable from either one reference or the other but not
+both: so
+
+-------------------------------------------------
+$ git log origin...master
+-------------------------------------------------
+
+will return no commits when the two branches are equal.
+
+Find first tagged version including a given fix
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Suppose you know that the commit e05db0fd fixed a certain problem.
+You'd like to find the earliest tagged release that contains that
+fix.
+
+Of course, there may be more than one answer--if the history branched
+after commit e05db0fd, then there could be multiple "earliest" tagged
+releases.
+
+You could just visually inspect the commits since e05db0fd:
+
+-------------------------------------------------
+$ gitk e05db0fd..
+-------------------------------------------------
+
+Or you can use gitlink:git-name-rev[1], which will give the commit a
+name based on any tag it finds pointing to one of the commit's
+descendants:
+
+-------------------------------------------------
+$ git name-rev e05db0fd
+e05db0fd tags/v1.5.0-rc1^0~23
+-------------------------------------------------
+
+The gitlink:git-describe[1] command does the opposite, naming the
+revision using a tag on which the given commit is based:
+
+-------------------------------------------------
+$ git describe e05db0fd
+v1.5.0-rc0-ge05db0f
+-------------------------------------------------
+
+but that may sometimes help you guess which tags might come after the
+given commit.
+
+If you just want to verify whether a given tagged version contains a
+given commit, you could use gitlink:git-merge-base[1]:
+
+-------------------------------------------------
+$ git merge-base e05db0fd v1.5.0-rc1
+e05db0fd4f31dde7005f075a84f96b360d05984b
+-------------------------------------------------
+
+The merge-base command finds a common ancestor of the given commits,
+and always returns one or the other in the case where one is a
+descendant of the other; so the above output shows that e05db0fd
+actually is an ancestor of v1.5.0-rc1.
+
+Alternatively, note that
+
+-------------------------------------------------
+$ git log v1.5.0-rc1..e05db0fd
+-------------------------------------------------
+
+will produce empty output if and only if v1.5.0-rc1 includes e05db0fd,
+because it outputs only commits that are not reachable from v1.5.0-rc1.
+
+As yet another alternative, the gitlink:git-show-branch[1] command lists
+the commits reachable from its arguments with a display on the left-hand
+side that indicates which arguments that commit is reachable from.  So,
+you can run something like
+
+-------------------------------------------------
+$ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2
+! [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if
+available
+ ! [v1.5.0-rc0] GIT v1.5.0 preview
+  ! [v1.5.0-rc1] GIT v1.5.0-rc1
+   ! [v1.5.0-rc2] GIT v1.5.0-rc2
+...
+-------------------------------------------------
+
+then search for a line that looks like
+
+-------------------------------------------------
++ ++ [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if
+available
+-------------------------------------------------
+
+Which shows that e05db0fd is reachable from itself, from v1.5.0-rc1, and
+from v1.5.0-rc2, but not from v1.5.0-rc0.
+
+
+Developing with git
+===================
+
+Telling git your name
+---------------------
+
+Before creating any commits, you should introduce yourself to git.  The
+easiest way to do so is:
+
+------------------------------------------------
+$ cat >~/.gitconfig <<\EOF
+[user]
+       name = Your Name Comes Here
+       email = you@yourdomain.example.com
+EOF
+------------------------------------------------
+
+(See the "CONFIGURATION FILE" section of gitlink:git-config[1] for
+details on the configuration file.)
+
+
+Creating a new repository
+-------------------------
+
+Creating a new repository from scratch is very easy:
+
+-------------------------------------------------
+$ mkdir project
+$ cd project
+$ git init
+-------------------------------------------------
+
+If you have some initial content (say, a tarball):
+
+-------------------------------------------------
+$ tar -xzvf project.tar.gz
+$ cd project
+$ git init
+$ git add . # include everything below ./ in the first commit:
+$ git commit
+-------------------------------------------------
+
+[[how-to-make-a-commit]]
+how to make a commit
+--------------------
+
+Creating a new commit takes three steps:
+
+       1. Making some changes to the working directory using your
+          favorite editor.
+       2. Telling git about your changes.
+       3. Creating the commit using the content you told git about
+          in step 2.
+
+In practice, you can interleave and repeat steps 1 and 2 as many
+times as you want: in order to keep track of what you want committed
+at step 3, git maintains a snapshot of the tree's contents in a
+special staging area called "the index."
+
+At the beginning, the content of the index will be identical to
+that of the HEAD.  The command "git diff --cached", which shows
+the difference between the HEAD and the index, should therefore
+produce no output at that point.
+
+Modifying the index is easy:
+
+To update the index with the new contents of a modified file, use
+
+-------------------------------------------------
+$ git add path/to/file
+-------------------------------------------------
+
+To add the contents of a new file to the index, use
+
+-------------------------------------------------
+$ git add path/to/file
+-------------------------------------------------
+
+To remove a file from the index and from the working tree,
+
+-------------------------------------------------
+$ git rm path/to/file
+-------------------------------------------------
+
+After each step you can verify that
+
+-------------------------------------------------
+$ git diff --cached
+-------------------------------------------------
+
+always shows the difference between the HEAD and the index file--this
+is what you'd commit if you created the commit now--and that
+
+-------------------------------------------------
+$ git diff
+-------------------------------------------------
+
+shows the difference between the working tree and the index file.
+
+Note that "git add" always adds just the current contents of a file
+to the index; further changes to the same file will be ignored unless
+you run git-add on the file again.
+
+When you're ready, just run
+
+-------------------------------------------------
+$ git commit
+-------------------------------------------------
+
+and git will prompt you for a commit message and then create the new
+commit.  Check to make sure it looks like what you expected with
+
+-------------------------------------------------
+$ git show
+-------------------------------------------------
+
+As a special shortcut,
+               
+-------------------------------------------------
+$ git commit -a
+-------------------------------------------------
+
+will update the index with any files that you've modified or removed
+and create a commit, all in one step.
+
+A number of commands are useful for keeping track of what you're
+about to commit:
+
+-------------------------------------------------
+$ git diff --cached # difference between HEAD and the index; what
+                   # would be commited 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.
+$ git status       # a brief per-file summary of the above.
+-------------------------------------------------
+
+creating good commit messages
+-----------------------------
+
+Though not required, it's a good idea to begin the commit message
+with a single short (less than 50 character) line summarizing the
+change, followed by a blank line and then a more thorough
+description.  Tools that turn commits into email, for example, use
+the first line on the Subject line and the rest of the commit in the
+body.
+
+how to merge
+------------
+
+You can rejoin two diverging branches of development using
+gitlink:git-merge[1]:
+
+-------------------------------------------------
+$ git merge branchname
+-------------------------------------------------
+
+merges the development in the branch "branchname" into the current
+branch.  If there are conflicts--for example, if the same file is
+modified in two different ways in the remote branch and the local
+branch--then you are warned; the output may look something like this:
+
+-------------------------------------------------
+$ git pull . next
+Trying really trivial in-index merge...
+fatal: Merge requires file-level merging
+Nope.
+Merging HEAD with 77976da35a11db4580b80ae27e8d65caf5208086
+Merging:
+15e2162 world
+77976da goodbye
+found 1 common ancestor(s):
+d122ed4 initial
+Auto-merging file.txt
+CONFLICT (content): Merge conflict in file.txt
+Automatic merge failed; fix conflicts and then commit the result.
+-------------------------------------------------
+
+Conflict markers are left in the problematic files, and after
+you resolve the conflicts manually, you can update the index
+with the contents and run git commit, as you normally would when
+creating a new file.
+
+If you examine the resulting commit using gitk, you will see that it
+has two parents, one pointing to the top of the current branch, and
+one to the top of the other branch.
+
+In more detail:
+
+[[resolving-a-merge]]
+Resolving a merge
+-----------------
+
+When a merge isn't resolved automatically, git leaves the index and
+the working tree in a special state that gives you all the
+information you need to help resolve the merge.
+
+Files with conflicts are marked specially in the index, so until you
+resolve the problem and update the index, git commit will fail:
+
+-------------------------------------------------
+$ git commit
+file.txt: needs merge
+-------------------------------------------------
+
+Also, git status will list those files as "unmerged".
+
+All of the changes that git was able to merge automatically are
+already added to the index file, so gitlink:git-diff[1] shows only
+the conflicts.  Also, it uses a somewhat unusual syntax:
+
+-------------------------------------------------
+$ git diff
+diff --cc file.txt
+index 802992c,2b60207..0000000
+--- a/file.txt
++++ b/file.txt
+@@@ -1,1 -1,1 +1,5 @@@
+++<<<<<<< HEAD:file.txt
+ +Hello world
+++=======
++ Goodbye
+++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
+-------------------------------------------------
+
+Recall that the commit which will be commited 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.
+
+The diff above shows the differences between the working-tree version
+of file.txt and two previous version: one version from HEAD, and one
+from MERGE_HEAD.  So instead of preceding each line by a single "+"
+or "-", it now uses two columns: the first column is used for
+differences between the first parent and the working directory copy,
+and the second for differences between the second parent and the
+working directory copy.  Thus after resolving the conflict in the
+obvious way, the diff will look like:
+
+-------------------------------------------------
+$ git diff
+diff --cc file.txt
+index 802992c,2b60207..0000000
+--- a/file.txt
++++ b/file.txt
+@@@ -1,1 -1,1 +1,1 @@@
+- Hello world
+ -Goodbye
+++Goodbye world
+-------------------------------------------------
+
+This shows that our resolved version deleted "Hello world" from the
+first parent, deleted "Goodbye" from the second parent, and added
+"Goodbye world", which was previously absent from both.
+
+The gitlink:git-log[1] command also provides special help for merges:
+
+-------------------------------------------------
+$ git log --merge
+-------------------------------------------------
+
+This will list all commits which exist only on HEAD or on MERGE_HEAD,
+and which touch an unmerged file.
+
+We can now add the resolved version to the index and commit:
+
+-------------------------------------------------
+$ git add file.txt
+$ git commit
+-------------------------------------------------
+
+Note that the commit message will already be filled in for you with
+some information about the merge.  Normally you can just use this
+default message unchanged, but you may add additional commentary of
+your own if desired.
+
+[[undoing-a-merge]]
+undoing a merge
+---------------
+
+If you get stuck and decide to just give up and throw the whole mess
+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,
+
+-------------------------------------------------
+$ git reset --hard HEAD^
+-------------------------------------------------
+
+However, this last command can be dangerous in some cases--never
+throw away a commit you have already committed if that commit may
+itself have been merged into another branch, as doing so may confuse
+further merges.
+
+Fast-forward merges
+-------------------
+
+There is one special case not mentioned above, which is treated
+differently.  Normally, a merge results in a merge commit, with two
+parents, one pointing at each of the two lines of development that
+were merged.
+
+However, if one of the two lines of development is completely
+contained within the other--so every commit present in the one is
+already contained in the other--then git just performs a
+<<fast-forwards,fast forward>>; the head of the current branch is
+moved forward to point at the head of the merged-in branch, without
+any new commits being created.
+
+Fixing mistakes
+---------------
+
+If you've messed up the working tree, but haven't yet committed your
+mistake, you can return the entire working tree to the last committed
+state with
+
+-------------------------------------------------
+$ git reset --hard HEAD
+-------------------------------------------------
+
+If you make a commit that you later wish you hadn't, there are two
+fundamentally different ways to fix the problem:
+
+       1. You can create a new commit that undoes whatever was done
+       by the previous commit.  This is the correct thing if your
+       mistake has already been made public.
+
+       2. You can go back and modify the old commit.  You should
+       never do this if you have already made the history public;
+       git does not normally expect the "history" of a project to
+       change, and cannot correctly perform repeated merges from
+       a branch that has had its history changed.
+
+Fixing a mistake with a new commit
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Creating a new commit that reverts an earlier change is very easy;
+just pass the gitlink:git-revert[1] command a reference to the bad
+commit; for example, to revert the most recent commit:
+
+-------------------------------------------------
+$ git revert HEAD
+-------------------------------------------------
+
+This will create a new commit which undoes the change in HEAD.  You
+will be given a chance to edit the commit message for the new commit.
+
+You can also revert an earlier change, for example, the next-to-last:
+
+-------------------------------------------------
+$ git revert HEAD^
+-------------------------------------------------
+
+In this case git will attempt to undo the old change while leaving
+intact any changes made since then.  If more recent changes overlap
+with the changes to be reverted, then you will be asked to fix
+conflicts manually, just as in the case of <<resolving-a-merge,
+resolving a merge>>.
+
+Fixing a mistake by editing history
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If the problematic commit is the most recent commit, and you have not
+yet made that commit public, then you may just
+<<undoing-a-merge,destroy it using git-reset>>.
+
+Alternatively, you
+can edit the working directory and update the index to fix your
+mistake, just as if you were going to <<how-to-make-a-commit,create a
+new commit>>, then run
+
+-------------------------------------------------
+$ git commit --amend
+-------------------------------------------------
+
+which will replace the old commit by a new commit incorporating your
+changes, giving you a chance to edit the old commit message first.
+
+Again, you should never do this to a commit that may already have
+been merged into another branch; use gitlink:git-revert[1] instead in
+that case.
+
+It is also possible to edit commits further back in the history, but
+this is an advanced topic to be left for
+<<cleaning-up-history,another chapter>>.
+
+Checking out an old version of a file
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In the process of undoing a previous bad change, you may find it
+useful to check out an older version of a particular file using
+gitlink:git-checkout[1].  We've used git checkout before to switch
+branches, but it has quite different behavior if it is given a path
+name: the command
+
+-------------------------------------------------
+$ git checkout HEAD^ path/to/file
+-------------------------------------------------
+
+replaces path/to/file by the contents it had in the commit HEAD^, and
+also updates the index to match.  It does not change branches.
+
+If you just want to look at an old version of the file, without
+modifying the working directory, you can do that with
+gitlink:git-show[1]:
+
+-------------------------------------------------
+$ git show HEAD^ path/to/file
+-------------------------------------------------
+
+which will display the given version of the file.
+
+Ensuring good performance
+-------------------------
+
+On large repositories, git depends on compression to keep the history
+information from taking up to much space on disk or in memory.
+
+This compression is not performed automatically.  Therefore you
+should occasionally run gitlink:git-gc[1]:
+
+-------------------------------------------------
+$ git gc
+-------------------------------------------------
+
+to recompress the archive.  This can be very time-consuming, so
+you may prefer to run git-gc when you are not doing other work.
+
+Ensuring reliability
+--------------------
+
+Checking the repository for corruption
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The gitlink:git-fsck[1] command runs a number of self-consistency checks
+on the repository, and reports on any problems.  This may take some
+time.  The most common warning by far is about "dangling" objects:
+
+-------------------------------------------------
+$ git fsck
+dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
+dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
+dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
+dangling blob 218761f9d90712d37a9c5e36f406f92202db07eb
+dangling commit bf093535a34a4d35731aa2bd90fe6b176302f14f
+dangling commit 8e4bec7f2ddaa268bef999853c25755452100f8e
+dangling tree d50bb86186bf27b681d25af89d3b5b68382e4085
+dangling tree b24c2473f1fd3d91352a624795be026d64c8841f
+...
+-------------------------------------------------
+
+Dangling objects are objects that are harmless, but also unnecessary;
+you can remove them at any time with gitlink:git-prune[1] or the --prune
+option to gitlink:git-gc[1]:
+
+-------------------------------------------------
+$ git gc --prune
+-------------------------------------------------
+
+This may be time-consuming.  Unlike most other git operations (including
+git-gc when run without any options), it is not safe to prune while
+other git operations are in progress in the same repository.
+
+For more about dangling objects, see <<dangling-objects>>.
+
+
+Recovering lost changes
+~~~~~~~~~~~~~~~~~~~~~~~
+
+Reflogs
+^^^^^^^
+
+Say you modify a branch with gitlink:git-reset[1] --hard, and then
+realize that the branch was the only reference you had to that point in
+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, 
+
+-------------------------------------------------
+$ git log master@{1}
+-------------------------------------------------
+
+This lists the commits reachable from the previous version of the head.
+This syntax can be used to with any git command that accepts a commit,
+not just with git log.  Some other examples:
+
+-------------------------------------------------
+$ git show master@{2}          # See where the branch pointed 2,
+$ git show master@{3}          # 3, ... changes ago.
+$ gitk master@{yesterday}      # See where it pointed yesterday,
+$ gitk master@{"1 week ago"}   # ... or last week
+-------------------------------------------------
+
+The reflogs are kept by default for 30 days, after which they may be
+pruned.  See gitlink:git-reflog[1] and gitlink:git-gc[1] to learn
+how to control this pruning, and see the "SPECIFYING REVISIONS"
+section of gitlink:git-rev-parse[1] for details.
+
+Note that the reflog history is very different from normal git history.
+While normal history is shared by every repository that works on the
+same project, the reflog history is not shared: it tells you only about
+how the branches in your local repository have changed over time.
+
+Examining dangling objects
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+In some situations the reflog may not be able to save you.  For
+example, suppose you delete a branch, then realize you need the history
+it pointed you.  The reflog is also deleted; however, if you have not
+yet pruned the repository, then you may still be able to find
+the lost commits; run git-fsck and watch for output that mentions
+"dangling commits":
+
+-------------------------------------------------
+$ git fsck
+dangling commit 7281251ddd2a61e38657c827739c57015671a6b3
+dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63
+dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5
+...
+-------------------------------------------------
+
+You can examine
+one of those dangling commits with, for example,
+
+------------------------------------------------
+$ gitk 7281251ddd --not --all
+------------------------------------------------
+
+which does what it sounds like: it says that you want to see the commit
+history that is described by the dangling commit(s), but not the
+history that is described by all your existing branches and tags.  Thus
+you get exactly the history reachable from that commit that is lost.
+(And notice that it might not be just one commit: we only report the
+"tip of the line" as being dangling, but there might be a whole deep
+and complex commit history that was gotten dropped.)
+
+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 
+------------------------------------------------
+
+
+Sharing development with others
+===============================
+
+[[getting-updates-with-git-pull]]
+Getting updates with git pull
+-----------------------------
+
+After you clone a repository and make a few changes of your own, you
+may wish to check the original repository for updates and merge them
+into your own work.
+
+We have already seen <<Updating-a-repository-with-git-fetch,how to
+keep remote tracking branches up to date>> with gitlink:git-fetch[1],
+and how to merge two branches.  So you can merge in changes from the
+original repository's master branch with:
+
+-------------------------------------------------
+$ git fetch
+$ git merge origin/master
+-------------------------------------------------
+
+However, the gitlink:git-pull[1] command provides a way to do this in
+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
+
+-------------------------------------------------
+$ 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.
+
+In addition to saving you keystrokes, "git pull" also helps you by
+producing a default commit message documenting the branch and
+repository that you pulled from.
+
+(But note that no such commit will be created in the case of a
+<<fast-forwards,fast forward>>; instead, your branch will just be
+updated to point to the latest commit from the upstream branch).
+
+The git-pull command can also be given "." as the "remote" repository,
+in which case it just merges in a branch from the current repository; so
+the commands
+
+-------------------------------------------------
+$ git pull . branch
+$ git merge branch
+-------------------------------------------------
+
+are roughly equivalent.  The former is actually very commonly used.
+
+Submitting patches to a project
+-------------------------------
+
+If you just have a few changes, the simplest way to submit them may
+just be to send them as patches in email:
+
+First, use gitlink:git-format-patch[1]; for example:
+
+-------------------------------------------------
+$ git format-patch origin
+-------------------------------------------------
+
+will produce a numbered series of files in the current directory, one
+for each patch in the current branch but not in origin/HEAD.
+
+You can then import these into your mail client and send them by
+hand.  However, if you have a lot to send at once, you may prefer to
+use the gitlink:git-send-email[1] script to automate the process.
+Consult the mailing list for your project first to determine how they
+prefer such patches be handled.
+
+Importing patches to a project
+------------------------------
+
+Git also provides a tool called gitlink:git-am[1] (am stands for
+"apply mailbox"), for importing such an emailed series of patches.
+Just save all of the patch-containing messages, in order, into a
+single mailbox file, say "patches.mbox", then run
+
+-------------------------------------------------
+$ git am -3 patches.mbox
+-------------------------------------------------
+
+Git will apply each patch in order; if any conflicts are found, it
+will stop, and you can fix the conflicts as described in
+"<<resolving-a-merge,Resolving a merge>>".  (The "-3" option tells
+git to perform a merge; if you would prefer it just to abort and
+leave your tree and index untouched, you may omit that option.)
+
+Once the index is updated with the results of the conflict
+resolution, instead of creating a new commit, just run
+
+-------------------------------------------------
+$ git am --resolved
+-------------------------------------------------
+
+and git will create the commit for you and continue applying the
+remaining patches from the mailbox.
+
+The final result will be a series of commits, one for each patch in
+the original mailbox, with authorship and commit log message each
+taken from the message containing each patch.
+
+[[setting-up-a-public-repository]]
+Setting up a public repository
+------------------------------
+
+Another way to submit changes to a project is to simply tell the
+maintainer of that project to pull from your repository, exactly as
+you did in the section "<<getting-updates-with-git-pull, Getting
+updates with git pull>>".
+
+If you and maintainer both have accounts on the same machine, then
+then you can just pull changes from each other's repositories
+directly; note that all of the command (gitlink:git-clone[1],
+git-fetch[1], git-pull[1], etc.) which accept a URL as an argument
+will also accept a local file patch; so, for example, you can
+use
+
+-------------------------------------------------
+$ git clone /path/to/repository
+$ git pull /path/to/other/repository
+-------------------------------------------------
+
+If this sort of setup is inconvenient or impossible, another (more
+common) option is to set up a public repository on a public server.
+This also allows you to cleanly separate private work in progress
+from publicly visible work.
+
+You will continue to do your day-to-day work in your personal
+repository, but periodically "push" changes from your personal
+repository into your public repository, allowing other developers to
+pull from that repository.  So the flow of changes, in a situation
+where there is one other developer with a public repository, looks
+like this:
+
+                        you push
+  your personal repo ------------------> your public repo
+       ^                                     |
+       |                                     |
+       | you pull                            | they pull
+       |                                     |
+       |                                     |
+        |               they push             V
+  their public repo <------------------- their repo
+
+Now, assume your personal repository is in the directory ~/proj.  We
+first create a new clone of the repository:
+
+-------------------------------------------------
+$ git clone --bare proj-clone.git
+-------------------------------------------------
+
+The resulting directory proj-clone.git will contains a "bare" git
+repository--it is just the contents of the ".git" directory, without
+a checked-out copy of a working directory.
+
+Next, copy proj-clone.git to the server where you plan to host the
+public repository.  You can use scp, rsync, or whatever is most
+convenient.
+
+If somebody else maintains the public server, they may already have
+set up a git service for you, and you may skip to the section
+"<<pushing-changes-to-a-public-repository,Pushing changes to a public
+repository>>", below.
+
+Otherwise, the following sections explain how to export your newly
+created public repository:
+
+[[exporting-via-http]]
+Exporting a git repository via http
+-----------------------------------
+
+The git protocol gives better performance and reliability, but on a
+host with a web server set up, http exports may be simpler to set up.
+
+All you need to do is place the newly created bare git repository in
+a directory that is exported by the web server, and make some
+adjustments to give web clients some extra information they need:
+
+-------------------------------------------------
+$ mv proj.git /home/you/public_html/proj.git
+$ cd proj.git
+$ git update-server-info
+$ chmod a+x hooks/post-update
+-------------------------------------------------
+
+(For an explanation of the last two lines, see
+gitlink:git-update-server-info[1], and the documentation
+link:hooks.txt[Hooks used by git].)
+
+Advertise the url of proj.git.  Anybody else should then be able to
+clone or pull from that url, for example with a commandline like:
+
+-------------------------------------------------
+$ git clone http://yourserver.com/~you/proj.git
+-------------------------------------------------
+
+(See also
+link:howto/setup-git-server-over-http.txt[setup-git-server-over-http]
+for a slightly more sophisticated setup using WebDAV which also
+allows pushing over http.)
+
+[[exporting-via-git]]
+Exporting a git repository via the git protocol
+-----------------------------------------------
+
+This is the preferred method.
+
+For now, we refer you to the gitlink:git-daemon[1] man page for
+instructions.  (See especially the examples section.)
+
+[[pushing-changes-to-a-public-repository]]
+Pushing changes to a public repository
+--------------------------------------
+
+Note that the two techniques outline above (exporting via
+<<exporting-via-http,http>> or <<exporting-via-git,git>>) allow other
+maintainers to fetch your latest changes, but they do not allow write
+access, which you will need to update the public repository with the
+latest changes created in your private repository.
+
+The simplest way to do this is using gitlink:git-push[1] and ssh; to
+update the remote branch named "master" with the latest state of your
+branch named "master", run
+
+-------------------------------------------------
+$ git push ssh://yourserver.com/~you/proj.git master:master
+-------------------------------------------------
+
+or just
+
+-------------------------------------------------
+$ git push ssh://yourserver.com/~you/proj.git master
+-------------------------------------------------
+
+As with git-fetch, git-push will complain if this does not result in
+a <<fast-forwards,fast forward>>.  Normally this is a sign of
+something wrong.  However, if you are sure you know what you're
+doing, you may force git-push to perform the update anyway by
+proceeding the branch name by a plus sign:
+
+-------------------------------------------------
+$ git push ssh://yourserver.com/~you/proj.git +master
+-------------------------------------------------
+
+As with git-fetch, you may also set up configuration options to
+save typing; so, for example, after
+
+-------------------------------------------------
+$ cat >.git/config <<EOF
+[remote "public-repo"]
+       url = ssh://yourserver.com/~you/proj.git
+EOF
+-------------------------------------------------
+
+you should be able to perform the above push with just
+
+-------------------------------------------------
+$ git push public-repo master
+-------------------------------------------------
+
+See the explanations of the remote.<name>.url, branch.<name>.remote,
+and remote.<name>.push options in gitlink:git-config[1] for
+details.
+
+Setting up a shared repository
+------------------------------
+
+Another way to collaborate is by using a model similar to that
+commonly used in CVS, where several developers with special rights
+all push to and pull from a single shared repository.  See
+link:cvs-migration.txt[git for CVS users] for instructions on how to
+set this up.
+
+Allow web browsing of a repository
+----------------------------------
+
+The gitweb cgi script provides users an easy way to browse your
+project's files and history without having to install git; see the file
+gitweb/README in the git source tree for instructions on setting it up.
+
+Examples
+--------
+
+TODO: topic branches, typical roles as in everyday.txt, ?
+
+
+[[cleaning-up-history]]
+Rewriting history and maintaining patch series
+==============================================
+
+Normally commits are only added to a project, never taken away or
+replaced.  Git is designed with this assumption, and violating it will
+cause git's merge machinery (for example) to do the wrong thing.
+
+However, there is a situation in which it can be useful to violate this
+assumption.
+
+Creating the perfect patch series
+---------------------------------
+
+Suppose you are a contributor to a large project, and you want to add a
+complicated feature, and to present it to the other developers in a way
+that makes it easy for them to read your changes, verify that they are
+correct, and understand why you made each change.
+
+If you present all of your changes as a single patch (or commit), they
+may find it is too much to digest all at once.
+
+If you present them with the entire history of your work, complete with
+mistakes, corrections, and dead ends, they may be overwhelmed.
+
+So the ideal is usually to produce a series of patches such that:
+
+       1. Each patch can be applied in order.
+
+       2. Each patch includes a single logical change, together with a
+          message explaining the change.
+
+       3. No patch introduces a regression: after applying any initial
+          part of the series, the resulting project still compiles and
+          works, and has no bugs that it didn't have before.
+
+       4. The complete series produces the same end result as your own
+          (probably much messier!) development process did.
+
+We will introduce some tools that can help you do this, explain how to
+use them, and then explain some of the problems that can arise because
+you are rewriting history.
+
+Keeping a patch series up to date using git-rebase
+--------------------------------------------------
+
+Suppose you have a series of commits in a branch "mywork", which
+originally branched off from "origin".
+
+Suppose you create a branch "mywork" on a remote-tracking branch
+"origin", and created some commits on top of it:
+
+-------------------------------------------------
+$ git checkout -b mywork origin
+$ vi file.txt
+$ git commit
+$ vi otherfile.txt
+$ git commit
+...
+-------------------------------------------------
+
+You have performed no merges into mywork, so it is just a simple linear
+sequence of patches on top of "origin":
+
+
+ o--o--o <-- origin
+        \
+         o--o--o <-- mywork
+
+Some more interesting work has been done in the upstream project, and
+"origin" has advanced:
+
+ o--o--O--o--o--o <-- origin
+        \
+         a--b--c <-- mywork
+
+At this point, you could use "pull" to merge your changes back in;
+the result would create a new merge commit, like this:
+
+
+ o--o--O--o--o--o <-- origin
+        \        \
+         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]:
+
+-------------------------------------------------
+$ git checkout mywork
+$ git rebase origin
+-------------------------------------------------
+
+This will remove each of your commits from mywork, temporarily saving
+them as patches (in a directory named ".dotest"), update mywork to
+point at the latest version of origin, then apply each of the saved
+patches to the new mywork.  The result will look like:
+
+
+ o--o--O--o--o--o <-- origin
+                \
+                 a'--b'--c' <-- mywork
+
+In the process, it may discover conflicts.  In that case it will stop
+and allow you to fix the conflicts; after fixing conflicts, use "git
+add" to update the index with those contents, and then, instead of
+running git-commit, just run
+
+-------------------------------------------------
+$ git rebase --continue
+-------------------------------------------------
+
+and git will continue applying the rest of the patches.
+
+At any point you may use the --abort option to abort this process and
+return mywork to the state it had before you started the rebase:
+
+-------------------------------------------------
+$ git rebase --abort
+-------------------------------------------------
+
+Reordering or selecting from a patch series
+-------------------------------------------
+
+Given one existing commit, the gitlink:git-cherry-pick[1] command
+allows you to apply the change introduced by that commit and create a
+new commit that records it.  So, for example, if "mywork" points to a
+series of patches on top of "origin", you might do something like:
+
+-------------------------------------------------
+$ git checkout -b mywork-new origin
+$ 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.
+
+Another technique is to use git-format-patch to create a series of
+patches, then reset the state to before the patches:
+
+-------------------------------------------------
+$ git format-patch origin
+$ git reset --hard origin
+-------------------------------------------------
+
+Then modify, reorder, or eliminate patches as preferred before applying
+them again with gitlink:git-am[1].
+
+Other tools
+-----------
+
+There are numerous other tools, such as stgit, which exist for the
+purpose of maintaining a patch series.  These are out of the scope of
+this manual.
+
+Problems with rewriting history
+-------------------------------
+
+The primary problem with rewriting the history of a branch has to do
+with merging.  Suppose somebody fetches your branch and merges it into
+their branch, with a result something like this:
+
+ o--o--O--o--o--o <-- origin
+        \        \
+         t--t--t--m <-- their branch:
+
+Then suppose you modify the last three commits:
+
+        o--o--o <-- new head of origin
+       /
+ o--o--O--o--o--o <-- old head of origin
+
+If we examined all this history together in one repository, it will
+look like:
+
+        o--o--o <-- new head of origin
+       /
+ o--o--O--o--o--o <-- old head of origin
+        \        \
+         t--t--t--m <-- their branch:
+
+Git has no way of knowing that the new head is an updated version of
+the old head; it treats this situation exactly the same as it would if
+two developers had independently done the work on the old and new heads
+in parallel.  At this point, if someone attempts to merge the new head
+in to their branch, git will attempt to merge together the two (old and
+new) lines of development, instead of trying to replace the old by the
+new.  The results are likely to be unexpected.
+
+You may still choose to publish branches whose history is rewritten,
+and it may be useful for others to be able to fetch those branches in
+order to examine or test them, but they should not attempt to pull such
+branches into their own work.
+
+For true distributed development that supports proper merging,
+published branches should never be rewritten.
+
+Advanced branch management
+==========================
+
+Fetching individual branches
+----------------------------
+
+Instead of using gitlink:git-remote[1], you can also choose just
+to update one branch at a time, and to store it locally under an
+arbitrary name:
+
+-------------------------------------------------
+$ git fetch origin todo:my-todo-work
+-------------------------------------------------
+
+The first argument, "origin", just tells git to fetch from the
+repository you originally cloned from.  The second argument tells git
+to fetch the branch named "todo" from the remote repository, and to
+store it locally under the name refs/heads/my-todo-work.
+
+You can also fetch branches from other repositories; so
+
+-------------------------------------------------
+$ git fetch git://example.com/proj.git master:example-master
+-------------------------------------------------
+
+will create a new branch named "example-master" and store in it the
+branch named "master" from the repository at the given URL.  If you
+already have a branch named example-master, it will attempt to
+"fast-forward" to the commit given by example.com's master branch.  So
+next we explain what a fast-forward is:
+
+[[fast-forwards]]
+Understanding git history: fast-forwards
+----------------------------------------
+
+In the previous example, when updating an existing branch, "git
+fetch" checks to make sure that the most recent commit on the remote
+branch is a descendant of the most recent commit on your copy of the
+branch before updating your copy of the branch to point at the new
+commit.  Git calls this process a "fast forward".
+
+A fast forward looks something like this:
+
+ o--o--o--o <-- old head of the branch
+           \
+            o--o--o <-- new head of the branch
+
+
+In some cases it is possible that the new head will *not* actually be
+a descendant of the old head.  For example, the developer may have
+realized she made a serious mistake, and decided to backtrack,
+resulting in a situation like:
+
+ o--o--o--o--a--b <-- old head of the branch
+           \
+            o--o--o <-- new head of the branch
+
+
+
+In this case, "git fetch" will fail, and print out a warning.
+
+In that case, you can still force git to update to the new head, as
+described in the following section.  However, note that in the
+situation above this may mean losing the commits labeled "a" and "b",
+unless you've already created a reference of your own pointing to
+them.
+
+Forcing git fetch to do non-fast-forward updates
+------------------------------------------------
+
+If git fetch fails because the new head of a branch is not a
+descendant of the old head, you may force the update with:
+
+-------------------------------------------------
+$ git fetch git://example.com/proj.git +master:refs/remotes/example/master
+-------------------------------------------------
+
+Note the addition of the "+" sign.  Be aware that commits which the
+old version of example/master pointed at may be lost, as we saw in
+the previous section.
+
+Configuring remote branches
+---------------------------
+
+We saw above that "origin" is just a shortcut to refer to the
+repository which you originally cloned from.  This information is
+stored in git configuration variables, which you can see using
+gitlink:git-config[1]:
+
+-------------------------------------------------
+$ git config -l
+core.repositoryformatversion=0
+core.filemode=true
+core.logallrefupdates=true
+remote.origin.url=git://git.kernel.org/pub/scm/git/git.git
+remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
+branch.master.remote=origin
+branch.master.merge=refs/heads/master
+-------------------------------------------------
+
+If there are other repositories that you also use frequently, you can
+create similar configuration options to save typing; for example,
+after
+
+-------------------------------------------------
+$ git config remote.example.url git://example.com/proj.git
+-------------------------------------------------
+
+then the following two commands will do the same thing:
+
+-------------------------------------------------
+$ git fetch git://example.com/proj.git master:refs/remotes/example/master
+$ git fetch example master:refs/remotes/example/master
+-------------------------------------------------
+
+Even better, if you add one more option:
+
+-------------------------------------------------
+$ git config remote.example.fetch master:refs/remotes/example/master
+-------------------------------------------------
+
+then the following commands will all do the same thing:
+
+-------------------------------------------------
+$ git fetch git://example.com/proj.git master:ref/remotes/example/master
+$ git fetch example master:ref/remotes/example/master
+$ git fetch example example/master
+$ git fetch example
+-------------------------------------------------
+
+You can also add a "+" to force the update each time:
+
+-------------------------------------------------
+$ git config remote.example.fetch +master:ref/remotes/example/master
+-------------------------------------------------
+
+Don't do this unless you're sure you won't mind "git fetch" possibly
+throwing away commits on mybranch.
+
+Also note that all of the above configuration can be performed by
+directly editing the file .git/config instead of using
+gitlink:git-config[1].
+
+See gitlink:git-config[1] for more details on the configuration
+options mentioned above.
+
+
+Git internals
+=============
+
+There are two object abstractions: the "object database", and the
+"current directory cache" aka "index".
+
+The Object Database
+-------------------
+
+The object database is literally just a content-addressable collection
+of objects.  All objects are named by their content, which is
+approximated by the SHA1 hash of the object itself.  Objects may refer
+to other objects (by referencing their SHA1 hash), and so you can
+build up a hierarchy of objects.
+
+All objects have a statically determined "type" aka "tag", which is
+determined at object creation time, and which identifies the format of
+the object (i.e. how it is used, and how it can refer to other
+objects).  There are currently four different object types: "blob",
+"tree", "commit" and "tag".
+
+A "blob" object cannot refer to any other object, and is, like the type
+implies, a pure storage object containing some user data.  It is used to
+actually store the file data, i.e. a blob object is associated with some
+particular version of some file. 
+
+A "tree" object is an object that ties one or more "blob" objects into a
+directory structure. In addition, a tree object can refer to other tree
+objects, thus creating a directory hierarchy. 
+
+A "commit" object ties such directory hierarchies together into
+a DAG of revisions - each "commit" is associated with exactly one tree
+(the directory hierarchy at the time of the commit). In addition, a
+"commit" refers to one or more "parent" commit objects that describe the
+history of how we arrived at that directory hierarchy.
+
+As a special case, a commit object with no parents is called the "root"
+object, and is the point of an initial project commit.  Each project
+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. 
+
+A "tag" object symbolically identifies and can be used to sign other
+objects. It contains the identifier and type of another object, a
+symbolic name (of course!) and, optionally, a signature.
+
+Regardless of object type, all objects share the following
+characteristics: they are all deflated with zlib, and have a header
+that not only specifies their type, but also provides size information
+about the data in the object.  It's worth noting that the SHA1 hash
+that is used to name the object is the hash of the original data
+plus this header, so `sha1sum` 'file' does not match the object name
+for 'file'.
+(Historical note: in the dawn of the age of git the hash
+was the sha1 of the 'compressed' object.)
+
+As a result, the general consistency of an object can always be tested
+independently of the contents or the type of the object: all objects can
+be validated by verifying that (a) their hashes match the content of the
+file and (b) the object successfully inflates to a stream of bytes that
+forms a sequence of <ascii type without space> + <space> + <ascii decimal
+size> + <byte\0> + <binary object data>. 
+
+The structured objects can further have their structure and
+connectivity to other objects verified. This is generally done with
+the `git-fsck` program, which generates a full dependency graph
+of all objects, and verifies their internal consistency (in addition
+to just verifying their superficial consistency through the hash).
+
+The object types in some more detail:
+
+Blob Object
+-----------
+
+A "blob" object is nothing but a binary blob of data, and doesn't
+refer to anything else.  There is no signature or any other
+verification of the data, so while the object is consistent (it 'is'
+indexed by its sha1 hash, so the data itself is certainly correct), it
+has absolutely no other attributes.  No name associations, no
+permissions.  It is purely a blob of data (i.e. normally "file
+contents").
+
+In particular, since the blob is entirely defined by its data, if two
+files in a directory tree (or in multiple different versions of the
+repository) have the same contents, they will share the same blob
+object. The object is totally independent of its location in the
+directory tree, and renaming a file does not change the object that
+file is associated with in any way.
+
+A blob is typically created when gitlink:git-update-index[1]
+is run, and its data can be accessed by gitlink:git-cat-file[1].
+
+Tree Object
+-----------
+
+The next hierarchical object type is the "tree" object.  A tree object
+is a list of mode/name/blob data, sorted by name.  Alternatively, the
+mode data may specify a directory mode, in which case instead of
+naming a blob, that name is associated with another TREE object.
+
+Like the "blob" object, a tree object is uniquely determined by the
+set contents, and so two separate but identical trees will always
+share the exact same object. This is true at all levels, i.e. it's
+true for a "leaf" tree (which does not refer to any other trees, only
+blobs) as well as for a whole subdirectory.
+
+For that reason a "tree" object is just a pure data abstraction: it
+has no history, no signatures, no verification of validity, except
+that since the contents are again protected by the hash itself, we can
+trust that the tree is immutable and its contents never change.
+
+So you can trust the contents of a tree to be valid, the same way you
+can trust the contents of a blob, but you don't know where those
+contents 'came' from.
+
+Side note on trees: since a "tree" object is a sorted list of
+"filename+content", you can create a diff between two trees without
+actually having to unpack two trees.  Just ignore all common parts,
+and your diff will look right.  In other words, you can effectively
+(and efficiently) tell the difference between any two random trees by
+O(n) where "n" is the size of the difference, rather than the size of
+the tree.
+
+Side note 2 on trees: since the name of a "blob" depends entirely and
+exclusively on its contents (i.e. there are no names or permissions
+involved), you can see trivial renames or permission changes by
+noticing that the blob stayed the same.  However, renames with data
+changes need a smarter "diff" implementation.
+
+A tree is created with gitlink:git-write-tree[1] and
+its data can be accessed by gitlink:git-ls-tree[1].
+Two trees can be compared with gitlink:git-diff-tree[1].
+
+Commit Object
+-------------
+
+The "commit" object is an object that introduces the notion of
+history into the picture.  In contrast to the other objects, it
+doesn't just describe the physical state of a tree, it describes how
+we got there, and why.
+
+A "commit" is defined by the tree-object that it results in, the
+parent commits (zero, one or more) that led up to that point, and a
+comment on what happened.  Again, a commit is not trusted per se:
+the contents are well-defined and "safe" due to the cryptographically
+strong signatures at all levels, but there is no reason to believe
+that the tree is "good" or that the merge information makes sense.
+The parents do not have to actually have any relationship with the
+result, for example.
+
+Note on commits: unlike real SCM's, commits do not contain
+rename information or file mode change information.  All of that is
+implicit in the trees involved (the result tree, and the result trees
+of the parents), and describing that makes no sense in this idiotic
+file manager.
+
+A commit is created with gitlink:git-commit-tree[1] and
+its data can be accessed by gitlink:git-cat-file[1].
+
+Trust
+-----
+
+An aside on the notion of "trust". Trust is really outside the scope
+of "git", but it's worth noting a few things.  First off, since
+everything is hashed with SHA1, you 'can' trust that an object is
+intact and has not been messed with by external sources.  So the name
+of an object uniquely identifies a known state - just not a state that
+you may want to trust.
+
+Furthermore, since the SHA1 signature of a commit refers to the
+SHA1 signatures of the tree it is associated with and the signatures
+of the parent, a single named commit specifies uniquely a whole set
+of history, with full contents.  You can't later fake any step of the
+way once you have the name of a commit.
+
+So to introduce some real trust in the system, the only thing you need
+to do is to digitally sign just 'one' special note, which includes the
+name of a top-level commit.  Your digital signature shows others
+that you trust that commit, and the immutability of the history of
+commits tells others that they can trust the whole history.
+
+In other words, you can easily validate a whole archive by just
+sending out a single email that tells the people the name (SHA1 hash)
+of the top commit, and digitally sign that email using something
+like GPG/PGP.
+
+To assist in this, git also provides the tag object...
+
+Tag Object
+----------
+
+Git provides the "tag" object to simplify creating, managing and
+exchanging symbolic and signed tokens.  The "tag" object at its
+simplest simply symbolically identifies another object by containing
+the sha1, type and symbolic name.
+
+However it can optionally contain additional signature information
+(which git doesn't care about as long as there's less than 8k of
+it). This can then be verified externally to git.
+
+Note that despite the tag features, "git" itself only handles content
+integrity; the trust framework (and signature provision and
+verification) has to come from outside.
+
+A tag is created with gitlink:git-mktag[1],
+its data can be accessed by gitlink:git-cat-file[1],
+and the signature can be verified by
+gitlink:git-verify-tag[1].
+
+
+The "index" aka "Current Directory Cache"
+-----------------------------------------
+
+The index is a simple binary file, which contains an efficient
+representation of a virtual directory content at some random time.  It
+does so by a simple array that associates a set of names, dates,
+permissions and content (aka "blob") objects together.  The cache is
+always kept ordered by name, and names are unique (with a few very
+specific rules) at any point in time, but the cache has no long-term
+meaning, and can be partially updated at any time.
+
+In particular, the index certainly does not need to be consistent with
+the current directory contents (in fact, most operations will depend on
+different ways to make the index 'not' be consistent with the directory
+hierarchy), but it has three very important attributes:
+
+'(a) it can re-generate the full state it caches (not just the
+directory structure: it contains pointers to the "blob" objects so
+that it can regenerate the data too)'
+
+As a special case, there is a clear and unambiguous one-way mapping
+from a current directory cache to a "tree object", which can be
+efficiently created from just the current directory cache without
+actually looking at any other data.  So a directory cache at any one
+time uniquely specifies one and only one "tree" object (but has
+additional data to make it easy to match up that tree object with what
+has happened in the directory)
+
+'(b) it has efficient methods for finding inconsistencies between that
+cached state ("tree object waiting to be instantiated") and the
+current state.'
+
+'(c) it can additionally efficiently represent information about merge
+conflicts between different tree objects, allowing each pathname to be
+associated with sufficient information about the trees involved that
+you can create a three-way merge between them.'
+
+Those are the three ONLY things that the directory cache does.  It's a
+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. 
+
+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.
+
+
+
+The Workflow
+------------
+
+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: 
+
+working directory -> index
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update the index with information from the working directory with
+the gitlink:git-update-index[1] command.  You
+generally update the index information by just specifying the filename
+you want to update, like so:
+
+-------------------------------------------------
+$ git-update-index filename
+-------------------------------------------------
+
+but to avoid common mistakes with filename globbing etc, the command
+will not normally add totally new entries or remove old entries,
+i.e. it will normally just update existing cache entries.
+
+To tell git that yes, you really do realize that certain files no
+longer exist, or that new files should be added, you
+should use the `--remove` and `--add` flags respectively.
+
+NOTE! A `--remove` flag does 'not' mean that subsequent filenames will
+necessarily be removed: if the files still exist in your directory
+structure, the index will be updated with their new status, not
+removed. The only thing `--remove` means is that update-cache will be
+considering a removed file to be a valid thing, and if the file really
+does not exist any more, it will update the index accordingly.
+
+As a special case, you can also do `git-update-index --refresh`, which
+will refresh the "stat" information of each index to match the current
+stat information. It will 'not' update the object status itself, and
+it will only update the fields that are used to quickly test whether
+an object still matches its old backing store object.
+
+index -> object database
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You write your current index file to a "tree" object with the program
+
+-------------------------------------------------
+$ git-write-tree
+-------------------------------------------------
+
+that doesn't come with any options - it will just write out the
+current index into the set of tree objects that describe that state,
+and it will return the name of the resulting top-level tree. You can
+use that tree to re-generate the index at any time by going in the
+other direction:
+
+object database -> index
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+You read a "tree" file from the object database, and use that to
+populate (and overwrite - don't do this if your index contains any
+unsaved state that you might want to restore later!) your current
+index.  Normal operation is just
+
+-------------------------------------------------
+$ git-read-tree <sha1 of tree>
+-------------------------------------------------
+
+and your index file will now be equivalent to the tree that you saved
+earlier. However, that is only your 'index' file: your working
+directory contents have not been modified.
+
+index -> working directory
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+You update your working directory from the index by "checking out"
+files. This is not a very common operation, since normally you'd just
+keep your files updated, and rather than write to your working
+directory, you'd tell the index files about the changes in your
+working directory (i.e. `git-update-index`).
+
+However, if you decide to jump to a new version, or check out somebody
+else's version, or just restore a previous tree, you'd populate your
+index file with read-tree, and then you need to check out the result
+with
+
+-------------------------------------------------
+$ git-checkout-index filename
+-------------------------------------------------
+
+or, if you want to check out all of the index, use `-a`.
+
+NOTE! git-checkout-index normally refuses to overwrite old files, so
+if you have an old version of the tree already checked out, you will
+need to use the "-f" flag ('before' the "-a" flag or the filename) to
+'force' the checkout.
+
+
+Finally, there are a few odds and ends which are not purely moving
+from one representation to the other:
+
+Tying it all together
+~~~~~~~~~~~~~~~~~~~~~
+
+To commit a tree you have instantiated with "git-write-tree", you'd
+create a "commit" object that refers to that tree and the history
+behind it - most notably the "parent" commits that preceded it in
+history.
+
+Normally a "commit" has one parent: the previous state of the tree
+before a certain change was made. However, sometimes it can have two
+or more parent commits, in which case we call it a "merge", due to the
+fact that such a commit brings together ("merges") two or more
+previous states represented by other commits.
+
+In other words, while a "tree" represents a particular directory state
+of a working directory, a "commit" represents that state in "time",
+and explains how we got there.
+
+You create a commit object by giving it the tree that describes the
+state at the time of the commit, and a list of parents:
+
+-------------------------------------------------
+$ git-commit-tree <tree> -p <parent> [-p <parent2> ..]
+-------------------------------------------------
+
+and then giving the reason for the commit on stdin (either through
+redirection from a pipe or file, or by just typing it at the tty).
+
+git-commit-tree will return the name of the object that represents
+that commit, and you should save it away for later use. Normally,
+you'd commit a new `HEAD` state, and while git doesn't care where you
+save the note about that state, in practice we tend to just write the
+result to the file pointed at by `.git/HEAD`, so that we can always see
+what the last committed state was.
+
+Here is an ASCII art by Jon Loeliger that illustrates how
+various pieces fit together.
+
+------------
+
+                     commit-tree
+                      commit obj
+                       +----+
+                       |    |
+                       |    |
+                       V    V
+                    +-----------+
+                    | Object DB |
+                    |  Backing  |
+                    |   Store   |
+                    +-----------+
+                       ^
+           write-tree  |     |
+             tree obj  |     |
+                       |     |  read-tree
+                       |     |  tree obj
+                             V
+                    +-----------+
+                    |   Index   |
+                    |  "cache"  |
+                    +-----------+
+         update-index  ^
+             blob obj  |     |
+                       |     |
+    checkout-index -u  |     |  checkout-index
+             stat      |     |  blob obj
+                             V
+                    +-----------+
+                    |  Working  |
+                    | Directory |
+                    +-----------+
+
+------------
+
+
+Examining the data
+------------------
+
+You can examine the data represented in the object database and the
+index with various helper tools. For every object, you can use
+gitlink:git-cat-file[1] to examine details about the
+object:
+
+-------------------------------------------------
+$ git-cat-file -t <objectname>
+-------------------------------------------------
+
+shows the type of the object, and once you have the type (which is
+usually implicit in where you find the object), you can use
+
+-------------------------------------------------
+$ git-cat-file blob|tree|commit|tag <objectname>
+-------------------------------------------------
+
+to show its contents. NOTE! Trees have binary content, and as a result
+there is a special helper for showing that content, called
+`git-ls-tree`, which turns the binary content into a more easily
+readable form.
+
+It's especially instructive to look at "commit" objects, since those
+tend to be small and fairly self-explanatory. In particular, if you
+follow the convention of having the top commit name in `.git/HEAD`,
+you can do
+
+-------------------------------------------------
+$ git-cat-file commit HEAD
+-------------------------------------------------
+
+to see what the top commit was.
+
+Merging multiple trees
+----------------------
+
+Git helps you do a three-way merge, which you can expand to n-way by
+repeating the merge procedure arbitrary times until you finally
+"commit" the state.  The normal situation is that you'd only do one
+three-way merge (two parents), and commit it, but if you like to, you
+can do multiple parents in one go.
+
+To do a three-way merge, you need the two sets of "commit" objects
+that you want to merge, use those to find the closest common parent (a
+third "commit" object), and then use those commit objects to find the
+state of the directory ("tree" object) at these points.
+
+To get the "base" for the merge, you first look up the common parent
+of two commits with
+
+-------------------------------------------------
+$ git-merge-base <commit1> <commit2>
+-------------------------------------------------
+
+which will return you the commit they are both based on.  You should
+now look up the "tree" objects of those commits, which you can easily
+do with (for example)
+
+-------------------------------------------------
+$ git-cat-file commit <commitname> | head -1
+-------------------------------------------------
+
+since the tree object information is always the first line in a commit
+object.
+
+Once you know the three trees you are going to merge (the one "original"
+tree, aka the common case, and the two "result" trees, aka the branches
+you want to merge), you do a "merge" read into the index. This will
+complain if it has to throw away your old index contents, so you should
+make sure that you've committed those - in fact you would normally
+always do a merge against your last commit (which should thus match what
+you have in your current index anyway).
+
+To do the merge, do
+
+-------------------------------------------------
+$ git-read-tree -m -u <origtree> <yourtree> <targettree>
+-------------------------------------------------
+
+which will do all trivial merge operations for you directly in the
+index file, and you can just write the result out with
+`git-write-tree`.
+
+
+Merging multiple trees, continued
+---------------------------------
+
+Sadly, many merges aren't trivial. If there are files that have
+been added.moved 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
+other tools before you can write out the result.
+
+You can examine such index state with `git-ls-files --unmerged`
+command.  An example:
+
+------------------------------------------------
+$ git-read-tree -m $orig HEAD $target
+$ git-ls-files --unmerged
+100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello.c
+100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello.c
+100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello.c
+------------------------------------------------
+
+Each line of the `git-ls-files --unmerged` output begins with
+the blob mode bits, blob SHA1, 'stage number', and the
+filename.  The 'stage number' is git's way to say which tree it
+came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD`
+tree, and stage3 `$target` tree.
+
+Earlier we said that trivial merges are done inside
+`git-read-tree -m`.  For example, if the file did not change
+from `$orig` to `HEAD` nor `$target`, or if the file changed
+from `$orig` to `HEAD` and `$orig` to `$target` the same way,
+obviously the final outcome is what is in `HEAD`.  What the
+above example shows is that file `hello.c` was changed from
+`$orig` to `HEAD` and `$orig` to `$target` in a different way.
+You could resolve this by running your favorite 3-way merge
+program, e.g.  `diff3` or `merge`, on the blob objects from
+these three stages yourself, like this:
+
+------------------------------------------------
+$ git-cat-file blob 263414f... >hello.c~1
+$ git-cat-file blob 06fa6a2... >hello.c~2
+$ git-cat-file blob cc44c73... >hello.c~3
+$ merge hello.c~2 hello.c~1 hello.c~3
+------------------------------------------------
+
+This would leave the merge result in `hello.c~2` file, along
+with conflict markers if there are conflicts.  After verifying
+the merge result makes sense, you can tell git what the final
+merge result for this file is by:
+
+-------------------------------------------------
+$ mv -f hello.c~2 hello.c
+$ git-update-index hello.c
+-------------------------------------------------
+
+When a path is in unmerged state, running `git-update-index` for
+that path tells git to mark the path resolved.
+
+The above is the description of a git merge at the lowest level,
+to help you understand what conceptually happens under the hood.
+In practice, nobody, not even git itself, uses three `git-cat-file`
+for this.  There is `git-merge-index` program that extracts the
+stages to temporary files and calls a "merge" script on it:
+
+-------------------------------------------------
+$ git-merge-index git-merge-one-file hello.c
+-------------------------------------------------
+
+and that is what higher level `git merge -s resolve` is implemented with.
+
+How git stores objects efficiently: pack files
+----------------------------------------------
+
+We've seen how git stores each object in a file named after the
+object's SHA1 hash.
+
+Unfortunately this system becomes inefficient once a project has a
+lot of objects.  Try this on an old project:
+
+------------------------------------------------
+$ git count-objects
+6930 objects, 47620 kilobytes
+------------------------------------------------
+
+The first number is the number of objects which are kept in
+individual files.  The second is the amount of space taken up by
+those "loose" objects.
+
+You can save space and make git faster by moving these loose objects in
+to a "pack file", which stores a group of objects in an efficient
+compressed format; the details of how pack files are formatted can be
+found in link:technical/pack-format.txt[technical/pack-format.txt].
+
+To put the loose objects into a pack, just run git repack:
+
+------------------------------------------------
+$ git repack
+Generating pack...
+Done counting 6020 objects.
+Deltifying 6020 objects.
+ 100% (6020/6020) done
+Writing 6020 objects.
+ 100% (6020/6020) done
+Total 6020, written 6020 (delta 4070), reused 0 (delta 0)
+Pack pack-3e54ad29d5b2e05838c75df582c65257b8d08e1c created.
+------------------------------------------------
+
+You can then run
+
+------------------------------------------------
+$ git prune
+------------------------------------------------
+
+to remove any of the "loose" objects that are now contained in the
+pack.  This will also remove any unreferenced objects (which may be
+created when, for example, you use "git reset" to remove a commit).
+You can verify that the loose objects are gone by looking at the
+.git/objects directory or by running
+
+------------------------------------------------
+$ git count-objects
+0 objects, 0 kilobytes
+------------------------------------------------
+
+Although the object files are gone, any commands that refer to those
+objects will work exactly as they did before.
+
+The gitlink:git-gc[1] command performs packing, pruning, and more for
+you, so is normally the only high-level command you need.
+
+[[dangling-objects]]
+Dangling objects
+----------------
+
+The gitlink:git-fsck[1] command will sometimes complain about dangling
+objects.  They are not a problem.
+
+The most common cause of dangling objects is that you've rebased a
+branch, or you have pulled from somebody else who rebased a branch--see
+<<cleaning-up-history>>.  In that case, the old head of the original
+branch still exists, as does obviously everything it pointed to. The
+branch pointer itself just doesn't, since you replaced it with another
+one.
+
+There are also other situations too that cause dangling objects. For
+example, a "dangling blob" may arise because you did a "git add" of a
+file, but then, before you actually committed it and made it part of the
+bigger picture, you changed something else in that file and committed
+that *updated* thing - the old state that you added originally ends up
+not being pointed to by any commit or tree, so it's now a dangling blob
+object.
+
+Similarly, when the "recursive" merge strategy runs, and finds that
+there are criss-cross merges and thus more than one merge base (which is
+fairly unusual, but it does happen), it will generate one temporary
+midway tree (or possibly even more, if you had lots of criss-crossing
+merges and more than two merge bases) as a temporary internal merge
+base, and again, those are real objects, but the end result will not end
+up pointing to them, so they end up "dangling" in your repository.
+
+Generally, dangling objects aren't anything to worry about. They can
+even be very useful: if you screw something up, the dangling objects can
+be how you recover your old tree (say, you did a rebase, and realized
+that you really didn't want to - you can look at what dangling objects
+you have, and decide to reset your head to some old dangling state).
+
+For commits, the most useful thing to do with dangling objects tends to
+be to do a simple
+
+------------------------------------------------
+$ gitk <dangling-commit-sha-goes-here> --not --all
+------------------------------------------------
+
+For blobs and trees, you can't do the same, but you can examine them.
+You can just do
+
+------------------------------------------------
+$ git show <dangling-blob/tree-sha-goes-here>
+------------------------------------------------
+
+to show what the contents of the blob were (or, for a tree, basically
+what the "ls" for that directory was), and that may give you some idea
+of what the operation was that left that dangling object.
+
+Usually, dangling blobs and trees aren't very interesting. They're
+almost always the result of either being a half-way mergebase (the blob
+will often even have the conflict markers from a merge in it, if you
+have had conflicting merges that you fixed up by hand), or simply
+because you interrupted a "git fetch" with ^C or something like that,
+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 
+state, you can just prune all unreachable objects:
+
+------------------------------------------------
+$ git prune
+------------------------------------------------
+
+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 
+repository is a *BAD* idea).
+
+Glossary of git terms
+=====================
+
+include::glossary.txt[]
+
+Notes and todo list for this manual
+===================================
+
+This is a work in progress.
+
+The basic requirements:
+       - It must be readable in order, from beginning to end, by
+         someone intelligent with a basic grasp of the unix
+         commandline, but without any special knowledge of git.  If
+         necessary, any other prerequisites should be specifically
+         mentioned as they arise.
+       - Whenever possible, section headings should clearly describe
+         the task they explain how to do, in language that requires
+         no more knowledge than necessary: for example, "importing
+         patches into a project" rather than "the git-am command"
+
+Think about how to create a clear chapter dependency graph that will
+allow people to get to important topics without necessarily reading
+everything in between.
+
+Say something about .gitignore.
+
+Scan Documentation/ for other stuff left out; in particular:
+       howto's
+       some of technical/?
+       hooks
+       list of commands in gitlink:git[1]
+
+Scan email archives for other stuff left out
+
+Scan man pages to see if any assume more background than this manual
+provides.
+
+Simplify beginning by suggesting disconnected head instead of
+temporary branch creation?
+
+Explain how to refer to file stages in the "how to resolve a merge"
+section: diff -1, -2, -3, --ours, --theirs :1:/path notation.  The
+"git ls-files --unmerged --stage" thing is sorta useful too,
+actually.  And note gitk --merge.
+
+Add more good examples.  Entire sections of just cookbook examples
+might be a good idea; maybe make an "advanced examples" section a
+standard end-of-chapter section?
+
+Include cross-references to the glossary, where appropriate.
+
+Document shallow clones?  See draft 1.5.0 release notes for some
+documentation.
+
+Add a section on working with other version control systems, including
+CVS, Subversion, and just imports of series of release tarballs.
+
+More details on gitweb?
+
+Write a chapter on using plumbing and writing scripts.
index eca1ff2175c9c62ce51fe6606d615f99925e7734..febacd2dc9a91cfd9135a64ee554d14282a105d2 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.4.4.GIT
+DEF_VER=v1.5.0.GIT
 
 LF='
 '
diff --git a/INSTALL b/INSTALL
index fce6bc39d562271417fe4caeaf2d5dd9691deaf8..361c65bacc50b4038cb9a32bab07c275a1ecdc76 100644 (file)
--- a/INSTALL
+++ b/INSTALL
@@ -72,25 +72,6 @@ Issues of note:
        - expat library; git-http-push uses it for remote lock
          management over DAV.  Similar to "curl" above, this is optional.
 
-       - "GNU diff" to generate patches.  Of course, you don't _have_ to
-         generate patches if you don't want to, but let's face it, you'll
-         be wanting to. Or why did you get git in the first place?
-
-         Non-GNU versions of the diff/patch programs don't generally support
-         the unified patch format (which is the one git uses), so you
-         really do want to get the GNU one.  Trust me, you will want to
-         do that even if it wasn't for git.  There's no point in living
-         in the dark ages any more. 
-
-       - "merge", the standard UNIX three-way merge program.  It usually
-         comes with the "rcs" package on most Linux distributions, so if
-         you have a developer install you probably have it already, but a
-         "graphical user desktop" install might have left it out.
-
-         You'll only need the merge program if you do development using
-         git, and if you only use git to track other peoples work you'll
-         never notice the lack of it. 
-
         - "wish", the Tcl/Tk windowing shell is used in gitk to show the
           history graphically
 
@@ -99,9 +80,6 @@ Issues of note:
        - "perl" and POSIX-compliant shells are needed to use most of
          the barebone Porcelainish scripts.
 
-       - "python" 2.3 or more recent; if you have 2.3, you may need
-          to build with "make WITH_OWN_SUBPROCESS_PY=YesPlease".
-
  - Some platform specific issues are dealt with Makefile rules,
    but depending on your specific installation, you may not
    have all the libraries/tools needed, or you may have
@@ -117,7 +95,7 @@ Issues of note:
    repository itself.  For example, you could:
 
        $ mkdir manual && cd manual
-       $ git init-db
+       $ git init
        $ git fetch-pack git://git.kernel.org/pub/scm/git/git.git man html |
          while read a b
          do
index 36ce8cd606395217850dd824c47d887783a09471..d66126d7ecce3b032e106342c46322e4cdcc3973 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 # The default target of this Makefile is...
-all:
+all::
 
 # Define NO_OPENSSL environment variable if you do not have OpenSSL.
 # This also implies MOZILLA_SHA1.
@@ -69,7 +69,14 @@ all:
 #
 # Define NO_MMAP if you want to avoid mmap.
 #
-# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
+# Define NO_PREAD if you have a problem with pread() system call (e.g.
+# cygwin.dll before v1.5.22).
+#
+# Define NO_FAST_WORKING_DIRECTORY if accessing objects in pack files is
+# generally faster on your platform than accessing the working directory.
+#
+# Define NO_TRUSTABLE_FILEMODE if your filesystem may claim to support
+# the executable mode bit, but doesn't really do so.
 #
 # Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
 #
@@ -78,12 +85,9 @@ all:
 #
 # Define NO_ICONV if your libc does not properly support iconv.
 #
-# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
-# a missing newline at the end of the file.
-#
-# Define COLLISION_CHECK below if you believe that SHA1's
-# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# sufficient guarantee that no collisions between objects will ever happen.
+# Define NO_R_TO_GCC if your gcc does not like "-R/path/lib" that
+# tells runtime paths to dynamic libraries; "-Wl,-rpath=/path/lib"
+# is used instead.
 #
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
@@ -93,6 +97,10 @@ all:
 #
 # Define USE_STDEV below if you want git to care about the underlying device
 # change being considered an inode change from the update-cache perspective.
+#
+# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
+# MakeMaker (e.g. using ActiveState under Cygwin).
+#
 
 GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -116,7 +124,6 @@ prefix = $(HOME)
 bindir = $(prefix)/bin
 gitexecdir = $(bindir)
 template_dir = $(prefix)/share/git-core/templates/
-GIT_PYTHON_DIR = $(prefix)/share/git-core/python
 # DESTDIR=
 
 # default configuration for gitweb
@@ -135,7 +142,7 @@ GITWEB_FAVICON = git-favicon.png
 GITWEB_SITE_HEADER =
 GITWEB_SITE_FOOTER =
 
-export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR
+export prefix bindir gitexecdir template_dir
 
 CC = gcc
 AR = ar
@@ -160,12 +167,12 @@ BASIC_LDFLAGS =
 SCRIPT_SH = \
        git-bisect.sh git-checkout.sh \
        git-clean.sh git-clone.sh git-commit.sh \
-       git-fetch.sh \
+       git-fetch.sh git-gc.sh \
        git-ls-remote.sh \
        git-merge-one-file.sh git-parse-remote.sh \
        git-pull.sh git-rebase.sh \
        git-repack.sh git-request-pull.sh git-reset.sh \
-       git-resolve.sh git-revert.sh git-sh-setup.sh \
+       git-revert.sh git-sh-setup.sh \
        git-tag.sh git-verify-tag.sh \
        git-applymbox.sh git-applypatch.sh git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
@@ -173,24 +180,21 @@ SCRIPT_SH = \
        git-lost-found.sh git-quiltimport.sh
 
 SCRIPT_PERL = \
+       git-add--interactive.perl \
        git-archimport.perl git-cvsimport.perl git-relink.perl \
-       git-shortlog.perl git-rerere.perl \
-       git-cvsserver.perl \
+       git-cvsserver.perl git-remote.perl \
        git-svnimport.perl git-cvsexportcommit.perl \
        git-send-email.perl git-svn.perl
 
-SCRIPT_PYTHON = \
-       git-merge-recursive-old.py
-
 SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
          $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-         $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
          git-cherry-pick git-status git-instaweb
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
-       git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \
+       git-convert-objects$X git-fetch-pack$X git-fsck$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 \
@@ -201,7 +205,7 @@ PROGRAMS = \
        git-update-server-info$X \
        git-upload-pack$X git-verify-pack$X \
        git-pack-redundant$X git-var$X \
-       git-describe$X git-merge-tree$X git-imap-send$X \
+       git-merge-tree$X git-imap-send$X \
        git-merge-recursive$X \
        $(EXTRA_PROGRAMS)
 
@@ -210,12 +214,12 @@ EXTRA_PROGRAMS =
 
 BUILT_INS = \
        git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
-       git-get-tar-commit-id$X \
+       git-get-tar-commit-id$X git-init$X git-repo-config$X \
+       git-fsck-objects$X \
        $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
 
 # what 'all' will build and 'install' will install, in gitexecdir
-ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS) \
-       git-merge-recur$X
+ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
 
 # Backward compatibility -- to be removed after 1.0
 PROGRAMS += git-ssh-pull$X git-ssh-push$X
@@ -227,12 +231,8 @@ endif
 ifndef PERL_PATH
        PERL_PATH = /usr/bin/perl
 endif
-ifndef PYTHON_PATH
-       PYTHON_PATH = /usr/bin/python
-endif
 
-PYMODULES = \
-       gitMergeCommon.py
+export PERL_PATH
 
 LIB_FILE=libgit.a
 XDIFF_LIB=xdiff/lib.a
@@ -241,7 +241,8 @@ LIB_H = \
        archive.h blob.h cache.h commit.h csum-file.h delta.h grep.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
+       tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \
+       utf8.h reflog-walk.h
 
 DIFF_OBJS = \
        diff.o diff-lib.o diffcore-break.o diffcore-order.o \
@@ -254,13 +255,14 @@ LIB_OBJS = \
        interpolate.o \
        lockfile.o \
        object.o pack-check.o patch-delta.o path.o pkt-line.o sideband.o \
+       reachable.o reflog-walk.o \
        quote.o read-cache.o refs.o run-command.o dir.o object-refs.o \
        server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
        tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
        revision.o pager.o tree-walk.o xdiff-interface.o \
        write_or_die.o trace.o list-objects.o grep.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
+       color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o
 
 BUILTIN_OBJS = \
        builtin-add.o \
@@ -274,13 +276,14 @@ BUILTIN_OBJS = \
        builtin-check-ref-format.o \
        builtin-commit-tree.o \
        builtin-count-objects.o \
+       builtin-describe.o \
        builtin-diff.o \
        builtin-diff-files.o \
        builtin-diff-index.o \
-       builtin-diff-stages.o \
        builtin-diff-tree.o \
        builtin-fmt-merge-msg.o \
        builtin-for-each-ref.o \
+       builtin-fsck.o \
        builtin-grep.o \
        builtin-init-db.o \
        builtin-log.o \
@@ -288,6 +291,8 @@ BUILTIN_OBJS = \
        builtin-ls-tree.o \
        builtin-mailinfo.o \
        builtin-mailsplit.o \
+       builtin-merge-base.o \
+       builtin-merge-file.o \
        builtin-mv.o \
        builtin-name-rev.o \
        builtin-pack-objects.o \
@@ -295,11 +300,14 @@ BUILTIN_OBJS = \
        builtin-prune-packed.o \
        builtin-push.o \
        builtin-read-tree.o \
-       builtin-repo-config.o \
+       builtin-reflog.o \
+       builtin-config.o \
+       builtin-rerere.o \
        builtin-rev-list.o \
        builtin-rev-parse.o \
        builtin-rm.o \
        builtin-runstatus.o \
+       builtin-shortlog.o \
        builtin-show-branch.o \
        builtin-stripspace.o \
        builtin-symbolic-ref.o \
@@ -334,18 +342,6 @@ ifeq ($(uname_S),Darwin)
        NEEDS_SSL_WITH_CRYPTO = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_STRLCPY = YesPlease
-       ifndef NO_FINK
-               ifeq ($(shell test -d /sw/lib && echo y),y)
-                       BASIC_CFLAGS += -I/sw/include
-                       BASIC_LDFLAGS += -L/sw/lib
-               endif
-       endif
-       ifndef NO_DARWIN_PORTS
-               ifeq ($(shell test -d /opt/local/lib && echo y),y)
-                       BASIC_CFLAGS += -I/opt/local/include
-                       BASIC_LDFLAGS += -L/opt/local/lib
-               endif
-       endif
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
@@ -374,10 +370,12 @@ ifeq ($(uname_O),Cygwin)
        NO_SYMLINK_HEAD = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_C99_FORMAT = YesPlease
+       NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
+       NO_TRUSTABLE_FILEMODE = UnfortunatelyYes
        # There are conflicting reports about this.
        # On some boxes NO_MMAP is needed, and not so elsewhere.
-       # Try uncommenting this if you see things break -- YMMV.
-       NO_MMAP = YesPlease
+       # Try commenting this out if you suspect MMAP is more efficient
+       NO_MMAP = YesPlease
        NO_IPV6 = YesPlease
        X = .exe
 endif
@@ -423,21 +421,34 @@ endif
 -include config.mak.autogen
 -include config.mak
 
-ifdef WITH_OWN_SUBPROCESS_PY
-       PYMODULES += compat/subprocess.py
-else
-       ifeq ($(NO_PYTHON),)
-               ifneq ($(shell $(PYTHON_PATH) -c 'import subprocess;print"OK"' 2>/dev/null),OK)
-                       PYMODULES += compat/subprocess.py
+ifeq ($(uname_S),Darwin)
+       ifndef NO_FINK
+               ifeq ($(shell test -d /sw/lib && echo y),y)
+                       BASIC_CFLAGS += -I/sw/include
+                       BASIC_LDFLAGS += -L/sw/lib
+               endif
+       endif
+       ifndef NO_DARWIN_PORTS
+               ifeq ($(shell test -d /opt/local/lib && echo y),y)
+                       BASIC_CFLAGS += -I/opt/local/include
+                       BASIC_LDFLAGS += -L/opt/local/lib
                endif
        endif
 endif
 
+ifdef NO_R_TO_GCC_LINKER
+       # Some gcc does not accept and pass -R to the linker to specify
+       # the runtime dynamic library path.
+       CC_LD_DYNPATH = -Wl,-rpath=
+else
+       CC_LD_DYNPATH = -R
+endif
+
 ifndef NO_CURL
        ifdef CURLDIR
-               # This is still problematic -- gcc does not always want -R.
+               # Try "-Wl,-rpath=$(CURLDIR)/lib" in such a case.
                BASIC_CFLAGS += -I$(CURLDIR)/include
-               CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
+               CURL_LIBCURL = -L$(CURLDIR)/lib $(CC_LD_DYNPATH)$(CURLDIR)/lib -lcurl
        else
                CURL_LIBCURL = -lcurl
        endif
@@ -456,9 +467,8 @@ endif
 ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
-               # Again this may be problematic -- gcc does not always want -R.
                BASIC_CFLAGS += -I$(OPENSSLDIR)/include
-               OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
+               OPENSSL_LINK = -L$(OPENSSLDIR)/lib $(CC_LD_DYNPATH)$(OPENSSLDIR)/lib
        else
                OPENSSL_LINK =
        endif
@@ -474,9 +484,8 @@ else
 endif
 ifdef NEEDS_LIBICONV
        ifdef ICONVDIR
-               # Again this may be problematic -- gcc does not always want -R.
                BASIC_CFLAGS += -I$(ICONVDIR)/include
-               ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
+               ICONV_LINK = -L$(ICONVDIR)/lib $(CC_LD_DYNPATH)$(ICONVDIR)/lib
        else
                ICONV_LINK =
        endif
@@ -495,7 +504,7 @@ ifdef NO_D_INO_IN_DIRENT
        BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
 endif
 ifdef NO_C99_FORMAT
-       ALL_CFLAGS += -DNO_C99_FORMAT
+       BASIC_CFLAGS += -DNO_C99_FORMAT
 endif
 ifdef NO_SYMLINK_HEAD
        BASIC_CFLAGS += -DNO_SYMLINK_HEAD
@@ -520,6 +529,16 @@ ifdef NO_MMAP
        COMPAT_CFLAGS += -DNO_MMAP
        COMPAT_OBJS += compat/mmap.o
 endif
+ifdef NO_PREAD
+       COMPAT_CFLAGS += -DNO_PREAD
+       COMPAT_OBJS += compat/pread.o
+endif
+ifdef NO_FAST_WORKING_DIRECTORY
+       BASIC_CFLAGS += -DNO_FAST_WORKING_DIRECTORY
+endif
+ifdef NO_TRUSTABLE_FILEMODE
+       BASIC_CFLAGS += -DNO_TRUSTABLE_FILEMODE
+endif
 ifdef NO_IPV6
        BASIC_CFLAGS += -DNO_IPV6
 endif
@@ -558,8 +577,8 @@ else
 endif
 endif
 endif
-ifdef NO_ACCURATE_DIFF
-       BASIC_CFLAGS += -DNO_ACCURATE_DIFF
+ifdef NO_PERL_MAKEMAKER
+       export NO_PERL_MAKEMAKER
 endif
 
 # Shell quote (do not use $(call) to accommodate ancient setups);
@@ -574,8 +593,6 @@ prefix_SQ = $(subst ','\'',$(prefix))
 
 SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
-PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
-GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
 
 LIBS = $(GITLIBS) $(EXTLIBS)
 
@@ -585,15 +602,19 @@ LIB_OBJS += $(COMPAT_OBJS)
 ALL_CFLAGS += $(BASIC_CFLAGS)
 ALL_LDFLAGS += $(BASIC_LDFLAGS)
 
-export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
+export prefix gitexecdir TAR INSTALL DESTDIR SHELL_PATH template_dir
 
 
 ### Build rules
 
-all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
+all:: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
+ifneq (,$X)
+       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$p';)
+endif
 
-all: perl/Makefile
-       $(MAKE) -C perl
+all::
+       $(MAKE) -C git-gui all
+       $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
        $(MAKE) -C templates
 
 strip: $(PROGRAMS) git$X
@@ -606,9 +627,6 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS
 
 help.o: common-cmds.h
 
-git-merge-recur$X: git-merge-recursive$X
-       rm -f $@ && ln git-merge-recursive$X $@
-
 $(BUILT_INS): git$X
        rm -f $@ && ln git$X $@
 
@@ -622,12 +640,15 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
            -e 's|@@PERL@@|$(PERL_PATH_SQ)|g' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
            $@.sh >$@+
        chmod +x $@+
        mv $@+ $@
 
-$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile
+$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
+
+perl/perl.mak: GIT-CFLAGS
+       $(MAKE) -C perl PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' $(@F)
+
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        rm -f $@ $@+
        INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
@@ -644,15 +665,6 @@ $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        chmod +x $@+
        mv $@+ $@
 
-$(patsubst %.py,%,$(SCRIPT_PYTHON)) : % : %.py GIT-CFLAGS
-       rm -f $@ $@+
-       sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \
-           -e 's|@@GIT_PYTHON_PATH@@|$(GIT_PYTHON_DIR_SQ)|g' \
-           -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
-           $@.py >$@+
-       chmod +x $@+
-       mv $@+ $@
-
 git-cherry-pick: git-revert
        cp $< $@+
        mv $@+ $@
@@ -689,7 +701,6 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
        sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
            -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
            -e 's/@@NO_CURL@@/$(NO_CURL)/g' \
-           -e 's/@@NO_PYTHON@@/$(NO_PYTHON)/g' \
            -e '/@@GITWEB_CGI@@/r gitweb/gitweb.cgi' \
            -e '/@@GITWEB_CGI@@/d' \
            -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \
@@ -709,7 +720,6 @@ configure: configure.ac
 git$X git.spec \
        $(patsubst %.sh,%,$(SCRIPT_SH)) \
        $(patsubst %.perl,%,$(SCRIPT_PERL)) \
-       $(patsubst %.py,%,$(SCRIPT_PYTHON)) \
        : GIT-VERSION-FILE
 
 %.o: %.c GIT-CFLAGS
@@ -759,7 +769,8 @@ $(DIFF_OBJS): diffcore.h
 $(LIB_FILE): $(LIB_OBJS)
        rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
 
-XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
+XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \
+       xdiff/xmerge.o
 $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
        xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
 
@@ -783,7 +794,7 @@ tags:
        find . -name '*.[hcS]' -print | xargs ctags -a
 
 ### Detect prefix changes
-TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):$(GIT_PYTHON_DIR_SQ):\
+TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
              $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
 
 GIT-CFLAGS: .FORCE-GIT-CFLAGS
@@ -799,7 +810,6 @@ GIT-CFLAGS: .FORCE-GIT-CFLAGS
 # However, the environment gets quite big, and some programs have problems
 # with that.
 
-export NO_PYTHON
 export NO_SVN_TESTS
 
 test: all
@@ -808,8 +818,8 @@ test: 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.c diff-delta.o patch-delta.o
-       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $^
+test-delta$X: test-delta.o diff-delta.o patch-delta.o $(GITLIBS)
+       $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
 
 test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS)
        $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
@@ -820,7 +830,7 @@ test-sha1$X: test-sha1.o $(GITLIBS)
 check-sha1:: test-sha1$X
        ./test-sha1.sh
 
-check:
+check: common-cmds.h
        for i in *.c; do sparse $(ALL_CFLAGS) $(SPARSE_FLAGS) $$i || exit; done
 
 
@@ -833,9 +843,8 @@ install: all
        $(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
        $(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
        $(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
-       $(MAKE) -C perl install
-       $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
-       $(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
+       $(MAKE) -C perl prefix='$(prefix_SQ)' install
+       $(MAKE) -C git-gui install
        if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
        then \
                ln -f '$(DESTDIR_SQ)$(bindir_SQ)/git$X' \
@@ -844,10 +853,15 @@ install: all
                        '$(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' ;)
+ifneq (,$X)
+       $(foreach p,$(patsubst %$X,%,$(filter %$X,$(ALL_PROGRAMS) $(BUILT_INS) git$X)), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p';)
+endif
 
 install-doc:
        $(MAKE) -C Documentation install
 
+quick-install-doc:
+       $(MAKE) -C Documentation quick-install
 
 
 
@@ -864,8 +878,11 @@ dist: git.spec git-archive
        @mkdir -p $(GIT_TARNAME)
        @cp git.spec $(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)/version
+               $(GIT_TARNAME)/git.spec \
+               $(GIT_TARNAME)/version \
+               $(GIT_TARNAME)/git-gui/version
        @rm -rf $(GIT_TARNAME)
        gzip -f -9 $(GIT_TARNAME).tar
 
@@ -905,8 +922,8 @@ clean:
        rm -f $(htmldocs).tar.gz $(manpages).tar.gz
        rm -f gitweb/gitweb.cgi
        $(MAKE) -C Documentation/ clean
-       [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean
-       rm -f perl/ppport.h perl/Makefile.old
+       $(MAKE) -C perl clean
+       $(MAKE) -C git-gui clean
        $(MAKE) -C templates/ clean
        $(MAKE) -C t/ clean
        rm -f GIT-VERSION-FILE GIT-CFLAGS
@@ -921,8 +938,7 @@ check-docs::
        do \
                case "$$v" in \
                git-merge-octopus | git-merge-ours | git-merge-recursive | \
-               git-merge-resolve | git-merge-stupid | git-merge-recur | \
-               git-merge-recursive-old | \
+               git-merge-resolve | git-merge-stupid | \
                git-ssh-pull | git-ssh-push ) continue ;; \
                esac ; \
                test -f "Documentation/$$v.txt" || \
diff --git a/README b/README
index cee7e435d7e28bb09c9dc39abf50e84358de7a92..441167cb8ad864b14c984ce9b58bf574c2b95357 100644 (file)
--- a/README
+++ b/README
@@ -3,6 +3,7 @@
        GIT - the stupid content tracker
 
 ////////////////////////////////////////////////////////////////
+
 "git" can mean anything, depending on your mood.
 
  - random three-letter combination that is pronounceable, and not
  - stupid. contemptible and despicable. simple. Take your pick from the
    dictionary of slang.
  - "global information tracker": you're in a good mood, and it actually
-   works for you. Angels sing, and a light suddenly fills the room. 
+   works for you. Angels sing, and a light suddenly fills the room.
  - "goddamn idiotic truckload of sh*t": when it breaks
 
-This is a stupid (but extremely fast) directory content manager.  It
-doesn't do a whole lot, but what it 'does' do is track directory
-contents efficiently. 
-
-There are two object abstractions: the "object database", and the
-"current directory cache" aka "index".
-
-The Object Database
-~~~~~~~~~~~~~~~~~~~
-The object database is literally just a content-addressable collection
-of objects.  All objects are named by their content, which is
-approximated by the SHA1 hash of the object itself.  Objects may refer
-to other objects (by referencing their SHA1 hash), and so you can
-build up a hierarchy of objects.
-
-All objects have a statically determined "type" aka "tag", which is
-determined at object creation time, and which identifies the format of
-the object (i.e. how it is used, and how it can refer to other
-objects).  There are currently four different object types: "blob",
-"tree", "commit" and "tag".
-
-A "blob" object cannot refer to any other object, and is, like the type
-implies, a pure storage object containing some user data.  It is used to
-actually store the file data, i.e. a blob object is associated with some
-particular version of some file. 
-
-A "tree" object is an object that ties one or more "blob" objects into a
-directory structure. In addition, a tree object can refer to other tree
-objects, thus creating a directory hierarchy. 
-
-A "commit" object ties such directory hierarchies together into
-a DAG of revisions - each "commit" is associated with exactly one tree
-(the directory hierarchy at the time of the commit). In addition, a
-"commit" refers to one or more "parent" commit objects that describe the
-history of how we arrived at that directory hierarchy.
-
-As a special case, a commit object with no parents is called the "root"
-object, and is the point of an initial project commit.  Each project
-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. 
-
-A "tag" object symbolically identifies and can be used to sign other
-objects. It contains the identifier and type of another object, a
-symbolic name (of course!) and, optionally, a signature.
-
-Regardless of object type, all objects share the following
-characteristics: they are all deflated with zlib, and have a header
-that not only specifies their type, but also provides size information
-about the data in the object.  It's worth noting that the SHA1 hash
-that is used to name the object is the hash of the original data
-plus this header, so `sha1sum` 'file' does not match the object name
-for 'file'.
-(Historical note: in the dawn of the age of git the hash
-was the sha1 of the 'compressed' object.)
-
-As a result, the general consistency of an object can always be tested
-independently of the contents or the type of the object: all objects can
-be validated by verifying that (a) their hashes match the content of the
-file and (b) the object successfully inflates to a stream of bytes that
-forms a sequence of <ascii type without space> + <space> + <ascii decimal
-size> + <byte\0> + <binary object data>. 
-
-The structured objects can further have their structure and
-connectivity to other objects verified. This is generally done with
-the `git-fsck-objects` program, which generates a full dependency graph
-of all objects, and verifies their internal consistency (in addition
-to just verifying their superficial consistency through the hash).
-
-The object types in some more detail:
-
-Blob Object
-~~~~~~~~~~~
-A "blob" object is nothing but a binary blob of data, and doesn't
-refer to anything else.  There is no signature or any other
-verification of the data, so while the object is consistent (it 'is'
-indexed by its sha1 hash, so the data itself is certainly correct), it
-has absolutely no other attributes.  No name associations, no
-permissions.  It is purely a blob of data (i.e. normally "file
-contents").
-
-In particular, since the blob is entirely defined by its data, if two
-files in a directory tree (or in multiple different versions of the
-repository) have the same contents, they will share the same blob
-object. The object is totally independent of its location in the
-directory tree, and renaming a file does not change the object that
-file is associated with in any way.
-
-A blob is typically created when gitlink:git-update-index[1]
-is run, and its data can be accessed by gitlink:git-cat-file[1].
-
-Tree Object
-~~~~~~~~~~~
-The next hierarchical object type is the "tree" object.  A tree object
-is a list of mode/name/blob data, sorted by name.  Alternatively, the
-mode data may specify a directory mode, in which case instead of
-naming a blob, that name is associated with another TREE object.
-
-Like the "blob" object, a tree object is uniquely determined by the
-set contents, and so two separate but identical trees will always
-share the exact same object. This is true at all levels, i.e. it's
-true for a "leaf" tree (which does not refer to any other trees, only
-blobs) as well as for a whole subdirectory.
-
-For that reason a "tree" object is just a pure data abstraction: it
-has no history, no signatures, no verification of validity, except
-that since the contents are again protected by the hash itself, we can
-trust that the tree is immutable and its contents never change.
-
-So you can trust the contents of a tree to be valid, the same way you
-can trust the contents of a blob, but you don't know where those
-contents 'came' from.
-
-Side note on trees: since a "tree" object is a sorted list of
-"filename+content", you can create a diff between two trees without
-actually having to unpack two trees.  Just ignore all common parts,
-and your diff will look right.  In other words, you can effectively
-(and efficiently) tell the difference between any two random trees by
-O(n) where "n" is the size of the difference, rather than the size of
-the tree.
-
-Side note 2 on trees: since the name of a "blob" depends entirely and
-exclusively on its contents (i.e. there are no names or permissions
-involved), you can see trivial renames or permission changes by
-noticing that the blob stayed the same.  However, renames with data
-changes need a smarter "diff" implementation.
-
-A tree is created with gitlink:git-write-tree[1] and
-its data can be accessed by gitlink:git-ls-tree[1].
-Two trees can be compared with gitlink:git-diff-tree[1].
-
-Commit Object
-~~~~~~~~~~~~~
-The "commit" object is an object that introduces the notion of
-history into the picture.  In contrast to the other objects, it
-doesn't just describe the physical state of a tree, it describes how
-we got there, and why.
-
-A "commit" is defined by the tree-object that it results in, the
-parent commits (zero, one or more) that led up to that point, and a
-comment on what happened.  Again, a commit is not trusted per se:
-the contents are well-defined and "safe" due to the cryptographically
-strong signatures at all levels, but there is no reason to believe
-that the tree is "good" or that the merge information makes sense.
-The parents do not have to actually have any relationship with the
-result, for example.
-
-Note on commits: unlike real SCM's, commits do not contain
-rename information or file mode change information.  All of that is
-implicit in the trees involved (the result tree, and the result trees
-of the parents), and describing that makes no sense in this idiotic
-file manager.
-
-A commit is created with gitlink:git-commit-tree[1] and
-its data can be accessed by gitlink:git-cat-file[1].
-
-Trust
-~~~~~
-An aside on the notion of "trust". Trust is really outside the scope
-of "git", but it's worth noting a few things.  First off, since
-everything is hashed with SHA1, you 'can' trust that an object is
-intact and has not been messed with by external sources.  So the name
-of an object uniquely identifies a known state - just not a state that
-you may want to trust.
-
-Furthermore, since the SHA1 signature of a commit refers to the
-SHA1 signatures of the tree it is associated with and the signatures
-of the parent, a single named commit specifies uniquely a whole set
-of history, with full contents.  You can't later fake any step of the
-way once you have the name of a commit.
-
-So to introduce some real trust in the system, the only thing you need
-to do is to digitally sign just 'one' special note, which includes the
-name of a top-level commit.  Your digital signature shows others
-that you trust that commit, and the immutability of the history of
-commits tells others that they can trust the whole history.
-
-In other words, you can easily validate a whole archive by just
-sending out a single email that tells the people the name (SHA1 hash)
-of the top commit, and digitally sign that email using something
-like GPG/PGP.
-
-To assist in this, git also provides the tag object...
-
-Tag Object
-~~~~~~~~~~
-Git provides the "tag" object to simplify creating, managing and
-exchanging symbolic and signed tokens.  The "tag" object at its
-simplest simply symbolically identifies another object by containing
-the sha1, type and symbolic name.
-
-However it can optionally contain additional signature information
-(which git doesn't care about as long as there's less than 8k of
-it). This can then be verified externally to git.
-
-Note that despite the tag features, "git" itself only handles content
-integrity; the trust framework (and signature provision and
-verification) has to come from outside.
-
-A tag is created with gitlink:git-mktag[1],
-its data can be accessed by gitlink:git-cat-file[1],
-and the signature can be verified by
-gitlink:git-verify-tag[1].
-
-
-The "index" aka "Current Directory Cache"
------------------------------------------
-The index is a simple binary file, which contains an efficient
-representation of a virtual directory content at some random time.  It
-does so by a simple array that associates a set of names, dates,
-permissions and content (aka "blob") objects together.  The cache is
-always kept ordered by name, and names are unique (with a few very
-specific rules) at any point in time, but the cache has no long-term
-meaning, and can be partially updated at any time.
-
-In particular, the index certainly does not need to be consistent with
-the current directory contents (in fact, most operations will depend on
-different ways to make the index 'not' be consistent with the directory
-hierarchy), but it has three very important attributes:
-
-'(a) it can re-generate the full state it caches (not just the
-directory structure: it contains pointers to the "blob" objects so
-that it can regenerate the data too)'
-
-As a special case, there is a clear and unambiguous one-way mapping
-from a current directory cache to a "tree object", which can be
-efficiently created from just the current directory cache without
-actually looking at any other data.  So a directory cache at any one
-time uniquely specifies one and only one "tree" object (but has
-additional data to make it easy to match up that tree object with what
-has happened in the directory)
-
-'(b) it has efficient methods for finding inconsistencies between that
-cached state ("tree object waiting to be instantiated") and the
-current state.'
-
-'(c) it can additionally efficiently represent information about merge
-conflicts between different tree objects, allowing each pathname to be
-associated with sufficient information about the trees involved that
-you can create a three-way merge between them.'
-
-Those are the three ONLY things that the directory cache does.  It's a
-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. 
-
-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.
-
-
-
-The Workflow
-------------
-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: 
-
-1) working directory -> index
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You update the index with information from the working directory with
-the gitlink:git-update-index[1] command.  You
-generally update the index information by just specifying the filename
-you want to update, like so:
-
-       git-update-index filename
-
-but to avoid common mistakes with filename globbing etc, the command
-will not normally add totally new entries or remove old entries,
-i.e. it will normally just update existing cache entries.
-
-To tell git that yes, you really do realize that certain files no
-longer exist, or that new files should be added, you
-should use the `--remove` and `--add` flags respectively.
-
-NOTE! A `--remove` flag does 'not' mean that subsequent filenames will
-necessarily be removed: if the files still exist in your directory
-structure, the index will be updated with their new status, not
-removed. The only thing `--remove` means is that update-cache will be
-considering a removed file to be a valid thing, and if the file really
-does not exist any more, it will update the index accordingly.
-
-As a special case, you can also do `git-update-index --refresh`, which
-will refresh the "stat" information of each index to match the current
-stat information. It will 'not' update the object status itself, and
-it will only update the fields that are used to quickly test whether
-an object still matches its old backing store object.
-
-2) index -> object database
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You write your current index file to a "tree" object with the program
-
-       git-write-tree
-
-that doesn't come with any options - it will just write out the
-current index into the set of tree objects that describe that state,
-and it will return the name of the resulting top-level tree. You can
-use that tree to re-generate the index at any time by going in the
-other direction:
-
-3) object database -> index
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You read a "tree" file from the object database, and use that to
-populate (and overwrite - don't do this if your index contains any
-unsaved state that you might want to restore later!) your current
-index.  Normal operation is just
-
-               git-read-tree <sha1 of tree>
-
-and your index file will now be equivalent to the tree that you saved
-earlier. However, that is only your 'index' file: your working
-directory contents have not been modified.
-
-4) index -> working directory
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-You update your working directory from the index by "checking out"
-files. This is not a very common operation, since normally you'd just
-keep your files updated, and rather than write to your working
-directory, you'd tell the index files about the changes in your
-working directory (i.e. `git-update-index`).
-
-However, if you decide to jump to a new version, or check out somebody
-else's version, or just restore a previous tree, you'd populate your
-index file with read-tree, and then you need to check out the result
-with
-
-               git-checkout-index filename
-
-or, if you want to check out all of the index, use `-a`.
-
-NOTE! git-checkout-index normally refuses to overwrite old files, so
-if you have an old version of the tree already checked out, you will
-need to use the "-f" flag ('before' the "-a" flag or the filename) to
-'force' the checkout.
-
-
-Finally, there are a few odds and ends which are not purely moving
-from one representation to the other:
-
-5) Tying it all together
-~~~~~~~~~~~~~~~~~~~~~~~~
-To commit a tree you have instantiated with "git-write-tree", you'd
-create a "commit" object that refers to that tree and the history
-behind it - most notably the "parent" commits that preceded it in
-history.
-
-Normally a "commit" has one parent: the previous state of the tree
-before a certain change was made. However, sometimes it can have two
-or more parent commits, in which case we call it a "merge", due to the
-fact that such a commit brings together ("merges") two or more
-previous states represented by other commits.
-
-In other words, while a "tree" represents a particular directory state
-of a working directory, a "commit" represents that state in "time",
-and explains how we got there.
-
-You create a commit object by giving it the tree that describes the
-state at the time of the commit, and a list of parents:
-
-       git-commit-tree <tree> -p <parent> [-p <parent2> ..]
-
-and then giving the reason for the commit on stdin (either through
-redirection from a pipe or file, or by just typing it at the tty).
-
-git-commit-tree will return the name of the object that represents
-that commit, and you should save it away for later use. Normally,
-you'd commit a new `HEAD` state, and while git doesn't care where you
-save the note about that state, in practice we tend to just write the
-result to the file pointed at by `.git/HEAD`, so that we can always see
-what the last committed state was.
-
-Here is an ASCII art by Jon Loeliger that illustrates how
-various pieces fit together.
-
-------------
-
-                     commit-tree
-                      commit obj
-                       +----+
-                       |    |
-                       |    |
-                       V    V
-                    +-----------+
-                    | Object DB |
-                    |  Backing  |
-                    |   Store   |
-                    +-----------+
-                       ^
-           write-tree  |     |
-             tree obj  |     |
-                       |     |  read-tree
-                       |     |  tree obj
-                             V
-                    +-----------+
-                    |   Index   |
-                    |  "cache"  |
-                    +-----------+
-         update-index  ^
-             blob obj  |     |
-                       |     |
-    checkout-index -u  |     |  checkout-index
-             stat      |     |  blob obj
-                             V
-                    +-----------+
-                    |  Working  |
-                    | Directory |
-                    +-----------+
-
-------------
-
-
-6) Examining the data
-~~~~~~~~~~~~~~~~~~~~~
-
-You can examine the data represented in the object database and the
-index with various helper tools. For every object, you can use
-gitlink:git-cat-file[1] to examine details about the
-object:
-
-               git-cat-file -t <objectname>
-
-shows the type of the object, and once you have the type (which is
-usually implicit in where you find the object), you can use
-
-               git-cat-file blob|tree|commit|tag <objectname>
-
-to show its contents. NOTE! Trees have binary content, and as a result
-there is a special helper for showing that content, called
-`git-ls-tree`, which turns the binary content into a more easily
-readable form.
-
-It's especially instructive to look at "commit" objects, since those
-tend to be small and fairly self-explanatory. In particular, if you
-follow the convention of having the top commit name in `.git/HEAD`,
-you can do
-
-               git-cat-file commit HEAD
-
-to see what the top commit was.
-
-7) Merging multiple trees
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Git helps you do a three-way merge, which you can expand to n-way by
-repeating the merge procedure arbitrary times until you finally
-"commit" the state.  The normal situation is that you'd only do one
-three-way merge (two parents), and commit it, but if you like to, you
-can do multiple parents in one go.
-
-To do a three-way merge, you need the two sets of "commit" objects
-that you want to merge, use those to find the closest common parent (a
-third "commit" object), and then use those commit objects to find the
-state of the directory ("tree" object) at these points.
-
-To get the "base" for the merge, you first look up the common parent
-of two commits with
-
-               git-merge-base <commit1> <commit2>
-
-which will return you the commit they are both based on.  You should
-now look up the "tree" objects of those commits, which you can easily
-do with (for example)
-
-               git-cat-file commit <commitname> | head -1
-
-since the tree object information is always the first line in a commit
-object.
-
-Once you know the three trees you are going to merge (the one
-"original" tree, aka the common case, and the two "result" trees, aka
-the branches you want to merge), you do a "merge" read into the
-index. This will complain if it has to throw away your old index contents, so you should
-make sure that you've committed those - in fact you would normally
-always do a merge against your last commit (which should thus match
-what you have in your current index anyway).
-
-To do the merge, do
-
-               git-read-tree -m -u <origtree> <yourtree> <targettree>
-
-which will do all trivial merge operations for you directly in the
-index file, and you can just write the result out with
-`git-write-tree`.
-
-Historical note.  We did not have `-u` facility when this
-section was first written, so we used to warn that
-the merge is done in the index file, not in your
-working tree, and your working tree will not match your
-index after this step.
-This is no longer true.  The above command, thanks to `-u`
-option, updates your working tree with the merge results for
-paths that have been trivially merged.
-
-
-8) Merging multiple trees, continued
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Sadly, many merges aren't trivial. If there are files that have
-been added.moved 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
-other tools before you can write out the result.
-
-You can examine such index state with `git-ls-files --unmerged`
-command.  An example:
-
-------------------------------------------------
-$ git-read-tree -m $orig HEAD $target
-$ git-ls-files --unmerged
-100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1      hello.c
-100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2      hello.c
-100644 cc44c73eb783565da5831b4d820c962954019b69 3      hello.c
-------------------------------------------------
-
-Each line of the `git-ls-files --unmerged` output begins with
-the blob mode bits, blob SHA1, 'stage number', and the
-filename.  The 'stage number' is git's way to say which tree it
-came from: stage 1 corresponds to `$orig` tree, stage 2 `HEAD`
-tree, and stage3 `$target` tree.
-
-Earlier we said that trivial merges are done inside
-`git-read-tree -m`.  For example, if the file did not change
-from `$orig` to `HEAD` nor `$target`, or if the file changed
-from `$orig` to `HEAD` and `$orig` to `$target` the same way,
-obviously the final outcome is what is in `HEAD`.  What the
-above example shows is that file `hello.c` was changed from
-`$orig` to `HEAD` and `$orig` to `$target` in a different way.
-You could resolve this by running your favorite 3-way merge
-program, e.g.  `diff3` or `merge`, on the blob objects from
-these three stages yourself, like this:
-
-------------------------------------------------
-$ git-cat-file blob 263414f... >hello.c~1
-$ git-cat-file blob 06fa6a2... >hello.c~2
-$ git-cat-file blob cc44c73... >hello.c~3
-$ merge hello.c~2 hello.c~1 hello.c~3
-------------------------------------------------
-
-This would leave the merge result in `hello.c~2` file, along
-with conflict markers if there are conflicts.  After verifying
-the merge result makes sense, you can tell git what the final
-merge result for this file is by:
-
-       mv -f hello.c~2 hello.c
-       git-update-index hello.c
-
-When a path is in unmerged state, running `git-update-index` for
-that path tells git to mark the path resolved.
-
-The above is the description of a git merge at the lowest level,
-to help you understand what conceptually happens under the hood.
-In practice, nobody, not even git itself, uses three `git-cat-file`
-for this.  There is `git-merge-index` program that extracts the
-stages to temporary files and calls a "merge" script on it:
-
-       git-merge-index git-merge-one-file hello.c
-
-and that is what higher level `git resolve` is implemented with.
+Git is a fast, scalable, distributed revision control system with an
+unusually rich command set that provides both high-level operations
+and full access to internals.
+
+Git is an Open Source project covered by the GNU General Public License.
+It was originally written by Linus Torvalds with help of a group of
+hackers around the net. It is currently maintained by Junio C Hamano.
+
+Please read the file INSTALL for installation instructions.
+See Documentation/tutorial.txt to get started, then see
+Documentation/everyday.txt for a useful minimum set of commands,
+and "man git-commandname" for documentation of each command.
+CVS users may also want to read Documentation/cvs-migration.txt.
+
+Many Git online resources are accessible from http://git.or.cz/
+including full documentation and Git related tools.
+
+The user discussion and development of Git take place on the Git
+mailing list -- everyone is welcome to post bug reports, feature
+requests, comments and patches to git@vger.kernel.org. To subscribe
+to the list, send an email with just "subscribe git" in the body to
+majordomo@vger.kernel.org. The mailing list archives are available at
+http://marc.theaimsgroup.com/?l=git and other archival sites.
diff --git a/RelNotes b/RelNotes
new file mode 120000 (symlink)
index 0000000..4571d0d
--- /dev/null
+++ b/RelNotes
@@ -0,0 +1 @@
+Documentation/RelNotes-1.5.0.txt
\ No newline at end of file
index ff0f6e29292cf716ac32e8ae63a58902fcccc120..7d52a061f4f8b5d29c52912889ca0d6ee46e4ee0 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2005, 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "commit.h"
 #include "strbuf.h"
@@ -16,7 +15,7 @@ static char block[BLOCKSIZE];
 static unsigned long offset;
 
 static time_t archive_time;
-static int tar_umask;
+static int tar_umask = 002;
 static int verbose;
 
 /* writes out the whole block, but only if it is full */
@@ -211,11 +210,10 @@ static void write_entry(const unsigned char *sha1, struct strbuf *path,
        sprintf(header.size, "%011lo", S_ISREG(mode) ? size : 0);
        sprintf(header.mtime, "%011lo", archive_time);
 
-       /* XXX: should we provide more meaningful info here? */
        sprintf(header.uid, "%07o", 0);
        sprintf(header.gid, "%07o", 0);
-       strlcpy(header.uname, "git", sizeof(header.uname));
-       strlcpy(header.gname, "git", sizeof(header.gname));
+       strlcpy(header.uname, "root", sizeof(header.uname));
+       strlcpy(header.gname, "root", sizeof(header.gname));
        sprintf(header.devmajor, "%07o", 0);
        sprintf(header.devminor, "%07o", 0);
 
index ae5572ae208940637314b80e147dcfb03e76fee3..f31b8ed8236c3fcad10ac9a84e2f508f11e1a6cf 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "commit.h"
 #include "blob.h"
@@ -35,6 +34,7 @@ struct zip_local_header {
        unsigned char size[4];
        unsigned char filename_length[2];
        unsigned char extra_length[2];
+       unsigned char _end[1];
 };
 
 struct zip_dir_header {
@@ -55,6 +55,7 @@ struct zip_dir_header {
        unsigned char attr1[2];
        unsigned char attr2[4];
        unsigned char offset[4];
+       unsigned char _end[1];
 };
 
 struct zip_dir_trailer {
@@ -66,8 +67,18 @@ struct zip_dir_trailer {
        unsigned char size[4];
        unsigned char offset[4];
        unsigned char comment_length[2];
+       unsigned char _end[1];
 };
 
+/*
+ * On ARM, padding is added at the end of the struct, so a simple
+ * sizeof(struct ...) reports two bytes more than the payload size
+ * we're interested in.
+ */
+#define ZIP_LOCAL_HEADER_SIZE  offsetof(struct zip_local_header, _end)
+#define ZIP_DIR_HEADER_SIZE    offsetof(struct zip_dir_header, _end)
+#define ZIP_DIR_TRAILER_SIZE   offsetof(struct zip_dir_trailer, _end)
+
 static void copy_le16(unsigned char *dest, unsigned int n)
 {
        dest[0] = 0xff & n;
@@ -211,7 +222,7 @@ static int write_zip_entry(const unsigned char *sha1,
        }
 
        /* make sure we have enough free space in the dictionary */
-       direntsize = sizeof(struct zip_dir_header) + pathlen;
+       direntsize = ZIP_DIR_HEADER_SIZE + pathlen;
        while (zip_dir_size < zip_dir_offset + direntsize) {
                zip_dir_size += ZIP_DIRECTORY_MIN_SIZE;
                zip_dir = xrealloc(zip_dir, zip_dir_size);
@@ -234,8 +245,8 @@ static int write_zip_entry(const unsigned char *sha1,
        copy_le16(dirent.attr1, 0);
        copy_le32(dirent.attr2, attr2);
        copy_le32(dirent.offset, zip_offset);
-       memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));
-       zip_dir_offset += sizeof(struct zip_dir_header);
+       memcpy(zip_dir + zip_dir_offset, &dirent, ZIP_DIR_HEADER_SIZE);
+       zip_dir_offset += ZIP_DIR_HEADER_SIZE;
        memcpy(zip_dir + zip_dir_offset, path, pathlen);
        zip_dir_offset += pathlen;
        zip_dir_entries++;
@@ -251,8 +262,8 @@ static int write_zip_entry(const unsigned char *sha1,
        copy_le32(header.size, uncompressed_size);
        copy_le16(header.filename_length, pathlen);
        copy_le16(header.extra_length, 0);
-       write_or_die(1, &header, sizeof(struct zip_local_header));
-       zip_offset += sizeof(struct zip_local_header);
+       write_or_die(1, &header, ZIP_LOCAL_HEADER_SIZE);
+       zip_offset += ZIP_LOCAL_HEADER_SIZE;
        write_or_die(1, path, pathlen);
        zip_offset += pathlen;
        if (compressed_size > 0) {
@@ -282,7 +293,7 @@ static void write_zip_trailer(const unsigned char *sha1)
        copy_le16(trailer.comment_length, sha1 ? 40 : 0);
 
        write_or_die(1, zip_dir, zip_dir_offset);
-       write_or_die(1, &trailer, sizeof(struct zip_dir_trailer));
+       write_or_die(1, &trailer, ZIP_DIR_TRAILER_SIZE);
        if (sha1)
                write_or_die(1, sha1_to_hex(sha1), 40);
 }
diff --git a/blob.c b/blob.c
index d1af2e62f14965fab491a3d9dde712f02b718adc..9776beac5827a4c705eddde426817dcc40c525bc 100644 (file)
--- a/blob.c
+++ b/blob.c
@@ -1,6 +1,5 @@
 #include "cache.h"
 #include "blob.h"
-#include <stdlib.h>
 
 const char *blob_type = "blob";
 
index febb75ed994b5edc51afc60e2d46483fa9e273f2..87e16aa2200f90e2f12673e856040ed29ee7d119 100644 (file)
@@ -3,15 +3,14 @@
  *
  * Copyright (C) 2006 Linus Torvalds
  */
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
+#include "exec_cmd.h"
 #include "cache-tree.h"
 
 static const char builtin_add_usage[] =
-"git-add [-n] [-v] <filepattern>...";
+"git-add [-n] [-v] [-f] [--interactive | -i] [--] <filepattern>...";
 
 static void prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
 {
@@ -27,11 +26,9 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
        i = dir->nr;
        while (--i >= 0) {
                struct dir_entry *entry = *src++;
-               if (!match_pathspec(pathspec, entry->name, entry->len, prefix, seen)) {
-                       free(entry);
-                       continue;
-               }
-               *dst++ = entry;
+               if (match_pathspec(pathspec, entry->name, entry->len,
+                                  prefix, seen))
+                       *dst++ = entry;
        }
        dir->nr = dst - dir->entries;
 
@@ -41,10 +38,20 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p
                if (seen[i])
                        continue;
 
-               /* Existing file? We must have ignored it */
                match = pathspec[i];
-               if (!match[0] || !lstat(match, &st))
+               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);
        }
 }
@@ -83,20 +90,35 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec)
 
 static struct lock_file lock_file;
 
+static const char ignore_warning[] =
+"The following paths are ignored by one of your .gitignore files:\n";
+
 int cmd_add(int argc, const char **argv, const char *prefix)
 {
        int i, newfd;
-       int verbose = 0, show_only = 0;
+       int verbose = 0, show_only = 0, ignored_too = 0;
        const char **pathspec;
        struct dir_struct dir;
+       int add_interactive = 0;
+
+       for (i = 1; i < argc; i++) {
+               if (!strcmp("--interactive", argv[i]) ||
+                   !strcmp("-i", argv[i]))
+                       add_interactive++;
+       }
+       if (add_interactive) {
+               const char *args[] = { "add--interactive", NULL };
+
+               if (add_interactive != 1 || argc != 2)
+                       die("add --interactive does not take any parameters");
+               execv_git_cmd(args);
+               exit(1);
+       }
 
        git_config(git_default_config);
 
        newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1);
 
-       if (read_cache() < 0)
-               die("index file corrupt");
-
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
@@ -110,12 +132,21 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                        show_only = 1;
                        continue;
                }
+               if (!strcmp(arg, "-f")) {
+                       ignored_too = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-v")) {
                        verbose = 1;
                        continue;
                }
                usage(builtin_add_usage);
        }
+       if (argc <= i) {
+               fprintf(stderr, "Nothing specified, nothing added.\n");
+               fprintf(stderr, "Maybe you wanted to say 'git add .'?\n");
+               return 0;
+       }
        pathspec = get_pathspec(prefix, argv + i);
 
        fill_directory(&dir, pathspec);
@@ -123,6 +154,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
        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";
@@ -131,6 +164,30 @@ int cmd_add(int argc, const char **argv, const char *prefix)
                return 0;
        }
 
+       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);
+               }
+       }
+
        for (i = 0; i < dir.nr; i++)
                add_file_to_index(dir.entries[i]->name, verbose);
 
index 57c46840d542f31dd4e42455140fc901f1eeb671..9db7cfe74c7a8583995e6c20c4eedebf58391fbc 100644 (file)
@@ -12,7 +12,7 @@ int cmd_annotate(int argc, const char **argv, const char *prefix)
        int i;
        nargv = xmalloc(sizeof(char *) * (argc + 2));
 
-       nargv[0] = "blame";
+       nargv[0] = "annotate";
        nargv[1] = "-c";
 
        for (i = 1; i < argc; i++) {
index 61f047fd45383ac64f27bef88e41b56efefdea3b..3fefdacd94526b62ac72707d6d80b039f104c06d 100644 (file)
@@ -6,7 +6,6 @@
  * This applies patches on top of some (arbitrary) version of the SCM.
  *
  */
-#include <fnmatch.h>
 #include "cache.h"
 #include "cache-tree.h"
 #include "quote.h"
@@ -812,7 +811,8 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
                        struct fragment dummy;
                        if (parse_fragment_header(line, len, &dummy) < 0)
                                continue;
-                       error("patch fragment without header at line %d: %.*s", linenr, (int)len-1, line);
+                       die("patch fragment without header at line %d: %.*s",
+                           linenr, (int)len-1, line);
                }
 
                if (size < len + 6)
@@ -2119,7 +2119,11 @@ static void numstat_patch_list(struct patch *patch)
        for ( ; patch; patch = patch->next) {
                const char *name;
                name = patch->new_name ? patch->new_name : patch->old_name;
-               printf("%d\t%d\t", patch->lines_added, patch->lines_deleted);
+               if (patch->is_binary)
+                       printf("-\t-\t");
+               else
+                       printf("%d\t%d\t",
+                              patch->lines_added, patch->lines_deleted);
                if (line_termination && quote_c_style(name, NULL, NULL, 0))
                        quote_c_style(name, NULL, stdout, 0);
                else
@@ -2235,8 +2239,19 @@ static void remove_file(struct patch *patch)
                        die("unable to remove %s from index", patch->old_name);
                cache_tree_invalidate_path(active_cache_tree, patch->old_name);
        }
-       if (!cached)
-               unlink(patch->old_name);
+       if (!cached) {
+               if (!unlink(patch->old_name)) {
+                       char *name = xstrdup(patch->old_name);
+                       char *end = strrchr(name, '/');
+                       while (end) {
+                               *end = 0;
+                               if (rmdir(name))
+                                       break;
+                               end = strrchr(name, '/');
+                       }
+                       free(name);
+               }
+       }
 }
 
 static void add_index_file(const char *path, unsigned mode, void *buf, unsigned long size)
@@ -2574,7 +2589,7 @@ static int git_apply_config(const char *var, const char *value)
 }
 
 
-int cmd_apply(int argc, const char **argv, const char *prefix)
+int cmd_apply(int argc, const char **argv, const char *unused_prefix)
 {
        int i;
        int read_stdin = 1;
index a8a1f079bf4e28a3f07d49b6a4723ee8dea17178..f613ac25164beadb4874d8a07b6bbac62796a530 100644 (file)
@@ -2,7 +2,6 @@
  * Copyright (c) 2006 Franck Bui-Huu
  * Copyright (c) 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "builtin.h"
 #include "archive.h"
@@ -75,6 +74,7 @@ static int run_remote_archiver(const char *remote, int argc,
        /* Now, start reading from fd[0] and spit it out to stdout */
        rv = recv_sideband("archive", fd[0], 1, 2);
        close(fd[0]);
+       close(fd[1]);
        rv |= finish_connect(pid);
 
        return !!rv;
@@ -138,7 +138,6 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
                if (err || !S_ISDIR(mode))
                        die("current working directory is untracked");
 
-               free(tree);
                tree = parse_tree_indirect(tree_sha1);
        }
        ar_args->tree = tree;
index 066dee743e9fdd80478395c605d9afc4682600c6..69fc145a38090488e8da4b60a56154fc999b6fe5 100644 (file)
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "quote.h"
 #include "xdiff-interface.h"
-
-#include <time.h>
-#include <sys/time.h>
-#include <regex.h>
+#include "cache-tree.h"
 
 static char blame_usage[] =
-"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [commit] [--] file\n"
+"git-blame [-c] [-l] [-t] [-f] [-n] [-p] [-L n,m] [-S <revs-file>] [-M] [-C] [-C] [--contents <filename>] [--incremental] [commit] [--] file\n"
 "  -c, --compatibility Use the same output mode as git-annotate (Default: off)\n"
+"  -b                  Show blank SHA-1 for boundary commits (Default: off)\n"
 "  -l, --long          Show long commit SHA1 (Default: off)\n"
+"  --root              Do not treat root commits as boundaries (Default: off)\n"
 "  -t, --time          Show raw timestamp (Default: off)\n"
 "  -f, --show-name     Show original filename (Default: auto)\n"
 "  -n, --show-number   Show original linenumber (Default: off)\n"
 "  -p, --porcelain     Show in a format designed for machine consumption\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"
+"  --contents file     Use <file>'s contents as the final image\n"
 "  -S revs-file        Use revisions from revs-file instead of calling git-rev-list\n";
 
 static int longest_file;
@@ -36,6 +38,10 @@ static int longest_author;
 static int max_orig_digits;
 static int max_digits;
 static int max_score_digits;
+static int show_root;
+static int blank_boundary;
+static int incremental;
+static int cmd_is_annotate;
 
 #ifndef DEBUG
 #define DEBUG 0
@@ -74,6 +80,10 @@ struct origin {
        char path[FLEX_ARRAY];
 };
 
+/*
+ * Given an origin, prepare mmfile_t structure to be used by the
+ * diff machinery
+ */
 static char *fill_origin_blob(struct origin *o, mmfile_t *file)
 {
        if (!o->file.ptr) {
@@ -88,6 +98,10 @@ static char *fill_origin_blob(struct origin *o, mmfile_t *file)
        return file->ptr;
 }
 
+/*
+ * Origin is refcounted and usually we keep the blob contents to be
+ * reused.
+ */
 static inline struct origin *origin_incref(struct origin *o)
 {
        if (o)
@@ -105,6 +119,11 @@ static void origin_decref(struct origin *o)
        }
 }
 
+/*
+ * Each group of lines is described by a blame_entry; it can be split
+ * as we pass blame to the parents.  They form a linked list in the
+ * scoreboard structure, sorted by the target line number.
+ */
 struct blame_entry {
        struct blame_entry *prev;
        struct blame_entry *next;
@@ -131,19 +150,24 @@ struct blame_entry {
        int s_lno;
 
        /* how significant this entry is -- cached to avoid
-        * scanning the lines over and over
+        * scanning the lines over and over.
         */
        unsigned score;
 };
 
+/*
+ * The current state of the blame assignment.
+ */
 struct scoreboard {
        /* the final commit (i.e. where we started digging from) */
        struct commit *final;
 
        const char *path;
 
-       /* the contents in the final; pointed into by buf pointers of
-        * blame_entries
+       /*
+        * The contents in the final image.
+        * Used by many functions to obtain contents of the nth line,
+        * indexed with scoreboard.lineno[blame_entry.lno].
         */
        const char *final_buf;
        unsigned long final_buf_size;
@@ -168,6 +192,11 @@ static int cmp_suspect(struct origin *a, struct origin *b)
 
 static void sanity_check_refcnt(struct scoreboard *);
 
+/*
+ * If two blame entries that are next to each other came from
+ * contiguous lines in the same origin (i.e. <commit, path> pair),
+ * merge them together.
+ */
 static void coalesce(struct scoreboard *sb)
 {
        struct blame_entry *ent, *next;
@@ -191,6 +220,12 @@ static void coalesce(struct scoreboard *sb)
                sanity_check_refcnt(sb);
 }
 
+/*
+ * Given a commit and a path in it, create a new origin structure.
+ * The callers that add blame to the scoreboard should use
+ * get_origin() to obtain shared, refcounted copy instead of calling
+ * this function directly.
+ */
 static struct origin *make_origin(struct commit *commit, const char *path)
 {
        struct origin *o;
@@ -201,6 +236,9 @@ static struct origin *make_origin(struct commit *commit, const char *path)
        return o;
 }
 
+/*
+ * Locate an existing origin or create a new one.
+ */
 static struct origin *get_origin(struct scoreboard *sb,
                                 struct commit *commit,
                                 const char *path)
@@ -215,6 +253,13 @@ static struct origin *get_origin(struct scoreboard *sb,
        return make_origin(commit, path);
 }
 
+/*
+ * Fill the blob_sha1 field of an origin if it hasn't, so that later
+ * call to fill_origin_blob() can use it to locate the data.  blob_sha1
+ * for an origin is also used to pass the blame for the entire file to
+ * the parent to detect the case where a child's blob is identical to
+ * that of its parent's.
+ */
 static int fill_blob_sha1(struct origin *origin)
 {
        unsigned mode;
@@ -235,6 +280,10 @@ static int fill_blob_sha1(struct origin *origin)
        return -1;
 }
 
+/*
+ * We have an origin -- check if the same path exists in the
+ * parent and return an origin structure to represent it.
+ */
 static struct origin *find_origin(struct scoreboard *sb,
                                  struct commit *parent,
                                  struct origin *origin)
@@ -244,12 +293,26 @@ static struct origin *find_origin(struct scoreboard *sb,
        const char *paths[2];
 
        if (parent->util) {
-               /* This is a freestanding copy of origin and not
-                * refcounted.
+               /*
+                * Each commit object can cache one origin in that
+                * commit.  This is a freestanding copy of origin and
+                * not refcounted.
                 */
                struct origin *cached = parent->util;
                if (!strcmp(cached->path, origin->path)) {
+                       /*
+                        * The same path between origin and its parent
+                        * without renaming -- the most common case.
+                        */
                        porigin = get_origin(sb, parent, cached->path);
+
+                       /*
+                        * If the origin was newly created (i.e. get_origin
+                        * would call make_origin if none is found in the
+                        * scoreboard), it does not know the blob_sha1,
+                        * so copy it.  Otherwise porigin was in the
+                        * scoreboard and already knows blob_sha1.
+                        */
                        if (porigin->refcnt == 1)
                                hashcpy(porigin->blob_sha1, cached->blob_sha1);
                        return porigin;
@@ -273,9 +336,13 @@ static struct origin *find_origin(struct scoreboard *sb,
        diff_tree_setup_paths(paths, &diff_opts);
        if (diff_setup_done(&diff_opts) < 0)
                die("diff-setup");
-       diff_tree_sha1(parent->tree->object.sha1,
-                      origin->commit->tree->object.sha1,
-                      "", &diff_opts);
+
+       if (is_null_sha1(origin->commit->object.sha1))
+               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       else
+               diff_tree_sha1(parent->tree->object.sha1,
+                              origin->commit->tree->object.sha1,
+                              "", &diff_opts);
        diffcore_std(&diff_opts);
 
        /* It is either one entry that says "modified", or "created",
@@ -306,7 +373,13 @@ static struct origin *find_origin(struct scoreboard *sb,
        }
        diff_flush(&diff_opts);
        if (porigin) {
+               /*
+                * Create a freestanding copy that is not part of
+                * the refcounted origin found in the scoreboard, and
+                * cache it in the commit.
+                */
                struct origin *cached;
+
                cached = make_origin(porigin->commit, porigin->path);
                hashcpy(cached->blob_sha1, porigin->blob_sha1);
                parent->util = cached;
@@ -314,6 +387,10 @@ static struct origin *find_origin(struct scoreboard *sb,
        return porigin;
 }
 
+/*
+ * We have an origin -- find the path that corresponds to it in its
+ * parent and return an origin structure to represent it.
+ */
 static struct origin *find_rename(struct scoreboard *sb,
                                  struct commit *parent,
                                  struct origin *origin)
@@ -332,9 +409,13 @@ static struct origin *find_rename(struct scoreboard *sb,
        diff_tree_setup_paths(paths, &diff_opts);
        if (diff_setup_done(&diff_opts) < 0)
                die("diff-setup");
-       diff_tree_sha1(parent->tree->object.sha1,
-                      origin->commit->tree->object.sha1,
-                      "", &diff_opts);
+
+       if (is_null_sha1(origin->commit->object.sha1))
+               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       else
+               diff_tree_sha1(parent->tree->object.sha1,
+                              origin->commit->tree->object.sha1,
+                              "", &diff_opts);
        diffcore_std(&diff_opts);
 
        for (i = 0; i < diff_queued_diff.nr; i++) {
@@ -350,6 +431,9 @@ static struct origin *find_rename(struct scoreboard *sb,
        return porigin;
 }
 
+/*
+ * Parsing of patch chunks...
+ */
 struct chunk {
        /* line number in postimage; up to but not including this
         * line is the same as preimage
@@ -451,6 +535,11 @@ static struct patch *compare_buffer(mmfile_t *file_p, mmfile_t *file_o,
        return state.ret;
 }
 
+/*
+ * Run diff between two origins and grab the patch output, so that
+ * we can pass blame for lines origin is currently suspected for
+ * to its parent.
+ */
 static struct patch *get_patch(struct origin *parent, struct origin *origin)
 {
        mmfile_t file_p, file_o;
@@ -471,6 +560,10 @@ static void free_patch(struct patch *p)
        free(p);
 }
 
+/*
+ * Link in a new blame entry to the scoreboard.  Entries that cover the
+ * same line range have been removed from the scoreboard previously.
+ */
 static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
 {
        struct blame_entry *ent, *prev = NULL;
@@ -494,6 +587,12 @@ static void add_blame_entry(struct scoreboard *sb, struct blame_entry *e)
                e->next->prev = e;
 }
 
+/*
+ * src typically is on-stack; we want to copy the information in it to
+ * an malloced blame_entry that is already on the linked list of the
+ * scoreboard.  The origin of dst loses a refcnt while the origin of src
+ * gains one.
+ */
 static void dup_entry(struct blame_entry *dst, struct blame_entry *src)
 {
        struct blame_entry *p, *n;
@@ -513,25 +612,25 @@ static const char *nth_line(struct scoreboard *sb, int lno)
        return sb->final_buf + sb->lineno[lno];
 }
 
+/*
+ * It is known that lines between tlno to same came from parent, and e
+ * has an overlap with that range.  it also is known that parent's
+ * line plno corresponds to e's line tlno.
+ *
+ *                <---- e ----->
+ *                   <------>
+ *                   <------------>
+ *             <------------>
+ *             <------------------>
+ *
+ * Split e into potentially three parts; before this chunk, the chunk
+ * to be blamed for the parent, and after that portion.
+ */
 static void split_overlap(struct blame_entry *split,
                          struct blame_entry *e,
                          int tlno, int plno, int same,
                          struct origin *parent)
 {
-       /* it is known that lines between tlno to same came from
-        * parent, and e has an overlap with that range.  it also is
-        * known that parent's line plno corresponds to e's line tlno.
-        *
-        *                <---- e ----->
-        *                   <------>
-        *                   <------------>
-        *             <------------>
-        *             <------------------>
-        *
-        * Potentially we need to split e into three parts; before
-        * this chunk, the chunk to be blamed for parent, and after
-        * that portion.
-        */
        int chunk_end_lno;
        memset(split, 0, sizeof(struct blame_entry [3]));
 
@@ -561,11 +660,20 @@ static void split_overlap(struct blame_entry *split,
                chunk_end_lno = e->lno + e->num_lines;
        split[1].num_lines = chunk_end_lno - split[1].lno;
 
+       /*
+        * if it turns out there is nothing to blame the parent for,
+        * forget about the splitting.  !split[1].suspect signals this.
+        */
        if (split[1].num_lines < 1)
                return;
        split[1].suspect = origin_incref(parent);
 }
 
+/*
+ * split_overlap() divided an existing blame e into up to three parts
+ * in split.  Adjust the linked list of blames in the scoreboard to
+ * reflect the split.
+ */
 static void split_blame(struct scoreboard *sb,
                        struct blame_entry *split,
                        struct blame_entry *e)
@@ -573,21 +681,27 @@ static void split_blame(struct scoreboard *sb,
        struct blame_entry *new_entry;
 
        if (split[0].suspect && split[2].suspect) {
-               /* we need to split e into two and add another for parent */
+               /* The first part (reuse storage for the existing entry e) */
                dup_entry(e, &split[0]);
 
+               /* The last part -- me */
                new_entry = xmalloc(sizeof(*new_entry));
                memcpy(new_entry, &(split[2]), sizeof(struct blame_entry));
                add_blame_entry(sb, new_entry);
 
+               /* ... and the middle part -- parent */
                new_entry = xmalloc(sizeof(*new_entry));
                memcpy(new_entry, &(split[1]), sizeof(struct blame_entry));
                add_blame_entry(sb, new_entry);
        }
        else if (!split[0].suspect && !split[2].suspect)
-               /* parent covers the entire area */
+               /*
+                * The parent covers the entire area; reuse storage for
+                * e and replace it with the parent.
+                */
                dup_entry(e, &split[1]);
        else if (split[0].suspect) {
+               /* me and then parent */
                dup_entry(e, &split[0]);
 
                new_entry = xmalloc(sizeof(*new_entry));
@@ -595,6 +709,7 @@ static void split_blame(struct scoreboard *sb,
                add_blame_entry(sb, new_entry);
        }
        else {
+               /* parent and then me */
                dup_entry(e, &split[1]);
 
                new_entry = xmalloc(sizeof(*new_entry));
@@ -625,6 +740,10 @@ static void split_blame(struct scoreboard *sb,
        }
 }
 
+/*
+ * After splitting the blame, the origins used by the
+ * on-stack blame_entry should lose one refcnt each.
+ */
 static void decref_split(struct blame_entry *split)
 {
        int i;
@@ -633,6 +752,10 @@ static void decref_split(struct blame_entry *split)
                origin_decref(split[i].suspect);
 }
 
+/*
+ * Helper for blame_chunk().  blame_entry e is known to overlap with
+ * the patch hunk; split it and pass blame to the parent.
+ */
 static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
                          int tlno, int plno, int same,
                          struct origin *parent)
@@ -645,6 +768,9 @@ static void blame_overlap(struct scoreboard *sb, struct blame_entry *e,
        decref_split(split);
 }
 
+/*
+ * Find the line number of the last line the target is suspected for.
+ */
 static int find_last_in_target(struct scoreboard *sb, struct origin *target)
 {
        struct blame_entry *e;
@@ -659,6 +785,11 @@ static int find_last_in_target(struct scoreboard *sb, struct origin *target)
        return last_in_target;
 }
 
+/*
+ * Process one hunk from the patch between the current suspect for
+ * blame_entry e and its parent.  Find and split the overlap, and
+ * pass blame to the overlapping part to the parent.
+ */
 static void blame_chunk(struct scoreboard *sb,
                        int tlno, int plno, int same,
                        struct origin *target, struct origin *parent)
@@ -675,6 +806,11 @@ static void blame_chunk(struct scoreboard *sb,
        }
 }
 
+/*
+ * We are looking at the origin 'target' and aiming to pass blame
+ * for the lines it is suspected to its parent.  Run diff to find
+ * which lines came from parent and pass blame for them.
+ */
 static int pass_blame_to_parent(struct scoreboard *sb,
                                struct origin *target,
                                struct origin *parent)
@@ -695,13 +831,22 @@ static int pass_blame_to_parent(struct scoreboard *sb,
                plno = chunk->p_next;
                tlno = chunk->t_next;
        }
-       /* rest (i.e. anything above tlno) are the same as parent */
+       /* The rest (i.e. anything after tlno) are the same as the parent */
        blame_chunk(sb, tlno, plno, last_in_target, target, parent);
 
        free_patch(patch);
        return 0;
 }
 
+/*
+ * The lines in blame_entry after splitting blames many times can become
+ * very small and trivial, and at some point it becomes pointless to
+ * blame the parents.  E.g. "\t\t}\n\t}\n\n" appears everywhere in any
+ * ordinary C program, and it is not worth to say it was copied from
+ * totally unrelated file in the parent.
+ *
+ * Compute how trivial the lines in the blame_entry are.
+ */
 static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
 {
        unsigned score;
@@ -723,6 +868,12 @@ static unsigned ent_score(struct scoreboard *sb, struct blame_entry *e)
        return score;
 }
 
+/*
+ * best_so_far[] and this[] are both a split of an existing blame_entry
+ * that passes blame to the parent.  Maintain best_so_far the best split
+ * so far, by comparing this and best_so_far and copying this into
+ * bst_so_far as needed.
+ */
 static void copy_split_if_better(struct scoreboard *sb,
                                 struct blame_entry *best_so_far,
                                 struct blame_entry *this)
@@ -742,6 +893,11 @@ static void copy_split_if_better(struct scoreboard *sb,
        memcpy(best_so_far, this, sizeof(struct blame_entry [3]));
 }
 
+/*
+ * Find the lines from parent that are the same as ent so that
+ * we can pass blames to it.  file_p has the blob contents for
+ * the parent.
+ */
 static void find_copy_in_blob(struct scoreboard *sb,
                              struct blame_entry *ent,
                              struct origin *parent,
@@ -754,6 +910,9 @@ static void find_copy_in_blob(struct scoreboard *sb,
        struct patch *patch;
        int i, plno, tlno;
 
+       /*
+        * Prepare mmfile that contains only the lines in ent.
+        */
        cp = nth_line(sb, ent->lno);
        file_o.ptr = (char*) cp;
        cnt = ent->num_lines;
@@ -789,6 +948,10 @@ static void find_copy_in_blob(struct scoreboard *sb,
        free_patch(patch);
 }
 
+/*
+ * See if lines currently target is suspected for can be attributed to
+ * parent.
+ */
 static int find_move_in_parent(struct scoreboard *sb,
                               struct origin *target,
                               struct origin *parent)
@@ -823,12 +986,15 @@ static int find_move_in_parent(struct scoreboard *sb,
        return 0;
 }
 
-
 struct blame_list {
        struct blame_entry *ent;
        struct blame_entry split[3];
 };
 
+/*
+ * Count the number of entries the target is suspected for,
+ * and prepare a list of entry and the best split.
+ */
 static struct blame_list *setup_blame_list(struct scoreboard *sb,
                                           struct origin *target,
                                           int *num_ents_p)
@@ -837,9 +1003,6 @@ static struct blame_list *setup_blame_list(struct scoreboard *sb,
        int num_ents, i;
        struct blame_list *blame_list = NULL;
 
-       /* Count the number of entries the target is suspected for,
-        * and prepare a list of entry and the best split.
-        */
        for (e = sb->ent, num_ents = 0; e; e = e->next)
                if (!e->guilty && !cmp_suspect(e->suspect, target))
                        num_ents++;
@@ -853,6 +1016,11 @@ static struct blame_list *setup_blame_list(struct scoreboard *sb,
        return blame_list;
 }
 
+/*
+ * For lines target is suspected for, see if we can find code movement
+ * across file boundary from the parent commit.  porigin is the path
+ * in the parent we already tried.
+ */
 static int find_copy_in_parent(struct scoreboard *sb,
                               struct origin *target,
                               struct commit *parent,
@@ -890,9 +1058,12 @@ static int find_copy_in_parent(struct scoreboard *sb,
            (!porigin || strcmp(target->path, porigin->path)))
                diff_opts.find_copies_harder = 1;
 
-       diff_tree_sha1(parent->tree->object.sha1,
-                      target->commit->tree->object.sha1,
-                      "", &diff_opts);
+       if (is_null_sha1(target->commit->object.sha1))
+               do_diff_cache(parent->tree->object.sha1, &diff_opts);
+       else
+               diff_tree_sha1(parent->tree->object.sha1,
+                              target->commit->tree->object.sha1,
+                              "", &diff_opts);
 
        if (!diff_opts.find_copies_harder)
                diffcore_std(&diff_opts);
@@ -953,7 +1124,8 @@ static int find_copy_in_parent(struct scoreboard *sb,
        return retval;
 }
 
-/* The blobs of origin and porigin exactly match, so everything
+/*
+ * The blobs of origin and porigin exactly match, so everything
  * origin is suspected for can be blamed on the parent.
  */
 static void pass_whole_blame(struct scoreboard *sb,
@@ -1038,7 +1210,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
        }
 
        /*
-        * Optionally run "miff" to find moves in parents' files here.
+        * Optionally find moves in parents' files.
         */
        if (opt & PICKAXE_BLAME_MOVE)
                for (i = 0, parent = commit->parents;
@@ -1052,7 +1224,7 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                }
 
        /*
-        * Optionally run "ciff" to find copies from parents' files here.
+        * Optionally find copies from parents' files.
         */
        if (opt & PICKAXE_BLAME_COPY)
                for (i = 0, parent = commit->parents;
@@ -1069,64 +1241,9 @@ static void pass_blame(struct scoreboard *sb, struct origin *origin, int opt)
                origin_decref(parent_origin[i]);
 }
 
-static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
-{
-       while (1) {
-               struct blame_entry *ent;
-               struct commit *commit;
-               struct origin *suspect = NULL;
-
-               /* find one suspect to break down */
-               for (ent = sb->ent; !suspect && ent; ent = ent->next)
-                       if (!ent->guilty)
-                               suspect = ent->suspect;
-               if (!suspect)
-                       return; /* all done */
-
-               origin_incref(suspect);
-               commit = suspect->commit;
-               if (!commit->object.parsed)
-                       parse_commit(commit);
-               if (!(commit->object.flags & UNINTERESTING) &&
-                   !(revs->max_age != -1 && commit->date  < revs->max_age))
-                       pass_blame(sb, suspect, opt);
-
-               /* Take responsibility for the remaining entries */
-               for (ent = sb->ent; ent; ent = ent->next)
-                       if (!cmp_suspect(ent->suspect, suspect))
-                               ent->guilty = 1;
-               origin_decref(suspect);
-
-               if (DEBUG) /* sanity */
-                       sanity_check_refcnt(sb);
-       }
-}
-
-static const char *format_time(unsigned long time, const char *tz_str,
-                              int show_raw_time)
-{
-       static char time_buf[128];
-       time_t t = time;
-       int minutes, tz;
-       struct tm *tm;
-
-       if (show_raw_time) {
-               sprintf(time_buf, "%lu %s", time, tz_str);
-               return time_buf;
-       }
-
-       tz = atoi(tz_str);
-       minutes = tz < 0 ? -tz : tz;
-       minutes = (minutes / 100)*60 + (minutes % 100);
-       minutes = tz < 0 ? -minutes : minutes;
-       t = time + minutes * 60;
-       tm = gmtime(&t);
-
-       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
-       strcat(time_buf, tz_str);
-       return time_buf;
-}
-
+/*
+ * Information on commits, used for output.
+ */
 struct commit_info
 {
        char *author;
@@ -1143,6 +1260,9 @@ struct commit_info
        char *summary;
 };
 
+/*
+ * Parse author/committer line in the commit object buffer
+ */
 static void get_ac_line(const char *inbuf, const char *what,
                        int bufsz, char *person, char **mail,
                        unsigned long *time, char **tz)
@@ -1197,7 +1317,8 @@ static void get_commit_info(struct commit *commit,
        static char committer_buf[1024];
        static char summary_buf[1024];
 
-       /* We've operated without save_commit_buffer, so
+       /*
+        * We've operated without save_commit_buffer, so
         * we now need to populate them for output.
         */
        if (!commit->buffer) {
@@ -1229,14 +1350,135 @@ static void get_commit_info(struct commit *commit,
        tmp += 2;
        endp = strchr(tmp, '\n');
        if (!endp)
-               goto error_out;
+               endp = tmp + strlen(tmp);
        len = endp - tmp;
-       if (len >= sizeof(summary_buf))
+       if (len >= sizeof(summary_buf) || len == 0)
                goto error_out;
        memcpy(summary_buf, tmp, len);
        summary_buf[len] = 0;
 }
 
+/*
+ * To allow LF and other nonportable characters in pathnames,
+ * they are c-style quoted as needed.
+ */
+static void write_filename_info(const char *path)
+{
+       printf("filename ");
+       write_name_quoted(NULL, 0, path, 1, stdout);
+       putchar('\n');
+}
+
+/*
+ * The blame_entry is found to be guilty for the range.  Mark it
+ * as such, and show it in incremental output.
+ */
+static void found_guilty_entry(struct blame_entry *ent)
+{
+       if (ent->guilty)
+               return;
+       ent->guilty = 1;
+       if (incremental) {
+               struct origin *suspect = ent->suspect;
+
+               printf("%s %d %d %d\n",
+                      sha1_to_hex(suspect->commit->object.sha1),
+                      ent->s_lno + 1, ent->lno + 1, ent->num_lines);
+               if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
+                       struct commit_info ci;
+                       suspect->commit->object.flags |= METAINFO_SHOWN;
+                       get_commit_info(suspect->commit, &ci, 1);
+                       printf("author %s\n", ci.author);
+                       printf("author-mail %s\n", ci.author_mail);
+                       printf("author-time %lu\n", ci.author_time);
+                       printf("author-tz %s\n", ci.author_tz);
+                       printf("committer %s\n", ci.committer);
+                       printf("committer-mail %s\n", ci.committer_mail);
+                       printf("committer-time %lu\n", ci.committer_time);
+                       printf("committer-tz %s\n", ci.committer_tz);
+                       printf("summary %s\n", ci.summary);
+                       if (suspect->commit->object.flags & UNINTERESTING)
+                               printf("boundary\n");
+               }
+               write_filename_info(suspect->path);
+       }
+}
+
+/*
+ * The main loop -- while the scoreboard has lines whose true origin
+ * is still unknown, pick one blame_entry, and allow its current
+ * suspect to pass blames to its parents.
+ */
+static void assign_blame(struct scoreboard *sb, struct rev_info *revs, int opt)
+{
+       while (1) {
+               struct blame_entry *ent;
+               struct commit *commit;
+               struct origin *suspect = NULL;
+
+               /* find one suspect to break down */
+               for (ent = sb->ent; !suspect && ent; ent = ent->next)
+                       if (!ent->guilty)
+                               suspect = ent->suspect;
+               if (!suspect)
+                       return; /* all done */
+
+               /*
+                * We will use this suspect later in the loop,
+                * so hold onto it in the meantime.
+                */
+               origin_incref(suspect);
+               commit = suspect->commit;
+               if (!commit->object.parsed)
+                       parse_commit(commit);
+               if (!(commit->object.flags & UNINTERESTING) &&
+                   !(revs->max_age != -1 && commit->date < revs->max_age))
+                       pass_blame(sb, suspect, opt);
+               else {
+                       commit->object.flags |= UNINTERESTING;
+                       if (commit->object.parsed)
+                               mark_parents_uninteresting(commit);
+               }
+               /* treat root commit as boundary */
+               if (!commit->parents && !show_root)
+                       commit->object.flags |= UNINTERESTING;
+
+               /* Take responsibility for the remaining entries */
+               for (ent = sb->ent; ent; ent = ent->next)
+                       if (!cmp_suspect(ent->suspect, suspect))
+                               found_guilty_entry(ent);
+               origin_decref(suspect);
+
+               if (DEBUG) /* sanity */
+                       sanity_check_refcnt(sb);
+       }
+}
+
+static const char *format_time(unsigned long time, const char *tz_str,
+                              int show_raw_time)
+{
+       static char time_buf[128];
+       time_t t = time;
+       int minutes, tz;
+       struct tm *tm;
+
+       if (show_raw_time) {
+               sprintf(time_buf, "%lu %s", time, tz_str);
+               return time_buf;
+       }
+
+       tz = atoi(tz_str);
+       minutes = tz < 0 ? -tz : tz;
+       minutes = (minutes / 100)*60 + (minutes % 100);
+       minutes = tz < 0 ? -minutes : minutes;
+       t = time + minutes * 60;
+       tm = gmtime(&t);
+
+       strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S ", tm);
+       strcat(time_buf, tz_str);
+       return time_buf;
+}
+
 #define OUTPUT_ANNOTATE_COMPAT 001
 #define OUTPUT_LONG_OBJECT_NAME        002
 #define OUTPUT_RAW_TIMESTAMP   004
@@ -1271,11 +1513,13 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
                printf("committer-mail %s\n", ci.committer_mail);
                printf("committer-time %lu\n", ci.committer_time);
                printf("committer-tz %s\n", ci.committer_tz);
-               printf("filename %s\n", suspect->path);
+               write_filename_info(suspect->path);
                printf("summary %s\n", ci.summary);
+               if (suspect->commit->object.flags & UNINTERESTING)
+                       printf("boundary\n");
        }
        else if (suspect->commit->object.flags & MORE_THAN_ONE_PATH)
-               printf("filename %s\n", suspect->path);
+               write_filename_info(suspect->path);
 
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@ -1308,8 +1552,18 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
        cp = nth_line(sb, ent->lno);
        for (cnt = 0; cnt < ent->num_lines; cnt++) {
                char ch;
+               int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8;
+
+               if (suspect->commit->object.flags & UNINTERESTING) {
+                       if (blank_boundary)
+                               memset(hex, ' ', length);
+                       else if (!cmd_is_annotate) {
+                               length--;
+                               putchar('^');
+                       }
+               }
 
-               printf("%.*s", (opt & OUTPUT_LONG_OBJECT_NAME) ? 40 : 8, hex);
+               printf("%.*s", length, hex);
                if (opt & OUTPUT_ANNOTATE_COMPAT)
                        printf("\t(%10s\t%10s\t%d)", ci.author,
                               format_time(ci.author_time, ci.author_tz,
@@ -1370,6 +1624,10 @@ static void output(struct scoreboard *sb, int option)
        }
 }
 
+/*
+ * To allow quick access to the contents of nth line in the
+ * final image, prepare an index in the scoreboard.
+ */
 static int prepare_lines(struct scoreboard *sb)
 {
        const char *buf = sb->final_buf;
@@ -1397,6 +1655,11 @@ static int prepare_lines(struct scoreboard *sb)
        return sb->num_lines;
 }
 
+/*
+ * Add phony grafts for use with -S; this is primarily to
+ * support git-cvsserver that wants to give a linear history
+ * to its clients.
+ */
 static int read_ancestry(const char *graft_file)
 {
        FILE *fp = fopen(graft_file, "r");
@@ -1414,6 +1677,9 @@ static int read_ancestry(const char *graft_file)
        return 0;
 }
 
+/*
+ * How many columns do we need to show line numbers in decimal?
+ */
 static int lineno_width(int lines)
 {
         int i, width;
@@ -1423,6 +1689,10 @@ static int lineno_width(int lines)
         return width;
 }
 
+/*
+ * How many columns do we need to show line numbers, authors,
+ * and filenames?
+ */
 static void find_alignment(struct scoreboard *sb, int *option)
 {
        int longest_src_lines = 0;
@@ -1435,14 +1705,14 @@ static void find_alignment(struct scoreboard *sb, int *option)
                struct commit_info ci;
                int num;
 
+               if (strcmp(suspect->path, sb->path))
+                       *option |= OUTPUT_SHOW_NAME;
+               num = strlen(suspect->path);
+               if (longest_file < num)
+                       longest_file = num;
                if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
                        suspect->commit->object.flags |= METAINFO_SHOWN;
                        get_commit_info(suspect->commit, &ci, 1);
-                       if (strcmp(suspect->path, sb->path))
-                               *option |= OUTPUT_SHOW_NAME;
-                       num = strlen(suspect->path);
-                       if (longest_file < num)
-                               longest_file = num;
                        num = strlen(ci.author);
                        if (longest_author < num)
                                longest_author = num;
@@ -1461,6 +1731,10 @@ static void find_alignment(struct scoreboard *sb, int *option)
        max_score_digits = lineno_width(largest_score);
 }
 
+/*
+ * For debugging -- origin is refcounted, and this asserts that
+ * we do not underflow.
+ */
 static void sanity_check_refcnt(struct scoreboard *sb)
 {
        int baa = 0;
@@ -1482,8 +1756,9 @@ static void sanity_check_refcnt(struct scoreboard *sb)
                        ent->suspect->refcnt = -ent->suspect->refcnt;
        }
        for (ent = sb->ent; ent; ent = ent->next) {
-               /* then pick each and see if they have the the correct
-                * refcnt.
+               /*
+                * ... then pick each and see if they have the the
+                * correct refcnt.
                 */
                int found;
                struct blame_entry *e;
@@ -1513,6 +1788,10 @@ static void sanity_check_refcnt(struct scoreboard *sb)
        }
 }
 
+/*
+ * Used for the command line parsing; check if the path exists
+ * in the working tree.
+ */
 static int has_path_in_work_tree(const char *path)
 {
        struct stat st;
@@ -1535,6 +1814,9 @@ static const char *add_prefix(const char *prefix, const char *path)
        return prefix_path(prefix, strlen(prefix), path);
 }
 
+/*
+ * Parsing of (comma separated) one item in the -L option
+ */
 static const char *parse_loc(const char *spec,
                             struct scoreboard *sb, long lno,
                             long begin, long *ret)
@@ -1609,6 +1891,9 @@ static const char *parse_loc(const char *spec,
        }
 }
 
+/*
+ * Parsing of -L option
+ */
 static void prepare_blame_range(struct scoreboard *sb,
                                const char *bottomtop,
                                long lno,
@@ -1626,6 +1911,150 @@ static void prepare_blame_range(struct scoreboard *sb,
                usage(blame_usage);
 }
 
+static int git_blame_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "blame.showroot")) {
+               show_root = git_config_bool(var, value);
+               return 0;
+       }
+       if (!strcmp(var, "blame.blankboundary")) {
+               blank_boundary = git_config_bool(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
+static struct commit *fake_working_tree_commit(const char *path, const char *contents_from)
+{
+       struct commit *commit;
+       struct origin *origin;
+       unsigned char head_sha1[20];
+       char *buf;
+       const char *ident;
+       int fd;
+       time_t now;
+       unsigned long fin_size;
+       int size, len;
+       struct cache_entry *ce;
+       unsigned mode;
+
+       if (get_sha1("HEAD", head_sha1))
+               die("No such ref: HEAD");
+
+       time(&now);
+       commit = xcalloc(1, sizeof(*commit));
+       commit->parents = xcalloc(1, sizeof(*commit->parents));
+       commit->parents->item = lookup_commit_reference(head_sha1);
+       commit->object.parsed = 1;
+       commit->date = now;
+       commit->object.type = OBJ_COMMIT;
+
+       origin = make_origin(commit, path);
+
+       if (!contents_from || strcmp("-", contents_from)) {
+               struct stat st;
+               const char *read_from;
+
+               if (contents_from) {
+                       if (stat(contents_from, &st) < 0)
+                               die("Cannot stat %s", contents_from);
+                       read_from = contents_from;
+               }
+               else {
+                       if (lstat(path, &st) < 0)
+                               die("Cannot lstat %s", path);
+                       read_from = path;
+               }
+               fin_size = st.st_size;
+               buf = xmalloc(fin_size+1);
+               mode = canon_mode(st.st_mode);
+               switch (st.st_mode & S_IFMT) {
+               case S_IFREG:
+                       fd = open(read_from, O_RDONLY);
+                       if (fd < 0)
+                               die("cannot open %s", read_from);
+                       if (read_in_full(fd, buf, fin_size) != fin_size)
+                               die("cannot read %s", read_from);
+                       break;
+               case S_IFLNK:
+                       if (readlink(read_from, buf, fin_size+1) != fin_size)
+                               die("cannot readlink %s", read_from);
+                       break;
+               default:
+                       die("unsupported file type %s", read_from);
+               }
+       }
+       else {
+               /* Reading from stdin */
+               contents_from = "standard input";
+               buf = NULL;
+               fin_size = 0;
+               mode = 0;
+               while (1) {
+                       ssize_t cnt = 8192;
+                       buf = xrealloc(buf, fin_size + cnt);
+                       cnt = xread(0, buf + fin_size, cnt);
+                       if (cnt < 0)
+                               die("read error %s from stdin",
+                                   strerror(errno));
+                       if (!cnt)
+                               break;
+                       fin_size += cnt;
+               }
+               buf = xrealloc(buf, fin_size + 1);
+       }
+       buf[fin_size] = 0;
+       origin->file.ptr = buf;
+       origin->file.size = fin_size;
+       pretend_sha1_file(buf, fin_size, blob_type, origin->blob_sha1);
+       commit->util = origin;
+
+       /*
+        * Read the current index, replace the path entry with
+        * origin->blob_sha1 without mucking with its mode or type
+        * bits; we are not going to write this index out -- we just
+        * want to run "diff-index --cached".
+        */
+       discard_cache();
+       read_cache();
+
+       len = strlen(path);
+       if (!mode) {
+               int pos = cache_name_pos(path, len);
+               if (0 <= pos)
+                       mode = ntohl(active_cache[pos]->ce_mode);
+               else
+                       /* Let's not bother reading from HEAD tree */
+                       mode = S_IFREG | 0644;
+       }
+       size = cache_entry_size(len);
+       ce = xcalloc(1, size);
+       hashcpy(ce->sha1, origin->blob_sha1);
+       memcpy(ce->name, path, len);
+       ce->ce_flags = create_ce_flags(len, 0);
+       ce->ce_mode = create_ce_mode(mode);
+       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
+
+       /*
+        * We are not going to write this out, so this does not matter
+        * right now, but someday we might optimize diff-index --cached
+        * with cache-tree information.
+        */
+       cache_tree_invalidate_path(active_cache_tree, path);
+
+       commit->buffer = xmalloc(400);
+       ident = fmt_ident("Not Committed Yet", "not.committed.yet", NULL, 0);
+       sprintf(commit->buffer,
+               "tree 0000000000000000000000000000000000000000\n"
+               "parent %s\n"
+               "author %s\n"
+               "committer %s\n\n"
+               "Version of %s from %s\n",
+               sha1_to_hex(head_sha1),
+               ident, ident, path, contents_from ? contents_from : path);
+       return commit;
+}
+
 int cmd_blame(int argc, const char **argv, const char *prefix)
 {
        struct rev_info revs;
@@ -1640,7 +2069,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        const char *final_commit_name = NULL;
        char type[10];
        const char *bottomtop = NULL;
+       const char *contents_from = NULL;
 
+       cmd_is_annotate = !strcmp(argv[0], "annotate");
+
+       git_config(git_blame_config);
        save_commit_buffer = 0;
 
        opt = 0;
@@ -1649,6 +2082,10 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                const char *arg = argv[i];
                if (*arg != '-')
                        break;
+               else if (!strcmp("-b", arg))
+                       blank_boundary = 1;
+               else if (!strcmp("--root", arg))
+                       show_root = 1;
                else if (!strcmp("-c", arg))
                        output_option |= OUTPUT_ANNOTATE_COMPAT;
                else if (!strcmp("-t", arg))
@@ -1679,6 +2116,13 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                                die("More than one '-L n,m' option given");
                        bottomtop = arg;
                }
+               else if (!strcmp("--contents", arg)) {
+                       if (++i >= argc)
+                               usage(blame_usage);
+                       contents_from = argv[i];
+               }
+               else if (!strcmp("--incremental", arg))
+                       incremental = 1;
                else if (!strcmp("--score-debug", arg))
                        output_option |= OUTPUT_SHOW_SCORE;
                else if (!strcmp("-f", arg) ||
@@ -1699,14 +2143,18 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
                        argv[unk++] = arg;
        }
 
+       if (!incremental)
+               setup_pager();
+
        if (!blame_move_score)
                blame_move_score = BLAME_DEFAULT_MOVE_SCORE;
        if (!blame_copy_score)
                blame_copy_score = BLAME_DEFAULT_COPY_SCORE;
 
-       /* We have collected options unknown to us in argv[1..unk]
+       /*
+        * We have collected options unknown to us in argv[1..unk]
         * which are to be passed to revision machinery if we are
-        * going to do the "bottom" procesing.
+        * going to do the "bottom" processing.
         *
         * The remaining are:
         *
@@ -1784,16 +2232,19 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        if (final_commit_name)
                argv[unk++] = final_commit_name;
 
-       /* Now we got rev and path.  We do not want the path pruning
+       /*
+        * Now we got rev and path.  We do not want the path pruning
         * but we may want "bottom" processing.
         */
+       argv[unk++] = "--"; /* terminate the rev name */
        argv[unk] = NULL;
 
        init_revisions(&revs, NULL);
-       setup_revisions(unk, argv, &revs, "HEAD");
+       setup_revisions(unk, argv, &revs, NULL);
        memset(&sb, 0, sizeof(sb));
 
-       /* There must be one and only one positive commit in the
+       /*
+        * There must be one and only one positive commit in the
         * revs->pending array.
         */
        for (i = 0; i < revs.pending.nr; i++) {
@@ -1814,27 +2265,40 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
        }
 
        if (!sb.final) {
-               /* "--not A B -- path" without anything positive */
-               unsigned char head_sha1[20];
-
-               final_commit_name = "HEAD";
-               if (get_sha1(final_commit_name, head_sha1))
-                       die("No such ref: HEAD");
-               sb.final = lookup_commit_reference(head_sha1);
-               add_pending_object(&revs, &(sb.final->object), "HEAD");
+               /*
+                * "--not A B -- path" without anything positive;
+                * do not default to HEAD, but use the working tree
+                * or "--contents".
+                */
+               sb.final = fake_working_tree_commit(path, contents_from);
+               add_pending_object(&revs, &(sb.final->object), ":");
        }
+       else if (contents_from)
+               die("Cannot use --contents with final commit object name");
 
-       /* If we have bottom, this will mark the ancestors of the
+       /*
+        * If we have bottom, this will mark the ancestors of the
         * bottom commits we would reach while traversing as
         * uninteresting.
         */
        prepare_revision_walk(&revs);
 
-       o = get_origin(&sb, sb.final, path);
-       if (fill_blob_sha1(o))
-               die("no such path %s in %s", path, final_commit_name);
+       if (is_null_sha1(sb.final->object.sha1)) {
+               char *buf;
+               o = sb.final->util;
+               buf = xmalloc(o->file.size + 1);
+               memcpy(buf, o->file.ptr, o->file.size + 1);
+               sb.final_buf = buf;
+               sb.final_buf_size = o->file.size;
+       }
+       else {
+               o = get_origin(&sb, sb.final, path);
+               if (fill_blob_sha1(o))
+                       die("no such path %s in %s", path, final_commit_name);
 
-       sb.final_buf = read_sha1_file(o->blob_sha1, type, &sb.final_buf_size);
+               sb.final_buf = read_sha1_file(o->blob_sha1, type,
+                                             &sb.final_buf_size);
+       }
        num_read_blob++;
        lno = prepare_lines(&sb);
 
@@ -1868,6 +2332,9 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 
        assign_blame(&sb, &revs, opt);
 
+       if (incremental)
+               return 0;
+
        coalesce(&sb);
 
        if (!(output_option & OUTPUT_PORCELAIN))
index 368b68ec91f3d72d1f5e1d1dea503334207f864d..d0e720936846355b1ba8937cea9c2a14e5b8856a 100644 (file)
  */
 
 #include "cache.h"
+#include "color.h"
 #include "refs.h"
 #include "commit.h"
 #include "builtin.h"
 
 static const char builtin_branch_usage[] =
-"git-branch (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | [-r]";
+  "git-branch [-r] (-d | -D) <branchname> | [-l] [-f] <branchname> [<start-point>] | (-m | -M) [<oldbranch>] <newbranch> | [--color | --no-color] [-r | -a] [-v [--abbrev=<length>]]";
 
+#define REF_UNKNOWN_TYPE    0x00
+#define REF_LOCAL_BRANCH    0x01
+#define REF_REMOTE_BRANCH   0x02
+#define REF_TAG             0x04
 
 static const char *head;
 static unsigned char head_sha1[20];
 
-static int in_merge_bases(const unsigned char *sha1,
-                         struct commit *rev1,
-                         struct commit *rev2)
+static int branch_use_color;
+static char branch_colors[][COLOR_MAXLEN] = {
+       "\033[m",       /* reset */
+       "",             /* PLAIN (normal) */
+       "\033[31m",     /* REMOTE (red) */
+       "",             /* LOCAL (normal) */
+       "\033[32m",     /* CURRENT (green) */
+};
+enum color_branch {
+       COLOR_BRANCH_RESET = 0,
+       COLOR_BRANCH_PLAIN = 1,
+       COLOR_BRANCH_REMOTE = 2,
+       COLOR_BRANCH_LOCAL = 3,
+       COLOR_BRANCH_CURRENT = 4,
+};
+
+static int parse_branch_color_slot(const char *var, int ofs)
 {
-       struct commit_list *bases, *b;
-       int ret = 0;
+       if (!strcasecmp(var+ofs, "plain"))
+               return COLOR_BRANCH_PLAIN;
+       if (!strcasecmp(var+ofs, "reset"))
+               return COLOR_BRANCH_RESET;
+       if (!strcasecmp(var+ofs, "remote"))
+               return COLOR_BRANCH_REMOTE;
+       if (!strcasecmp(var+ofs, "local"))
+               return COLOR_BRANCH_LOCAL;
+       if (!strcasecmp(var+ofs, "current"))
+               return COLOR_BRANCH_CURRENT;
+       die("bad config variable '%s'", var);
+}
 
-       bases = get_merge_bases(rev1, rev2, 1);
-       for (b = bases; b; b = b->next) {
-               if (!hashcmp(sha1, b->item->object.sha1)) {
-                       ret = 1;
-                       break;
-               }
+int git_branch_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "color.branch")) {
+               branch_use_color = git_config_colorbool(var, value);
+               return 0;
        }
+       if (!strncmp(var, "color.branch.", 13)) {
+               int slot = parse_branch_color_slot(var, 13);
+               color_parse(value, var, branch_colors[slot]);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
 
-       free_commit_list(bases);
-       return ret;
+const char *branch_get_color(enum color_branch ix)
+{
+       if (branch_use_color)
+               return branch_colors[ix];
+       return "";
 }
 
-static void delete_branches(int argc, const char **argv, int force)
+static int delete_branches(int argc, const char **argv, int force, int kinds)
 {
-       struct commit *rev, *head_rev;
+       struct commit *rev, *head_rev = head_rev;
        unsigned char sha1[20];
-       char *name;
+       char *name = NULL;
+       const char *fmt, *remote;
        int i;
+       int ret = 0;
 
-       head_rev = lookup_commit_reference(head_sha1);
+       switch (kinds) {
+       case REF_REMOTE_BRANCH:
+               fmt = "refs/remotes/%s";
+               remote = "remote ";
+               force = 1;
+               break;
+       case REF_LOCAL_BRANCH:
+               fmt = "refs/heads/%s";
+               remote = "";
+               break;
+       default:
+               die("cannot use -a with -d");
+       }
+
+       if (!force) {
+               head_rev = lookup_commit_reference(head_sha1);
+               if (!head_rev)
+                       die("Couldn't look up commit object for HEAD");
+       }
        for (i = 0; i < argc; i++) {
-               if (!strcmp(head, argv[i]))
-                       die("Cannot delete the branch you are currently on.");
+               if (kinds == REF_LOCAL_BRANCH && !strcmp(head, argv[i])) {
+                       error("Cannot delete the branch '%s' "
+                               "which you are currently on.", argv[i]);
+                       ret = 1;
+                       continue;
+               }
 
-               name = xstrdup(mkpath("refs/heads/%s", argv[i]));
-               if (!resolve_ref(name, sha1, 1, NULL))
-                       die("Branch '%s' not found.", argv[i]);
+               if (name)
+                       free(name);
+
+               name = xstrdup(mkpath(fmt, argv[i]));
+               if (!resolve_ref(name, sha1, 1, NULL)) {
+                       error("%sbranch '%s' not found.",
+                                       remote, argv[i]);
+                       ret = 1;
+                       continue;
+               }
 
                rev = lookup_commit_reference(sha1);
-               if (!rev || !head_rev)
-                       die("Couldn't look up commit objects.");
+               if (!rev) {
+                       error("Couldn't look up commit object for '%s'", name);
+                       ret = 1;
+                       continue;
+               }
 
                /* This checks whether the merge bases of branch and
                 * HEAD contains branch -- which means that the HEAD
@@ -62,72 +134,189 @@ static void delete_branches(int argc, const char **argv, int force)
                 */
 
                if (!force &&
-                   !in_merge_bases(sha1, rev, head_rev)) {
-                       fprintf(stderr,
-                               "The branch '%s' is not a strict subset of your current HEAD.\n"
-                               "If you are sure you want to delete it, run 'git branch -D %s'.\n",
-                               argv[i], argv[i]);
-                       exit(1);
+                   !in_merge_bases(rev, &head_rev, 1)) {
+                       error("The branch '%s' is not a strict subset of "
+                               "your current HEAD.\n"
+                               "If you are sure you want to delete it, "
+                               "run 'git branch -D %s'.", argv[i], argv[i]);
+                       ret = 1;
+                       continue;
                }
 
-               if (delete_ref(name, sha1))
-                       printf("Error deleting branch '%s'\n", argv[i]);
-               else
-                       printf("Deleted branch %s.\n", argv[i]);
+               if (delete_ref(name, sha1)) {
+                       error("Error deleting %sbranch '%s'", remote,
+                              argv[i]);
+                       ret = 1;
+               } else
+                       printf("Deleted %sbranch %s.\n", remote, argv[i]);
 
-               free(name);
        }
+
+       if (name)
+               free(name);
+
+       return(ret);
 }
 
-static int ref_index, ref_alloc;
-static char **ref_list;
+struct ref_item {
+       char *name;
+       unsigned int kind;
+       unsigned char sha1[20];
+};
+
+struct ref_list {
+       int index, alloc, maxwidth;
+       struct ref_item *list;
+       int kinds;
+};
 
-static int append_ref(const char *refname, const unsigned char *sha1, int flags,
-               void *cb_data)
+static int append_ref(const char *refname, const unsigned char *sha1, int flags, void *cb_data)
 {
-       if (ref_index >= ref_alloc) {
-               ref_alloc = alloc_nr(ref_alloc);
-               ref_list = xrealloc(ref_list, ref_alloc * sizeof(char *));
+       struct ref_list *ref_list = (struct ref_list*)(cb_data);
+       struct ref_item *newitem;
+       int kind = REF_UNKNOWN_TYPE;
+       int len;
+
+       /* Detect kind */
+       if (!strncmp(refname, "refs/heads/", 11)) {
+               kind = REF_LOCAL_BRANCH;
+               refname += 11;
+       } else if (!strncmp(refname, "refs/remotes/", 13)) {
+               kind = REF_REMOTE_BRANCH;
+               refname += 13;
+       } else if (!strncmp(refname, "refs/tags/", 10)) {
+               kind = REF_TAG;
+               refname += 10;
        }
 
-       ref_list[ref_index++] = xstrdup(refname);
+       /* Don't add types the caller doesn't want */
+       if ((kind & ref_list->kinds) == 0)
+               return 0;
+
+       /* Resize buffer */
+       if (ref_list->index >= ref_list->alloc) {
+               ref_list->alloc = alloc_nr(ref_list->alloc);
+               ref_list->list = xrealloc(ref_list->list,
+                               ref_list->alloc * sizeof(struct ref_item));
+       }
+
+       /* Record the new item */
+       newitem = &(ref_list->list[ref_list->index++]);
+       newitem->name = xstrdup(refname);
+       newitem->kind = kind;
+       hashcpy(newitem->sha1, sha1);
+       len = strlen(newitem->name);
+       if (len > ref_list->maxwidth)
+               ref_list->maxwidth = len;
 
        return 0;
 }
 
+static void free_ref_list(struct ref_list *ref_list)
+{
+       int i;
+
+       for (i = 0; i < ref_list->index; i++)
+               free(ref_list->list[i].name);
+       free(ref_list->list);
+}
+
 static int ref_cmp(const void *r1, const void *r2)
 {
-       return strcmp(*(char **)r1, *(char **)r2);
+       struct ref_item *c1 = (struct ref_item *)(r1);
+       struct ref_item *c2 = (struct ref_item *)(r2);
+
+       if (c1->kind != c2->kind)
+               return c1->kind - c2->kind;
+       return strcmp(c1->name, c2->name);
 }
 
-static void print_ref_list(int remote_only)
+static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
+                          int abbrev, int current)
 {
-       int i;
        char c;
+       int color;
+       struct commit *commit;
+       char subject[256];
+
+       switch (item->kind) {
+       case REF_LOCAL_BRANCH:
+               color = COLOR_BRANCH_LOCAL;
+               break;
+       case REF_REMOTE_BRANCH:
+               color = COLOR_BRANCH_REMOTE;
+               break;
+       default:
+               color = COLOR_BRANCH_PLAIN;
+               break;
+       }
 
-       if (remote_only)
-               for_each_remote_ref(append_ref, NULL);
-       else
-               for_each_branch_ref(append_ref, NULL);
+       c = ' ';
+       if (current) {
+               c = '*';
+               color = COLOR_BRANCH_CURRENT;
+       }
 
-       qsort(ref_list, ref_index, sizeof(char *), ref_cmp);
+       if (verbose) {
+               commit = lookup_commit(item->sha1);
+               if (commit && !parse_commit(commit))
+                       pretty_print_commit(CMIT_FMT_ONELINE, commit, ~0,
+                                           subject, sizeof(subject), 0,
+                                           NULL, NULL, 0);
+               else
+                       strcpy(subject, " **** invalid ref ****");
+               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);
+       } else {
+               printf("%c %s%s%s\n", c, branch_get_color(color), item->name,
+                      branch_get_color(COLOR_BRANCH_RESET));
+       }
+}
 
-       for (i = 0; i < ref_index; i++) {
-               c = ' ';
-               if (!strcmp(ref_list[i], head))
-                       c = '*';
+static void print_ref_list(int kinds, int detached, int verbose, int abbrev)
+{
+       int i;
+       struct ref_list ref_list;
+
+       memset(&ref_list, 0, sizeof(ref_list));
+       ref_list.kinds = kinds;
+       for_each_ref(append_ref, &ref_list);
+
+       qsort(ref_list.list, ref_list.index, sizeof(struct ref_item), ref_cmp);
+
+       detached = (detached && (kinds & REF_LOCAL_BRANCH));
+       if (detached) {
+               struct ref_item item;
+               item.name = "(no branch)";
+               item.kind = REF_LOCAL_BRANCH;
+               hashcpy(item.sha1, head_sha1);
+               if (strlen(item.name) > ref_list.maxwidth)
+                             ref_list.maxwidth = strlen(item.name);
+               print_ref_item(&item, ref_list.maxwidth, verbose, abbrev, 1);
+       }
 
-               printf("%c %s\n", c, ref_list[i]);
+       for (i = 0; i < ref_list.index; i++) {
+               int current = !detached &&
+                       (ref_list.list[i].kind == REF_LOCAL_BRANCH) &&
+                       !strcmp(ref_list.list[i].name, head);
+               print_ref_item(&ref_list.list[i], ref_list.maxwidth, verbose,
+                              abbrev, current);
        }
+
+       free_ref_list(&ref_list);
 }
 
-static void create_branch(const char *name, const char *start,
+static void create_branch(const char *name, const char *start_name,
+                         unsigned char *start_sha1,
                          int force, int reflog)
 {
        struct ref_lock *lock;
        struct commit *commit;
        unsigned char sha1[20];
        char ref[PATH_MAX], msg[PATH_MAX + 20];
+       int forcing = 0;
 
        snprintf(ref, sizeof ref, "refs/heads/%s", name);
        if (check_ref_format(ref))
@@ -136,35 +325,83 @@ static void create_branch(const char *name, const char *start,
        if (resolve_ref(ref, sha1, 1, NULL)) {
                if (!force)
                        die("A branch named '%s' already exists.", name);
-               else if (!strcmp(head, name))
+               else if (!is_bare_repository() && !strcmp(head, name))
                        die("Cannot force update the current branch.");
+               forcing = 1;
        }
 
-       if (get_sha1(start, sha1) ||
-           (commit = lookup_commit_reference(sha1)) == NULL)
-               die("Not a valid branch point: '%s'.", start);
+       if (start_sha1)
+               /* detached HEAD */
+               hashcpy(sha1, start_sha1);
+       else if (get_sha1(start_name, sha1))
+               die("Not a valid object name: '%s'.", start_name);
+
+       if ((commit = lookup_commit_reference(sha1)) == NULL)
+               die("Not a valid branch point: '%s'.", start_name);
        hashcpy(sha1, commit->object.sha1);
 
        lock = lock_any_ref_for_update(ref, NULL);
        if (!lock)
                die("Failed to lock ref for update: %s.", strerror(errno));
 
-       if (reflog) {
+       if (reflog)
                log_all_ref_updates = 1;
-               snprintf(msg, sizeof msg, "branch: Created from %s", start);
-       }
+
+       if (forcing)
+               snprintf(msg, sizeof msg, "branch: Reset from %s",
+                        start_name);
+       else
+               snprintf(msg, sizeof msg, "branch: Created from %s",
+                        start_name);
 
        if (write_ref_sha1(lock, sha1, msg) < 0)
                die("Failed to write ref: %s.", strerror(errno));
 }
 
+static void rename_branch(const char *oldname, const char *newname, int force)
+{
+       char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
+       unsigned char sha1[20];
+
+       if (!oldname)
+               die("cannot rename the current branch while not on any.");
+
+       if (snprintf(oldref, sizeof(oldref), "refs/heads/%s", oldname) > sizeof(oldref))
+               die("Old branchname too long");
+
+       if (check_ref_format(oldref))
+               die("Invalid branch name: %s", oldref);
+
+       if (snprintf(newref, sizeof(newref), "refs/heads/%s", newname) > sizeof(newref))
+               die("New branchname too long");
+
+       if (check_ref_format(newref))
+               die("Invalid branch name: %s", newref);
+
+       if (resolve_ref(newref, sha1, 1, NULL) && !force)
+               die("A branch named '%s' already exists.", newname);
+
+       snprintf(logmsg, sizeof(logmsg), "Branch: renamed %s to %s",
+                oldref, newref);
+
+       if (rename_ref(oldref, newref, logmsg))
+               die("Branch rename failed");
+
+       /* no need to pass logmsg here as HEAD didn't really move */
+       if (!strcmp(oldname, head) && create_symref("HEAD", newref, NULL))
+               die("Branch renamed to %s, but HEAD is not updated!", newname);
+}
+
 int cmd_branch(int argc, const char **argv, const char *prefix)
 {
-       int delete = 0, force_delete = 0, force_create = 0, remote_only = 0;
+       int delete = 0, force_delete = 0, force_create = 0;
+       int rename = 0, force_rename = 0;
+       int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
        int reflog = 0;
+       int kinds = REF_LOCAL_BRANCH;
        int i;
 
-       git_config(git_default_config);
+       git_config(git_branch_config);
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -188,32 +425,74 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
                        force_create = 1;
                        continue;
                }
+               if (!strcmp(arg, "-m")) {
+                       rename = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "-M")) {
+                       rename = 1;
+                       force_rename = 1;
+                       continue;
+               }
                if (!strcmp(arg, "-r")) {
-                       remote_only = 1;
+                       kinds = REF_REMOTE_BRANCH;
+                       continue;
+               }
+               if (!strcmp(arg, "-a")) {
+                       kinds = REF_REMOTE_BRANCH | REF_LOCAL_BRANCH;
                        continue;
                }
                if (!strcmp(arg, "-l")) {
                        reflog = 1;
                        continue;
                }
+               if (!strncmp(arg, "--abbrev=", 9)) {
+                       abbrev = atoi(arg+9);
+                       continue;
+               }
+               if (!strcmp(arg, "-v")) {
+                       verbose = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--color")) {
+                       branch_use_color = 1;
+                       continue;
+               }
+               if (!strcmp(arg, "--no-color")) {
+                       branch_use_color = 0;
+                       continue;
+               }
                usage(builtin_branch_usage);
        }
 
+       if ((delete && rename) || (delete && force_create) ||
+           (rename && force_create))
+               usage(builtin_branch_usage);
+
        head = xstrdup(resolve_ref("HEAD", head_sha1, 0, NULL));
        if (!head)
                die("Failed to resolve HEAD as a valid ref.");
-       if (strncmp(head, "refs/heads/", 11))
-               die("HEAD not found below refs/heads!");
-       head += 11;
+       if (!strcmp(head, "HEAD")) {
+               detached = 1;
+       }
+       else {
+               if (strncmp(head, "refs/heads/", 11))
+                       die("HEAD not found below refs/heads!");
+               head += 11;
+       }
 
        if (delete)
-               delete_branches(argc - i, argv + i, force_delete);
+               return delete_branches(argc - i, argv + i, force_delete, kinds);
        else if (i == argc)
-               print_ref_list(remote_only);
+               print_ref_list(kinds, detached, verbose, abbrev);
+       else if (rename && (i == argc - 1))
+               rename_branch(head, argv[i], force_rename);
+       else if (rename && (i == argc - 2))
+               rename_branch(argv[i], argv[i + 1], force_rename);
        else if (i == argc - 1)
-               create_branch(argv[i], head, force_create, reflog);
+               create_branch(argv[i], head, head_sha1, force_create, reflog);
        else if (i == argc - 2)
-               create_branch(argv[i], argv[i + 1], force_create, reflog);
+               create_branch(argv[i], argv[i+1], NULL, force_create, reflog);
        else
                usage(builtin_branch_usage);
 
index e2e690a1ae89b47f595288e452da10113f7b2e8f..2a818a0a2c218b5b98d82ab0d8be0c65c1edf9c0 100644 (file)
@@ -7,6 +7,7 @@
 #include "commit.h"
 #include "tree.h"
 #include "builtin.h"
+#include "utf8.h"
 
 #define BLOCKING (1ul << 14)
 
@@ -32,7 +33,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
        len = vsnprintf(one_line, sizeof(one_line), fmt, args);
        va_end(args);
        size = *sizep;
-       newsize = size + len;
+       newsize = size + len + 1;
        alloc = (size + 32767) & ~32767;
        buf = *bufp;
        if (newsize > alloc) {
@@ -40,7 +41,7 @@ static void add_buffer(char **bufp, unsigned int *sizep, const char *fmt, ...)
                buf = xrealloc(buf, alloc);
                *bufp = buf;
        }
-       *sizep = newsize;
+       *sizep = newsize - 1;
        memcpy(buf + size, one_line, len);
 }
 
@@ -77,6 +78,11 @@ static int new_parent(int idx)
        return 1;
 }
 
+static const char commit_utf8_warn[] =
+"Warning: commit message does not conform to UTF-8.\n"
+"You may want to amend it after fixing the message, or set the config\n"
+"variable i18n.commitencoding to the encoding your project uses.\n";
+
 int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 {
        int i;
@@ -86,8 +92,8 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
        char comment[1000];
        char *buffer;
        unsigned int size;
+       int encoding_is_utf8;
 
-       setup_ident();
        git_config(git_default_config);
 
        if (argc < 2)
@@ -101,14 +107,18 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
                a = argv[i]; b = argv[i+1];
                if (!b || strcmp(a, "-p"))
                        usage(commit_tree_usage);
+
+               if (parents >= MAXPARENT)
+                       die("Too many parents (%d max)", MAXPARENT);
                if (get_sha1(b, parent_sha1[parents]))
                        die("Not a valid object name %s", b);
                check_valid(parent_sha1[parents], commit_type);
                if (new_parent(parents))
                        parents++;
        }
-       if (!parents)
-               fprintf(stderr, "Committing initial tree %s\n", argv[1]);
+
+       /* Not having i18n.commitencoding is the same as having utf-8 */
+       encoding_is_utf8 = is_encoding_utf8(git_commit_encoding);
 
        init_buffer(&buffer, &size);
        add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1));
@@ -123,12 +133,21 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)
 
        /* Person/date information */
        add_buffer(&buffer, &size, "author %s\n", git_author_info(1));
-       add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1));
+       add_buffer(&buffer, &size, "committer %s\n", git_committer_info(1));
+       if (!encoding_is_utf8)
+               add_buffer(&buffer, &size,
+                               "encoding %s\n", git_commit_encoding);
+       add_buffer(&buffer, &size, "\n");
 
        /* And add the comment */
        while (fgets(comment, sizeof(comment), stdin) != NULL)
                add_buffer(&buffer, &size, "%s", comment);
 
+       /* And check the encoding */
+       buffer[size] = '\0';
+       if (encoding_is_utf8 && !is_utf8(buffer))
+               fprintf(stderr, commit_utf8_warn);
+
        if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) {
                printf("%s\n", sha1_to_hex(commit_sha1));
                return 0;
similarity index 85%
rename from builtin-repo-config.c
rename to builtin-config.c
index 7b6e5725ae6d33350a9648e57139b646ab49696e..0f9051da17c6e399709bd476ab867f4505ecba63 100644 (file)
@@ -1,9 +1,8 @@
 #include "builtin.h"
 #include "cache.h"
-#include <regex.h>
 
 static const char git_config_set_usage[] =
-"git-repo-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list";
+"git-config [ --global ] [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --add | --unset | --unset-all] name [value [value_regex]] | --rename-section old_name new_name | --list";
 
 static char *key;
 static regex_t *key_regexp;
@@ -67,10 +66,10 @@ static int get_value(const char* key_, const char* regex_)
        char *global = NULL, *repo_config = NULL;
        const char *local;
 
-       local = getenv("GIT_CONFIG");
+       local = getenv(CONFIG_ENVIRONMENT);
        if (!local) {
                const char *home = getenv("HOME");
-               local = getenv("GIT_CONFIG_LOCAL");
+               local = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!local)
                        local = repo_config = xstrdup(git_path("config"));
                if (home)
@@ -127,7 +126,7 @@ free_strings:
        return ret;
 }
 
-int cmd_repo_config(int argc, const char **argv, const char *prefix)
+int cmd_config(int argc, const char **argv, const char *prefix)
 {
        int nongit = 0;
        setup_git_directory_gently(&nongit);
@@ -148,6 +147,18 @@ int cmd_repo_config(int argc, const char **argv, const char *prefix)
                        } else {
                                die("$HOME not set");
                        }
+               } else if (!strcmp(argv[1], "--rename-section")) {
+                       int ret;
+                       if (argc != 4)
+                               usage(git_config_set_usage);
+                       ret = git_config_rename_section(argv[2], argv[3]);
+                       if (ret < 0)
+                               return ret;
+                       if (ret == 0) {
+                               fprintf(stderr, "No such section!\n");
+                               return 1;
+                       }
+                       return 0;
                } else
                        break;
                argc--;
@@ -190,7 +201,9 @@ int cmd_repo_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], "--replace-all"))
+               } 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
index 73c59824238f58c2dc94544ef67ea51741d834ac..f5b22bb80e23aef6acde2533701b7b56150948b6 100644 (file)
@@ -105,16 +105,19 @@ int cmd_count_objects(int ac, const char **av, const char *prefix)
        }
        if (verbose) {
                struct packed_git *p;
+               unsigned long num_pack = 0;
                if (!packed_git)
                        prepare_packed_git();
                for (p = packed_git; p; p = p->next) {
                        if (!p->pack_local)
                                continue;
                        packed += num_packed_objects(p);
+                       num_pack++;
                }
                printf("count: %lu\n", loose);
                printf("size: %lu\n", loose_size / 2);
                printf("in-pack: %lu\n", packed);
+               printf("packs: %lu\n", num_pack);
                printf("prune-packable: %lu\n", packed_loose);
                printf("garbage: %lu\n", garbage);
        }
diff --git a/builtin-describe.c b/builtin-describe.c
new file mode 100644 (file)
index 0000000..bcc6456
--- /dev/null
@@ -0,0 +1,284 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+#include "refs.h"
+#include "builtin.h"
+
+#define SEEN           (1u<<0)
+#define MAX_TAGS       (FLAG_BITS - 1)
+
+static const char describe_usage[] =
+"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*";
+
+static int debug;      /* Display lots of verbose info */
+static int all;        /* Default to annotated tags only */
+static int tags;       /* But allow any tags if --tags is specified */
+static int abbrev = DEFAULT_ABBREV;
+static int max_candidates = 10;
+
+struct commit_name {
+       int prio; /* annotated tag = 2, tag = 1, head = 0 */
+       char path[FLEX_ARRAY]; /* more */
+};
+static const char *prio_names[] = {
+       "head", "lightweight", "annotated",
+};
+
+static void add_to_known_names(const char *path,
+                              struct commit *commit,
+                              int prio)
+{
+       struct commit_name *e = commit->util;
+       if (!e || e->prio < prio) {
+               size_t len = strlen(path)+1;
+               free(e);
+               e = xmalloc(sizeof(struct commit_name) + len);
+               e->prio = prio;
+               memcpy(e->path, path, len);
+               commit->util = e;
+       }
+}
+
+static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+       struct object *object;
+       int prio;
+
+       if (!commit)
+               return 0;
+       object = parse_object(sha1);
+       /* If --all, then any refs are used.
+        * If --tags, then any tags are used.
+        * Otherwise only annotated tags are used.
+        */
+       if (!strncmp(path, "refs/tags/", 10)) {
+               if (object->type == OBJ_TAG)
+                       prio = 2;
+               else
+                       prio = 1;
+       }
+       else
+               prio = 0;
+
+       if (!all) {
+               if (!prio)
+                       return 0;
+               if (!tags && prio < 2)
+                       return 0;
+       }
+       add_to_known_names(all ? path + 5 : path + 10, commit, prio);
+       return 0;
+}
+
+struct possible_tag {
+       struct commit_name *name;
+       int depth;
+       int found_order;
+       unsigned flag_within;
+};
+
+static int compare_pt(const void *a_, const void *b_)
+{
+       struct possible_tag *a = (struct possible_tag *)a_;
+       struct possible_tag *b = (struct possible_tag *)b_;
+       if (a->name->prio != b->name->prio)
+               return b->name->prio - a->name->prio;
+       if (a->depth != b->depth)
+               return a->depth - b->depth;
+       if (a->found_order != b->found_order)
+               return a->found_order - b->found_order;
+       return 0;
+}
+
+static unsigned long finish_depth_computation(
+       struct commit_list **list,
+       struct possible_tag *best)
+{
+       unsigned long seen_commits = 0;
+       while (*list) {
+               struct commit *c = pop_commit(list);
+               struct commit_list *parents = c->parents;
+               seen_commits++;
+               if (c->object.flags & best->flag_within) {
+                       struct commit_list *a = *list;
+                       while (a) {
+                               struct commit *i = a->item;
+                               if (!(i->object.flags & best->flag_within))
+                                       break;
+                               a = a->next;
+                       }
+                       if (!a)
+                               break;
+               } else
+                       best->depth++;
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parse_commit(p);
+                       if (!(p->object.flags & SEEN))
+                               insert_by_date(p, list);
+                       p->object.flags |= c->object.flags;
+                       parents = parents->next;
+               }
+       }
+       return seen_commits;
+}
+
+static void describe(const char *arg, int last_one)
+{
+       unsigned char sha1[20];
+       struct commit *cmit, *gave_up_on = NULL;
+       struct commit_list *list;
+       static int initialized = 0;
+       struct commit_name *n;
+       struct possible_tag all_matches[MAX_TAGS];
+       unsigned int match_cnt = 0, annotated_cnt = 0, cur_match;
+       unsigned long seen_commits = 0;
+
+       if (get_sha1(arg, sha1))
+               die("Not a valid object name %s", arg);
+       cmit = lookup_commit_reference(sha1);
+       if (!cmit)
+               die("%s is not a valid '%s' object", arg, commit_type);
+
+       if (!initialized) {
+               initialized = 1;
+               for_each_ref(get_name, NULL);
+       }
+
+       n = cmit->util;
+       if (n) {
+               printf("%s\n", n->path);
+               return;
+       }
+
+       if (debug)
+               fprintf(stderr, "searching to describe %s\n", arg);
+
+       list = NULL;
+       cmit->object.flags = SEEN;
+       commit_list_insert(cmit, &list);
+       while (list) {
+               struct commit *c = pop_commit(&list);
+               struct commit_list *parents = c->parents;
+               seen_commits++;
+               n = c->util;
+               if (n) {
+                       if (match_cnt < max_candidates) {
+                               struct possible_tag *t = &all_matches[match_cnt++];
+                               t->name = n;
+                               t->depth = seen_commits - 1;
+                               t->flag_within = 1u << match_cnt;
+                               t->found_order = match_cnt;
+                               c->object.flags |= t->flag_within;
+                               if (n->prio == 2)
+                                       annotated_cnt++;
+                       }
+                       else {
+                               gave_up_on = c;
+                               break;
+                       }
+               }
+               for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+                       struct possible_tag *t = &all_matches[cur_match];
+                       if (!(c->object.flags & t->flag_within))
+                               t->depth++;
+               }
+               if (annotated_cnt && !list) {
+                       if (debug)
+                               fprintf(stderr, "finished search at %s\n",
+                                       sha1_to_hex(c->object.sha1));
+                       break;
+               }
+               while (parents) {
+                       struct commit *p = parents->item;
+                       parse_commit(p);
+                       if (!(p->object.flags & SEEN))
+                               insert_by_date(p, &list);
+                       p->object.flags |= c->object.flags;
+                       parents = parents->next;
+               }
+       }
+
+       if (!match_cnt)
+               die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
+
+       qsort(all_matches, match_cnt, sizeof(all_matches[0]), compare_pt);
+
+       if (gave_up_on) {
+               insert_by_date(gave_up_on, &list);
+               seen_commits--;
+       }
+       seen_commits += finish_depth_computation(&list, &all_matches[0]);
+       free_commit_list(list);
+
+       if (debug) {
+               for (cur_match = 0; cur_match < match_cnt; cur_match++) {
+                       struct possible_tag *t = &all_matches[cur_match];
+                       fprintf(stderr, " %-11s %8d %s\n",
+                               prio_names[t->name->prio],
+                               t->depth, t->name->path);
+               }
+               fprintf(stderr, "traversed %lu commits\n", seen_commits);
+               if (gave_up_on) {
+                       fprintf(stderr,
+                               "more than %i tags found; listed %i most recent\n"
+                               "gave up search at %s\n",
+                               max_candidates, max_candidates,
+                               sha1_to_hex(gave_up_on->object.sha1));
+               }
+       }
+       if (abbrev == 0)
+               printf("%s\n", all_matches[0].name->path );
+       else
+               printf("%s-%d-g%s\n", all_matches[0].name->path,
+                      all_matches[0].depth,
+                      find_unique_abbrev(cmit->object.sha1, abbrev));
+
+       if (!last_one)
+               clear_commit_marks(cmit, -1);
+}
+
+int cmd_describe(int argc, const char **argv, const char *prefix)
+{
+       int i;
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+
+               if (*arg != '-')
+                       break;
+               else if (!strcmp(arg, "--debug"))
+                       debug = 1;
+               else if (!strcmp(arg, "--all"))
+                       all = 1;
+               else if (!strcmp(arg, "--tags"))
+                       tags = 1;
+               else if (!strncmp(arg, "--abbrev=", 9)) {
+                       abbrev = strtoul(arg + 9, NULL, 10);
+                       if (abbrev != 0 && (abbrev < MINIMUM_ABBREV || 40 < abbrev))
+                               abbrev = DEFAULT_ABBREV;
+               }
+               else if (!strncmp(arg, "--candidates=", 13)) {
+                       max_candidates = strtoul(arg + 13, NULL, 10);
+                       if (max_candidates < 1)
+                               max_candidates = 1;
+                       else if (max_candidates > MAX_TAGS)
+                               max_candidates = MAX_TAGS;
+               }
+               else
+                       usage(describe_usage);
+       }
+
+       save_commit_buffer = 0;
+
+       if (argc <= i)
+               describe("HEAD", 1);
+       else
+               while (i < argc) {
+                       describe(argv[i], (i == argc - 1));
+                       i++;
+               }
+
+       return 0;
+}
diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c
deleted file mode 100644 (file)
index 70bb898..0000000
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (c) 2005 Junio C Hamano
- */
-
-#include "cache.h"
-#include "diff.h"
-#include "builtin.h"
-
-static struct diff_options diff_options;
-
-static const char diff_stages_usage[] =
-"git-diff-stages [<common diff options>] <stage1> <stage2> [<path>...]"
-COMMON_DIFF_OPTIONS_HELP;
-
-static void diff_stages(int stage1, int stage2, const char **pathspec)
-{
-       int i = 0;
-       while (i < active_nr) {
-               struct cache_entry *ce, *stages[4] = { NULL, };
-               struct cache_entry *one, *two;
-               const char *name;
-               int len, skip;
-
-               ce = active_cache[i];
-               skip = !ce_path_match(ce, pathspec);
-               len = ce_namelen(ce);
-               name = ce->name;
-               for (;;) {
-                       int stage = ce_stage(ce);
-                       stages[stage] = ce;
-                       if (active_nr <= ++i)
-                               break;
-                       ce = active_cache[i];
-                       if (ce_namelen(ce) != len ||
-                           memcmp(name, ce->name, len))
-                               break;
-               }
-               one = stages[stage1];
-               two = stages[stage2];
-
-               if (skip || (!one && !two))
-                       continue;
-               if (!one)
-                       diff_addremove(&diff_options, '+', ntohl(two->ce_mode),
-                                      two->sha1, name, NULL);
-               else if (!two)
-                       diff_addremove(&diff_options, '-', ntohl(one->ce_mode),
-                                      one->sha1, name, NULL);
-               else if (hashcmp(one->sha1, two->sha1) ||
-                        (one->ce_mode != two->ce_mode) ||
-                        diff_options.find_copies_harder)
-                       diff_change(&diff_options,
-                                   ntohl(one->ce_mode), ntohl(two->ce_mode),
-                                   one->sha1, two->sha1, name, NULL);
-       }
-}
-
-int cmd_diff_stages(int ac, const char **av, const char *prefix)
-{
-       int stage1, stage2;
-       const char **pathspec = NULL;
-
-       git_config(git_default_config); /* no "diff" UI options */
-       read_cache();
-       diff_setup(&diff_options);
-       while (1 < ac && av[1][0] == '-') {
-               const char *arg = av[1];
-               if (!strcmp(arg, "-r"))
-                       ; /* as usual */
-               else {
-                       int diff_opt_cnt;
-                       diff_opt_cnt = diff_opt_parse(&diff_options,
-                                                     av+1, ac-1);
-                       if (diff_opt_cnt < 0)
-                               usage(diff_stages_usage);
-                       else if (diff_opt_cnt) {
-                               av += diff_opt_cnt;
-                               ac -= diff_opt_cnt;
-                               continue;
-                       }
-                       else
-                               usage(diff_stages_usage);
-               }
-               ac--; av++;
-       }
-
-       if (!diff_options.output_format)
-               diff_options.output_format = DIFF_FORMAT_RAW;
-
-       if (ac < 3 ||
-           sscanf(av[1], "%d", &stage1) != 1 ||
-           ! (0 <= stage1 && stage1 <= 3) ||
-           sscanf(av[2], "%d", &stage2) != 1 ||
-           ! (0 <= stage2 && stage2 <= 3))
-               usage(diff_stages_usage);
-
-       av += 3; /* The rest from av[0] are for paths restriction. */
-       pathspec = get_pathspec(prefix, av);
-
-       if (diff_setup_done(&diff_options) < 0)
-               usage(diff_stages_usage);
-
-       diff_stages(stage1, stage2, pathspec);
-       diffcore_std(&diff_options);
-       diff_flush(&diff_options);
-       return 0;
-}
index 227aa3cd7f887ad44fd6b885479ef2afb586ded4..16c785f0477f2688632f3b67e39083f60e48b2db 100644 (file)
@@ -6,13 +6,13 @@
 #include "tree.h"
 #include "blob.h"
 #include "quote.h"
-#include <fnmatch.h>
 
 /* Quoting styles */
 #define QUOTE_NONE 0
 #define QUOTE_SHELL 1
 #define QUOTE_PERL 2
 #define QUOTE_PYTHON 3
+#define QUOTE_TCL 4
 
 typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 
@@ -135,7 +135,7 @@ static const char *find_next(const char *cp)
        while (*cp) {
                if (*cp == '%') {
                        /* %( is the start of an atom;
-                        * %% is a quoteed per-cent.
+                        * %% is a quoted per-cent.
                         */
                        if (cp[1] == '(')
                                return cp;
@@ -724,6 +724,9 @@ static void print_value(struct refinfo *ref, int atom, int quote_style)
        case QUOTE_PYTHON:
                python_quote_print(stdout, v->s);
                break;
+       case QUOTE_TCL:
+               tcl_quote_print(stdout, v->s);
+               break;
        }
 }
 
@@ -835,6 +838,12 @@ int cmd_for_each_ref(int ac, const char **av, char *prefix)
                        quote_style = QUOTE_PYTHON;
                        continue;
                }
+               if (!strcmp(arg, "--tcl") ) {
+                       if (0 <= quote_style)
+                               die("more than one quoting style?");
+                       quote_style = QUOTE_TCL;
+                       continue;
+               }
                if (!strncmp(arg, "--count=", 8)) {
                        if (maxcount)
                                die("more than one --count?");
similarity index 79%
rename from fsck-objects.c
rename to builtin-fsck.c
index 46b628cb94375e3f645f868efb04547ffc20e6e7..6da3814d59a2c04cbcc9853035f5d72c0a3a4928 100644 (file)
@@ -1,6 +1,3 @@
-#include <sys/types.h>
-#include <dirent.h>
-
 #include "cache.h"
 #include "commit.h"
 #include "tree.h"
@@ -57,6 +54,99 @@ static int objwarning(struct object *obj, const char *err, ...)
        return -1;
 }
 
+/*
+ * Check a single reachable object
+ */
+static void check_reachable_object(struct object *obj)
+{
+       const struct object_refs *refs;
+
+       /*
+        * We obviously want the object to be parsed,
+        * except if it was in a pack-file and we didn't
+        * do a full fsck
+        */
+       if (!obj->parsed) {
+               if (has_sha1_file(obj->sha1))
+                       return; /* it is in pack - forget about it */
+               printf("missing %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+               return;
+       }
+
+       /*
+        * Check that everything that we try to reference is also good.
+        */
+       refs = lookup_object_refs(obj);
+       if (refs) {
+               unsigned j;
+               for (j = 0; j < refs->count; j++) {
+                       struct object *ref = refs->ref[j];
+                       if (ref->parsed ||
+                           (has_sha1_file(ref->sha1)))
+                               continue;
+                       printf("broken link from %7s %s\n",
+                              typename(obj->type), sha1_to_hex(obj->sha1));
+                       printf("              to %7s %s\n",
+                              typename(ref->type), sha1_to_hex(ref->sha1));
+               }
+       }
+}
+
+/*
+ * Check a single unreachable object
+ */
+static void check_unreachable_object(struct object *obj)
+{
+       /*
+        * Missing unreachable object? Ignore it. It's not like
+        * we miss it (since it can't be reached), nor do we want
+        * to complain about it being unreachable (since it does
+        * not exist).
+        */
+       if (!obj->parsed)
+               return;
+
+       /*
+        * Unreachable object that exists? Show it if asked to,
+        * since this is something that is prunable.
+        */
+       if (show_unreachable) {
+               printf("unreachable %s %s\n", typename(obj->type), sha1_to_hex(obj->sha1));
+               return;
+       }
+
+       /*
+        * "!used" means that nothing at all points to it, including
+        * other unreachable objects. In other words, it's the "tip"
+        * of some set of unreachable objects, usually a commit that
+        * got dropped.
+        *
+        * Such starting points are more interesting than some random
+        * set of unreachable objects, so we show them even if the user
+        * hasn't asked for _all_ unreachable objects. If you have
+        * deleted a branch by mistake, this is a prime candidate to
+        * start looking at, for example.
+        */
+       if (!obj->used) {
+               printf("dangling %s %s\n", typename(obj->type),
+                      sha1_to_hex(obj->sha1));
+               return;
+       }
+
+       /*
+        * Otherwise? It's there, it's unreachable, and some other unreachable
+        * object points to it. Ignore it - it's not interesting, and we showed
+        * all the interesting cases above.
+        */
+}
+
+static void check_object(struct object *obj)
+{
+       if (obj->flags & REACHABLE)
+               check_reachable_object(obj);
+       else
+               check_unreachable_object(obj);
+}
 
 static void check_connectivity(void)
 {
@@ -65,46 +155,10 @@ static void check_connectivity(void)
        /* Look up all the requirements, warn about missing objects.. */
        max = get_max_object_index();
        for (i = 0; i < max; i++) {
-               const struct object_refs *refs;
                struct object *obj = get_indexed_object(i);
 
-               if (!obj)
-                       continue;
-
-               if (!obj->parsed) {
-                       if (has_sha1_file(obj->sha1))
-                               ; /* it is in pack */
-                       else
-                               printf("missing %s %s\n",
-                                      typename(obj->type), sha1_to_hex(obj->sha1));
-                       continue;
-               }
-
-               refs = lookup_object_refs(obj);
-               if (refs) {
-                       unsigned j;
-                       for (j = 0; j < refs->count; j++) {
-                               struct object *ref = refs->ref[j];
-                               if (ref->parsed ||
-                                   (has_sha1_file(ref->sha1)))
-                                       continue;
-                               printf("broken link from %7s %s\n",
-                                      typename(obj->type), sha1_to_hex(obj->sha1));
-                               printf("              to %7s %s\n",
-                                      typename(ref->type), sha1_to_hex(ref->sha1));
-                       }
-               }
-
-               if (show_unreachable && !(obj->flags & REACHABLE)) {
-                       printf("unreachable %s %s\n",
-                              typename(obj->type), sha1_to_hex(obj->sha1));
-                       continue;
-               }
-
-               if (!obj->used) {
-                       printf("dangling %s %s\n", typename(obj->type),
-                              sha1_to_hex(obj->sha1));
-               }
+               if (obj)
+                       check_object(obj);
        }
 }
 
@@ -293,7 +347,7 @@ static int fsck_sha1(unsigned char *sha1)
 {
        struct object *obj = parse_object(sha1);
        if (!obj)
-               return error("%s: object not found", sha1_to_hex(sha1));
+               return error("%s: object corrupt or missing", sha1_to_hex(sha1));
        if (obj->flags & SEEN)
                return 0;
        obj->flags |= SEEN;
@@ -402,6 +456,33 @@ static void fsck_dir(int i, char *path)
 
 static int default_refs;
 
+static int fsck_handle_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct object *obj;
+
+       if (!is_null_sha1(osha1)) {
+               obj = lookup_object(osha1);
+               if (obj) {
+                       obj->used = 1;
+                       mark_reachable(obj, REACHABLE);
+               }
+       }
+       obj = lookup_object(nsha1);
+       if (obj) {
+               obj->used = 1;
+               mark_reachable(obj, REACHABLE);
+       }
+       return 0;
+}
+
+static int fsck_handle_reflog(const char *logname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       for_each_reflog_ent(logname, fsck_handle_reflog_ent, NULL);
+       return 0;
+}
+
 static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *obj;
@@ -419,12 +500,14 @@ static int fsck_handle_ref(const char *refname, const unsigned char *sha1, int f
        default_refs++;
        obj->used = 1;
        mark_reachable(obj, REACHABLE);
+
        return 0;
 }
 
 static void get_default_heads(void)
 {
        for_each_ref(fsck_handle_ref, NULL);
+       for_each_reflog(fsck_handle_reflog, NULL);
 
        /*
         * Not having any default heads isn't really fatal, but
@@ -493,12 +576,11 @@ static int fsck_cache_tree(struct cache_tree *it)
        return err;
 }
 
-int main(int argc, char **argv)
+int cmd_fsck(int argc, char **argv, const char *prefix)
 {
        int i, heads;
 
        track_object_refs = 1;
-       setup_git_directory();
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -528,7 +610,7 @@ int main(int argc, char **argv)
                        continue;
                }
                if (*arg == '-')
-                       usage("git-fsck-objects [--tags] [--root] [[--unreachable] [--cache] [--full] [--strict] <head-sha1>*]");
+                       usage("git-fsck [--tags] [--root] [[--unreachable] [--cache] [--full] [--strict] <head-sha1>*]");
        }
 
        fsck_head_link();
index ad7dc00cde4e8e08ef35b313525781c135357df3..2bfbdb71407dcd0ddfe970f6a614158d954cd4c0 100644 (file)
 #include "tag.h"
 #include "tree-walk.h"
 #include "builtin.h"
-#include <regex.h>
 #include "grep.h"
-#include <fnmatch.h>
-#include <sys/wait.h>
 
 /*
  * git grep pathspecs are somewhat different from diff-tree pathspecs;
@@ -139,7 +136,7 @@ static int grep_file(struct grep_opt *opt, const char *filename)
        if (i < 0)
                goto err_ret;
        data = xmalloc(st.st_size + 1);
-       if (st.st_size != xread(i, data, st.st_size)) {
+       if (st.st_size != read_in_full(i, data, st.st_size)) {
                error("'%s': short read %s", filename, strerror(errno));
                close(i);
                free(data);
@@ -268,7 +265,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
                char *name;
-               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+               if (!S_ISREG(ntohl(ce->ce_mode)))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
@@ -280,12 +277,19 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached)
                        memcpy(name + 2, ce->name, len + 1);
                }
                argv[argc++] = name;
-               if (argc < MAXARGS)
+               if (argc < MAXARGS && !ce_stage(ce))
                        continue;
                status = exec_grep(argc, argv);
                if (0 < status)
                        hit = 1;
                argc = nr;
+               if (ce_stage(ce)) {
+                       do {
+                               i++;
+                       } while (i < active_nr &&
+                                !strcmp(ce->name, active_cache[i]->name));
+                       i--; /* compensate for loop control */
+               }
        }
        if (argc > nr) {
                status = exec_grep(argc, argv);
@@ -316,14 +320,24 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
 
        for (nr = 0; nr < active_nr; nr++) {
                struct cache_entry *ce = active_cache[nr];
-               if (ce_stage(ce) || !S_ISREG(ntohl(ce->ce_mode)))
+               if (!S_ISREG(ntohl(ce->ce_mode)))
                        continue;
                if (!pathspec_matches(paths, ce->name))
                        continue;
-               if (cached)
+               if (cached) {
+                       if (ce_stage(ce))
+                               continue;
                        hit |= grep_sha1(opt, ce->sha1, ce->name, 0);
+               }
                else
                        hit |= grep_file(opt, ce->name);
+               if (ce_stage(ce)) {
+                       do {
+                               nr++;
+                       } while (nr < active_nr &&
+                                !strcmp(ce->name, active_cache[nr]->name));
+                       nr--; /* compensate for loop control */
+               }
        }
        free_grep_patterns(opt);
        return hit;
index 235a0ee48f2c5ce09c63a949358eb16cde05332d..12e43d0db4df0c40fe6375b38691684efcddad18 100644 (file)
 #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates/"
 #endif
 
+#ifdef NO_TRUSTABLE_FILEMODE
+#define TEST_FILEMODE 0
+#else
+#define TEST_FILEMODE 1
+#endif
+
 static void safe_create_dir(const char *dir, int share)
 {
        if (mkdir(dir, 0777) < 0) {
@@ -50,7 +56,7 @@ static void copy_templates_1(char *path, int baselen,
 
        /* Note: if ".git/hooks" file exists in the repository being
         * re-initialized, /etc/core-git/templates/hooks/update would
-        * cause git-init-db to fail here.  I think this is sane but
+        * cause git-init to fail here.  I think this is sane but
         * it means that the set of templates we ship by default, along
         * with the way the namespace under .git/ is organized, should
         * be really carefully chosen.
@@ -124,8 +130,11 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        int template_len;
        DIR *dir;
 
-       if (!template_dir)
-               template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+       if (!template_dir) {
+               template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
+               if (!template_dir)
+                       template_dir = DEFAULT_GIT_TEMPLATE_DIR;
+       }
        strcpy(template_path, template_dir);
        template_len = strlen(template_path);
        if (template_path[template_len-1] != '/') {
@@ -164,13 +173,15 @@ static void copy_templates(const char *git_dir, int len, const char *template_di
        closedir(dir);
 }
 
-static void create_default_files(const char *git_dir, const char *template_path)
+static int create_default_files(const char *git_dir, const char *template_path)
 {
        unsigned len = strlen(git_dir);
        static char path[PATH_MAX];
        unsigned char sha1[20];
        struct stat st1;
        char repo_version_string[10];
+       int reinit;
+       int filemode;
 
        if (len > sizeof(path)-50)
                die("insane git directory %s", git_dir);
@@ -218,8 +229,9 @@ static void create_default_files(const char *git_dir, const char *template_path)
         * branch, if it does not exist yet.
         */
        strcpy(path + len, "HEAD");
-       if (read_ref("HEAD", sha1) < 0) {
-               if (create_symref("HEAD", "refs/heads/master") < 0)
+       reinit = !read_ref("HEAD", sha1);
+       if (!reinit) {
+               if (create_symref("HEAD", "refs/heads/master", NULL) < 0)
                        exit(1);
        }
 
@@ -231,18 +243,29 @@ static void create_default_files(const char *git_dir, const char *template_path)
        strcpy(path + len, "config");
 
        /* Check filemode trustability */
-       if (!lstat(path, &st1)) {
+       filemode = TEST_FILEMODE;
+       if (TEST_FILEMODE && !lstat(path, &st1)) {
                struct stat st2;
-               int filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) &&
+               filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) &&
                                !lstat(path, &st2) &&
                                st1.st_mode != st2.st_mode);
-               git_config_set("core.filemode",
-                              filemode ? "true" : "false");
        }
+       git_config_set("core.filemode", filemode ? "true" : "false");
+
+       if (is_bare_repository()) {
+               git_config_set("core.bare", "true");
+       }
+       else {
+               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");
+       }
+       return reinit;
 }
 
 static const char init_db_usage[] =
-"git-init-db [--template=<template-directory>] [--shared]";
+"git-init [--template=<template-directory>] [--shared]";
 
 /*
  * If you want to, you can share the DB area with any number of branches.
@@ -256,7 +279,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        const char *sha1_dir;
        const char *template_dir = NULL;
        char *path;
-       int len, i;
+       int len, i, reinit;
 
        for (i = 1; i < argc; i++, argv++) {
                const char *arg = argv[1];
@@ -274,10 +297,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
         * Set up the default .git directory contents
         */
        git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       if (!git_dir) {
+       if (!git_dir)
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
-               fprintf(stderr, "defaulting to local storage area\n");
-       }
        safe_create_dir(git_dir, 0);
 
        /* Check to see if the repository version is right.
@@ -287,7 +308,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
         */
        check_repository_format();
 
-       create_default_files(git_dir, template_dir);
+       reinit = create_default_files(git_dir, template_dir);
 
        /*
         * And set up the object store.
@@ -314,5 +335,10 @@ 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);
+
        return 0;
 }
index fedb0137bc5e3ba50ecb90ce560ce9ce9290d8df..af2de54371cf7fc294c17431622f759e8e6066db 100644 (file)
 #include "revision.h"
 #include "log-tree.h"
 #include "builtin.h"
-#include <time.h>
-#include <sys/time.h>
+#include "tag.h"
+#include "reflog-walk.h"
+
+static int default_show_root = 1;
 
 /* this is in builtin-diff.c */
 void add_head(struct rev_info *revs);
@@ -19,14 +21,27 @@ void add_head(struct rev_info *revs);
 static void cmd_log_init(int argc, const char **argv, const char *prefix,
                      struct rev_info *rev)
 {
+       int i;
+
        rev->abbrev = DEFAULT_ABBREV;
        rev->commit_format = CMIT_FMT_DEFAULT;
        rev->verbose_header = 1;
+       rev->show_root_diff = default_show_root;
        argc = setup_revisions(argc, argv, rev, "HEAD");
        if (rev->diffopt.pickaxe || rev->diffopt.filter)
                rev->always_show_header = 0;
-       if (argc > 1)
-               die("unrecognized argument: %s", argv[1]);
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strncmp(arg, "--encoding=", 11)) {
+                       arg += 11;
+                       if (strcmp(arg, "none"))
+                               git_log_output_encoding = strdup(arg);
+                       else
+                               git_log_output_encoding = "";
+               }
+               else
+                       die("unrecognized argument: %s", arg);
+       }
 }
 
 static int cmd_log_walk(struct rev_info *rev)
@@ -36,19 +51,31 @@ static int cmd_log_walk(struct rev_info *rev)
        prepare_revision_walk(rev);
        while ((commit = get_revision(rev)) != NULL) {
                log_tree_commit(rev, commit);
-               free(commit->buffer);
-               commit->buffer = NULL;
+               if (!rev->reflog_info) {
+                       /* we allow cycles in reflog ancestry */
+                       free(commit->buffer);
+                       commit->buffer = NULL;
+               }
                free_commit_list(commit->parents);
                commit->parents = NULL;
        }
        return 0;
 }
 
+static int git_log_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "log.showroot")) {
+               default_show_root = git_config_bool(var, value);
+               return 0;
+       }
+       return git_diff_ui_config(var, value);
+}
+
 int cmd_whatchanged(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
 
-       git_config(git_diff_ui_config);
+       git_config(git_log_config);
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.diffopt.recursive = 1;
@@ -59,11 +86,45 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix)
        return cmd_log_walk(&rev);
 }
 
+static int show_object(const unsigned char *sha1, int suppress_header)
+{
+       unsigned long size;
+       char type[20];
+       char *buf = read_sha1_file(sha1, type, &size);
+       int offset = 0;
+
+       if (!buf)
+               return error("Could not read object %s", sha1_to_hex(sha1));
+
+       if (suppress_header)
+               while (offset < size && buf[offset++] != '\n') {
+                       int new_offset = offset;
+                       while (new_offset < size && buf[new_offset++] != '\n')
+                               ; /* do nothing */
+                       offset = new_offset;
+               }
+
+       if (offset < size)
+               fwrite(buf + offset, size - offset, 1, stdout);
+       free(buf);
+       return 0;
+}
+
+static int show_tree_object(const unsigned char *sha1,
+               const char *base, int baselen,
+               const char *pathname, unsigned mode, int stage)
+{
+       printf("%s%s\n", pathname, S_ISDIR(mode) ? "/" : "");
+       return 0;
+}
+
 int cmd_show(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
+       struct object_array_entry *objects;
+       int i, count, ret = 0;
 
-       git_config(git_diff_ui_config);
+       git_config(git_log_config);
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.diffopt.recursive = 1;
@@ -73,6 +134,82 @@ int cmd_show(int argc, const char **argv, const char *prefix)
        rev.ignore_merges = 0;
        rev.no_walk = 1;
        cmd_log_init(argc, argv, prefix, &rev);
+
+       count = rev.pending.nr;
+       objects = rev.pending.objects;
+       for (i = 0; i < count && !ret; i++) {
+               struct object *o = objects[i].item;
+               const char *name = objects[i].name;
+               switch (o->type) {
+               case OBJ_BLOB:
+                       ret = show_object(o->sha1, 0);
+                       break;
+               case OBJ_TAG: {
+                       struct tag *t = (struct tag *)o;
+
+                       printf("%stag %s%s\n\n",
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_COMMIT),
+                                       t->tag,
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_RESET));
+                       ret = show_object(o->sha1, 1);
+                       objects[i].item = (struct object *)t->tagged;
+                       i--;
+                       break;
+               }
+               case OBJ_TREE:
+                       printf("%stree %s%s\n\n",
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_COMMIT),
+                                       name,
+                                       diff_get_color(rev.diffopt.color_diff,
+                                               DIFF_RESET));
+                       read_tree_recursive((struct tree *)o, "", 0, 0, NULL,
+                                       show_tree_object);
+                       break;
+               case OBJ_COMMIT:
+                       rev.pending.nr = rev.pending.alloc = 0;
+                       rev.pending.objects = NULL;
+                       add_object_array(o, name, &rev.pending);
+                       ret = cmd_log_walk(&rev);
+                       break;
+               default:
+                       ret = error("Unknown type: %d", o->type);
+               }
+       }
+       free(objects);
+       return ret;
+}
+
+/*
+ * This is equivalent to "git log -g --abbrev-commit --pretty=oneline"
+ */
+int cmd_log_reflog(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info rev;
+
+       git_config(git_log_config);
+       init_revisions(&rev, prefix);
+       init_reflog_walk(&rev.reflog_info);
+       rev.abbrev_commit = 1;
+       rev.verbose_header = 1;
+       cmd_log_init(argc, argv, prefix, &rev);
+
+       /*
+        * This means that we override whatever commit format the user gave
+        * on the cmd line.  Sad, but cmd_log_init() currently doesn't
+        * allow us to set a different default.
+        */
+       rev.commit_format = CMIT_FMT_ONELINE;
+       rev.always_show_header = 1;
+
+       /*
+        * We get called through "git reflog", so unlike the other log
+        * routines, we need to set up our pager manually..
+        */
+       setup_pager();
+
        return cmd_log_walk(&rev);
 }
 
@@ -80,7 +217,7 @@ int cmd_log(int argc, const char **argv, const char *prefix)
 {
        struct rev_info rev;
 
-       git_config(git_diff_ui_config);
+       git_config(git_log_config);
        init_revisions(&rev, prefix);
        rev.always_show_header = 1;
        cmd_log_init(argc, argv, prefix, &rev);
@@ -95,21 +232,32 @@ static int istitlechar(char c)
 
 static char *extra_headers = NULL;
 static int extra_headers_size = 0;
+static const char *fmt_patch_suffix = ".patch";
 
 static int git_format_config(const char *var, const char *value)
 {
        if (!strcmp(var, "format.headers")) {
-               int len = strlen(value);
+               int len;
+
+               if (!value)
+                       die("format.headers without value");
+               len = strlen(value);
                extra_headers_size += len + 1;
                extra_headers = xrealloc(extra_headers, extra_headers_size);
                extra_headers[extra_headers_size - len - 1] = 0;
                strcat(extra_headers, value);
                return 0;
        }
-       if (!strcmp(var, "diff.color")) {
+       if (!strcmp(var, "format.suffix")) {
+               if (!value)
+                       die("format.suffix without value");
+               fmt_patch_suffix = xstrdup(value);
                return 0;
        }
-       return git_diff_ui_config(var, value);
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
+               return 0;
+       }
+       return git_log_config(var, value);
 }
 
 
@@ -121,9 +269,10 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
        char filename[1024];
        char *sol;
        int len = 0;
+       int suffix_len = strlen(fmt_patch_suffix) + 10; /* ., NUL and slop */
 
        if (output_directory) {
-               strlcpy(filename, output_directory, 1010);
+               strlcpy(filename, output_directory, 1000);
                len = strlen(filename);
                if (filename[len - 1] != '/')
                        filename[len++] = '/';
@@ -147,7 +296,10 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
                        }
                }
 
-               for (j = 0; len < 1024 - 6 && sol[j] && sol[j] != '\n'; j++) {
+               for (j = 0;
+                    len < sizeof(filename) - suffix_len &&
+                            sol[j] && sol[j] != '\n';
+                    j++) {
                        if (istitlechar(sol[j])) {
                                if (space) {
                                        filename[len++] = '-';
@@ -163,7 +315,7 @@ static void reopen_stdout(struct commit *commit, int nr, int keep_subject)
                while (filename[len - 1] == '.' || filename[len - 1] == '-')
                        len--;
        }
-       strcpy(filename + len, ".txt");
+       strcpy(filename + len, fmt_patch_suffix);
        fprintf(realstdout, "%s\n", filename);
        freopen(filename, "w", stdout);
 }
@@ -232,7 +384,7 @@ static void get_patch_ids(struct rev_info *rev, struct diff_options *options, co
 
 static void gen_message_id(char *dest, unsigned int length, char *base)
 {
-       const char *committer = git_committer_info(1);
+       const char *committer = git_committer_info(-1);
        const char *email_start = strrchr(committer, '<');
        const char *email_end = strrchr(committer, '>');
        if(!email_start || !email_end || email_start > email_end - 1)
@@ -260,7 +412,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        char message_id[1024];
        char ref_message_id[1024];
 
-       setup_ident();
        git_config(git_format_config);
        init_revisions(&rev, prefix);
        rev.commit_format = CMIT_FMT_EMAIL;
@@ -334,6 +485,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                                die("Need a Message-Id for --in-reply-to");
                        in_reply_to = argv[i];
                }
+               else if (!strncmp(argv[i], "--suffix=", 9))
+                       fmt_patch_suffix = argv[i] + 9;
                else
                        argv[j++] = argv[i];
        }
@@ -349,9 +502,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
                die ("unrecognized argument: %s", argv[1]);
 
        if (!rev.diffopt.output_format)
-               rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
+               rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_SUMMARY | DIFF_FORMAT_PATCH;
 
-       if (!output_directory)
+       if (!rev.diffopt.text)
+               rev.diffopt.binary = 1;
+
+       if (!output_directory && !use_stdout)
                output_directory = prefix;
 
        if (output_directory) {
@@ -363,8 +519,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
        }
 
        if (rev.pending.nr == 1) {
-               rev.pending.objects[0].item->flags |= UNINTERESTING;
-               add_head(&rev);
+               if (rev.max_count < 0) {
+                       rev.pending.objects[0].item->flags |= UNINTERESTING;
+                       add_head(&rev);
+               }
+               /* Otherwise, it is "format-patch -22 HEAD", and
+                * get_revision() would return only the specified count.
+                */
        }
 
        if (ignore_if_in_upstream)
index ad8c41e7310032c7a868ecb6a87e542af799c361..ac89eb2f770d3b8dbc35ee2c24d9d6c82b8fb6fe 100644 (file)
@@ -5,8 +5,6 @@
  *
  * Copyright (C) Linus Torvalds, 2005
  */
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "quote.h"
 #include "dir.h"
@@ -325,7 +323,7 @@ static const char ls_files_usage[] =
 int cmd_ls_files(int argc, const char **argv, const char *prefix)
 {
        int i;
-       int exc_given = 0;
+       int exc_given = 0, require_work_tree = 0;
        struct dir_struct dir;
 
        memset(&dir, 0, sizeof(dir));
@@ -365,14 +363,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                }
                if (!strcmp(arg, "-m") || !strcmp(arg, "--modified")) {
                        show_modified = 1;
+                       require_work_tree = 1;
                        continue;
                }
                if (!strcmp(arg, "-o") || !strcmp(arg, "--others")) {
                        show_others = 1;
+                       require_work_tree = 1;
                        continue;
                }
                if (!strcmp(arg, "-i") || !strcmp(arg, "--ignored")) {
                        dir.show_ignored = 1;
+                       require_work_tree = 1;
                        continue;
                }
                if (!strcmp(arg, "-s") || !strcmp(arg, "--stage")) {
@@ -381,6 +382,7 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                }
                if (!strcmp(arg, "-k") || !strcmp(arg, "--killed")) {
                        show_killed = 1;
+                       require_work_tree = 1;
                        continue;
                }
                if (!strcmp(arg, "--directory")) {
@@ -449,6 +451,10 @@ 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");
+
        pathspec = get_pathspec(prefix, argv + i);
 
        /* Verify that the pathspec matches the prefix */
@@ -487,10 +493,14 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)
                for (num = 0; pathspec[num]; num++) {
                        if (ps_matched[num])
                                continue;
-                       error("pathspec '%s' did not match any.",
+                       error("pathspec '%s' did not match any file(s) known to git.",
                              pathspec[num] + prefix_offset);
                        errors++;
                }
+
+               if (errors)
+                       fprintf(stderr, "Did you forget to 'git add'?\n");
+
                return errors ? 1 : 0;
        }
 
index b8d7dbc0b71929a95aaebd79d5912897d5eb70e7..583da38b6750185eb38f04d91555aa75ee4a77b0 100644 (file)
@@ -2,17 +2,9 @@
  * Another stupid program, this one parsing the headers of an
  * email to figure out authorship and subject
  */
-#define _GNU_SOURCE
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#ifndef NO_ICONV
-#include <iconv.h>
-#endif
-#include "git-compat-util.h"
 #include "cache.h"
 #include "builtin.h"
+#include "utf8.h"
 
 static FILE *cmitmsg, *patchfile, *fin, *fout;
 
@@ -519,40 +511,15 @@ static int decode_b_segment(char *in, char *ot, char *ep)
 
 static void convert_to_utf8(char *line, char *charset)
 {
-#ifndef NO_ICONV
-       char *in, *out;
-       size_t insize, outsize, nrc;
-       char outbuf[4096]; /* cheat */
        static char latin_one[] = "latin1";
        char *input_charset = *charset ? charset : latin_one;
-       iconv_t conv = iconv_open(metainfo_charset, input_charset);
-
-       if (conv == (iconv_t) -1) {
-               static int warned_latin1_once = 0;
-               if (input_charset != latin_one) {
-                       fprintf(stderr, "cannot convert from %s to %s\n",
-                               input_charset, metainfo_charset);
-                       *charset = 0;
-               }
-               else if (!warned_latin1_once) {
-                       warned_latin1_once = 1;
-                       fprintf(stderr, "tried to convert from %s to %s, "
-                               "but your iconv does not work with it.\n",
-                               input_charset, metainfo_charset);
-               }
-               return;
-       }
-       in = line;
-       insize = strlen(in);
-       out = outbuf;
-       outsize = sizeof(outbuf);
-       nrc = iconv(conv, &in, &insize, &out, &outsize);
-       iconv_close(conv);
-       if (nrc == (size_t) -1)
-               return;
-       *out = 0;
-       strcpy(line, outbuf);
-#endif
+       char *out = reencode_string(line, metainfo_charset, input_charset);
+
+       if (!out)
+               die("cannot convert from %s to %s\n",
+                   input_charset, metainfo_charset);
+       strcpy(line, out);
+       free(out);
 }
 
 static int decode_header_bq(char *it)
@@ -827,16 +794,23 @@ static const char mailinfo_usage[] =
 
 int cmd_mailinfo(int argc, const char **argv, const char *prefix)
 {
+       const char *def_charset;
+
        /* NEEDSWORK: might want to do the optional .git/ directory
         * discovery
         */
        git_config(git_default_config);
 
+       def_charset = (git_commit_encoding ? git_commit_encoding : "utf-8");
+       metainfo_charset = def_charset;
+
        while (1 < argc && argv[1][0] == '-') {
                if (!strcmp(argv[1], "-k"))
                        keep_subject = 1;
                else if (!strcmp(argv[1], "-u"))
-                       metainfo_charset = git_commit_encoding;
+                       metainfo_charset = def_charset;
+               else if (!strcmp(argv[1], "-n"))
+                       metainfo_charset = NULL;
                else if (!strncmp(argv[1], "--encoding=", 11))
                        metainfo_charset = argv[1] + 11;
                else
index 91a699d34d93462b56f2591193bb5729b42b3950..3bca855aae857cde15f2a249f8e0a5c30b3e7d82 100644 (file)
@@ -4,13 +4,6 @@
  * It just splits a mbox into a list of files: "0001" "0002" ..
  * so you can process them further from there.
  */
-#include <unistd.h>
-#include <stdlib.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <string.h>
-#include <stdio.h>
 #include "cache.h"
 #include "builtin.h"
 
similarity index 79%
rename from merge-base.c
rename to builtin-merge-base.c
index 009caf804b43fdd644ff08991d55d0e417fd34d8..e35d362f2697ebc7e627f230283aedb8ed92857f 100644 (file)
@@ -1,10 +1,7 @@
-#include <stdlib.h>
 #include "cache.h"
 #include "commit.h"
 
-static int show_all;
-
-static int merge_base(struct commit *rev1, struct commit *rev2)
+static int show_merge_base(struct commit *rev1, struct commit *rev2, int show_all)
 {
        struct commit_list *result = get_merge_bases(rev1, rev2, 0);
 
@@ -24,16 +21,16 @@ static int merge_base(struct commit *rev1, struct commit *rev2)
 static const char merge_base_usage[] =
 "git-merge-base [--all] <commit-id> <commit-id>";
 
-int main(int argc, char **argv)
+int cmd_merge_base(int argc, const char **argv, const char *prefix)
 {
        struct commit *rev1, *rev2;
        unsigned char rev1key[20], rev2key[20];
+       int show_all = 0;
 
-       setup_git_directory();
        git_config(git_default_config);
 
        while (1 < argc && argv[1][0] == '-') {
-               char *arg = argv[1];
+               const char *arg = argv[1];
                if (!strcmp(arg, "-a") || !strcmp(arg, "--all"))
                        show_all = 1;
                else
@@ -50,5 +47,5 @@ int main(int argc, char **argv)
        rev2 = lookup_commit_reference(rev2key);
        if (!rev1 || !rev2)
                return 1;
-       return merge_base(rev1, rev2);
+       return show_merge_base(rev1, rev2, show_all);
 }
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
new file mode 100644 (file)
index 0000000..9135773
--- /dev/null
@@ -0,0 +1,63 @@
+#include "cache.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+
+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)
+{
+       char *names[3];
+       mmfile_t mmfs[3];
+       mmbuffer_t result = {NULL, 0};
+       xpparam_t xpp = {XDF_NEED_MINIMAL};
+       int ret = 0, i = 0, to_stdout = 0;
+
+       while (argc > 4) {
+               if (!strcmp(argv[1], "-L") && i < 3) {
+                       names[i++] = argv[2];
+                       argc--;
+                       argv++;
+               } else if (!strcmp(argv[1], "-p") ||
+                               !strcmp(argv[1], "--stdout"))
+                       to_stdout = 1;
+               else if (!strcmp(argv[1], "-q") ||
+                               !strcmp(argv[1], "--quiet"))
+                       freopen("/dev/null", "w", stderr);
+               else
+                       usage(merge_file_usage);
+               argc--;
+               argv++;
+       }
+
+       if (argc != 4)
+               usage(merge_file_usage);
+
+       for (; i < 3; i++)
+               names[i] = argv[i + 1];
+
+       for (i = 0; i < 3; i++)
+               if (read_mmfile(mmfs + i, argv[i + 1]))
+                       return -1;
+
+       ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
+                       &xpp, XDL_MERGE_ZEALOUS, &result);
+
+       for (i = 0; i < 3; i++)
+               free(mmfs[i].ptr);
+
+       if (ret >= 0) {
+               char *filename = argv[1];
+               FILE *f = to_stdout ? stdout : fopen(filename, "wb");
+
+               if (!f)
+                       ret = error("Could not open %s for writing", filename);
+               else if (fwrite(result.ptr, result.size, 1, f) != 1)
+                       ret = error("Could not write to %s", filename);
+               else if (fclose(f))
+                       ret = error("Could not close %s", filename);
+               free(result.ptr);
+       }
+
+       return ret;
+}
index 54dd3bfe8ac787fda32f3557faac00c65a61de96..737af350b873e90c787cb49960236fc19b62a3bf 100644 (file)
@@ -3,8 +3,6 @@
  *
  * Copyright (C) 2006 Johannes Schindelin
  */
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "builtin.h"
 #include "dir.h"
@@ -146,21 +144,24 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                                && lstat(dst, &st) == 0)
                        bad = "cannot move directory over file";
                else if (src_is_dir) {
+                       const char *src_w_slash = add_slash(src);
+                       int len_w_slash = length + 1;
                        int first, last;
 
                        modes[i] = WORKING_DIRECTORY;
 
-                       first = cache_name_pos(src, length);
+                       first = cache_name_pos(src_w_slash, len_w_slash);
                        if (first >= 0)
-                               die ("Huh? %s/ is in index?", src);
+                               die ("Huh? %.*s is in index?",
+                                               len_w_slash, src_w_slash);
 
                        first = -1 - first;
                        for (last = first; last < active_nr; last++) {
                                const char *path = active_cache[last]->name;
-                               if (strncmp(path, src, length)
-                                               || path[length] != '/')
+                               if (strncmp(path, src_w_slash, len_w_slash))
                                        break;
                        }
+                       free((char *)src_w_slash);
 
                        if (last - first < 1)
                                bad = "source directory is empty";
index 618aa314d22dd16bb62b0a7deb3be14fda411ebe..b4f15cc38ab7fa045fc5cf025ca3fd6251a01973 100644 (file)
@@ -1,4 +1,3 @@
-#include <stdlib.h>
 #include "builtin.h"
 #include "cache.h"
 #include "commit.h"
index 69e5dd39ca6f007d016ca562454cdb75ec9362fb..3824ee33acb2f984419ab79c2f3ecc306d3e5ea5 100644 (file)
 #include "diff.h"
 #include "revision.h"
 #include "list-objects.h"
-#include <sys/time.h>
-#include <signal.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]*] [--stdout | base-name] \n\
+       [--revs [--unpacked | --all]*] [--reflog] [--stdout | base-name] \n\
        [<ref-list | <object-list]";
 
 struct object_entry {
@@ -278,7 +276,52 @@ static int encode_header(enum object_type type, unsigned long size, unsigned cha
  * we are going to reuse the existing object data as is.  make
  * sure it is not corrupt.
  */
-static int check_inflate(unsigned char *data, unsigned long len, unsigned long expect)
+static int check_pack_inflate(struct packed_git *p,
+               struct pack_window **w_curs,
+               unsigned long offset,
+               unsigned long len,
+               unsigned long expect)
+{
+       z_stream stream;
+       unsigned char fakebuf[4096], *in;
+       int st;
+
+       memset(&stream, 0, sizeof(stream));
+       inflateInit(&stream);
+       do {
+               in = use_pack(p, w_curs, offset, &stream.avail_in);
+               stream.next_in = in;
+               stream.next_out = fakebuf;
+               stream.avail_out = sizeof(fakebuf);
+               st = inflate(&stream, Z_FINISH);
+               offset += stream.next_in - in;
+       } while (st == Z_OK || st == Z_BUF_ERROR);
+       inflateEnd(&stream);
+       return (st == Z_STREAM_END &&
+               stream.total_out == expect &&
+               stream.total_in == len) ? 0 : -1;
+}
+
+static void copy_pack_data(struct sha1file *f,
+               struct packed_git *p,
+               struct pack_window **w_curs,
+               unsigned long offset,
+               unsigned long len)
+{
+       unsigned char *in;
+       unsigned int avail;
+
+       while (len) {
+               in = use_pack(p, w_curs, offset, &avail);
+               if (avail > len)
+                       avail = len;
+               sha1write(f, in, avail);
+               offset += avail;
+               len -= avail;
+       }
+}
+
+static int check_loose_inflate(unsigned char *data, unsigned long len, unsigned long expect)
 {
        z_stream stream;
        unsigned char fakebuf[4096];
@@ -325,7 +368,7 @@ static int revalidate_loose_object(struct object_entry *entry,
                return -1;
        map += used;
        mapsize -= used;
-       return check_inflate(map, mapsize, size);
+       return check_loose_inflate(map, mapsize, size);
 }
 
 static unsigned long write_object(struct sha1file *f,
@@ -418,6 +461,8 @@ static unsigned long write_object(struct sha1file *f,
        }
        else {
                struct packed_git *p = entry->in_pack;
+               struct pack_window *w_curs = NULL;
+               unsigned long offset;
 
                if (entry->delta) {
                        obj_type = (allow_ofs_delta && entry->delta->offset) ?
@@ -439,16 +484,14 @@ static unsigned long write_object(struct sha1file *f,
                        hdrlen += 20;
                }
 
-               use_packed_git(p);
-               buf = (char *) p->pack_base
-                       + entry->in_pack_offset
-                       + entry->in_pack_header_size;
+               offset = entry->in_pack_offset + entry->in_pack_header_size;
                datalen = find_packed_object_size(p, entry->in_pack_offset)
                                - entry->in_pack_header_size;
-               if (!pack_to_stdout && check_inflate(buf, datalen, entry->size))
+               if (!pack_to_stdout && check_pack_inflate(p, &w_curs,
+                               offset, datalen, entry->size))
                        die("corrupt delta in pack %s", sha1_to_hex(entry->sha1));
-               sha1write(f, buf, datalen);
-               unuse_packed_git(p);
+               copy_pack_data(f, p, &w_curs, offset, datalen);
+               unuse_pack(&w_curs);
                reused++;
        }
        if (entry->delta)
@@ -514,6 +557,8 @@ static void write_pack_file(void)
        if (do_progress)
                fputc('\n', stderr);
  done:
+       if (written != nr_result)
+               die("wrote %d objects while expecting %d", written, nr_result);
        sha1close(f, pack_file_sha1, 1);
 }
 
@@ -524,7 +569,7 @@ static void write_index_file(void)
                                        sha1_to_hex(object_list_sha1), "idx");
        struct object_entry **list = sorted_by_sha;
        struct object_entry **last = list + nr_result;
-       unsigned int array[256];
+       uint32_t array[256];
 
        /*
         * Write the first-level table (the list is sorted,
@@ -542,7 +587,7 @@ static void write_index_file(void)
                array[i] = htonl(next - sorted_by_sha);
                list = next;
        }
-       sha1write(f, array, 256 * sizeof(int));
+       sha1write(f, array, 256 * 4);
 
        /*
         * Write the actual SHA1 entries..
@@ -550,7 +595,7 @@ static void write_index_file(void)
        list = sorted_by_sha;
        for (i = 0; i < nr_result; i++) {
                struct object_entry *entry = *list++;
-               unsigned int offset = htonl(entry->offset);
+               uint32_t offset = htonl(entry->offset);
                sha1write(f, &offset, 4);
                sha1write(f, entry->sha1, 20);
        }
@@ -937,22 +982,19 @@ static void check_object(struct object_entry *entry)
 
        if (entry->in_pack && !entry->preferred_base) {
                struct packed_git *p = entry->in_pack;
+               struct pack_window *w_curs = NULL;
                unsigned long left = p->pack_size - entry->in_pack_offset;
                unsigned long size, used;
                unsigned char *buf;
                struct object_entry *base_entry = NULL;
 
-               use_packed_git(p);
-               buf = p->pack_base;
-               buf += entry->in_pack_offset;
+               buf = use_pack(p, &w_curs, entry->in_pack_offset, NULL);
 
                /* We want in_pack_type even if we do not reuse delta.
                 * There is no point not reusing non-delta representations.
                 */
                used = unpack_object_header_gently(buf, left,
                                                   &entry->in_pack_type, &size);
-               if (!used || left - used <= 20)
-                       die("corrupt pack for %s", sha1_to_hex(entry->sha1));
 
                /* Check if it is delta, and the base is also an object
                 * we are going to pack.  If so we will reuse the existing
@@ -961,21 +1003,26 @@ static void check_object(struct object_entry *entry)
                if (!no_reuse_delta) {
                        unsigned char c, *base_name;
                        unsigned long ofs;
+                       unsigned long used_0;
                        /* there is at least 20 bytes left in the pack */
                        switch (entry->in_pack_type) {
                        case OBJ_REF_DELTA:
-                               base_name = buf + used;
+                               base_name = use_pack(p, &w_curs,
+                                       entry->in_pack_offset + used, NULL);
                                used += 20;
                                break;
                        case OBJ_OFS_DELTA:
-                               c = buf[used++];
+                               buf = use_pack(p, &w_curs,
+                                       entry->in_pack_offset + used, NULL);
+                               used_0 = 0;
+                               c = buf[used_0++];
                                ofs = c & 127;
                                while (c & 128) {
                                        ofs += 1;
                                        if (!ofs || ofs & ~(~0UL >> 7))
                                                die("delta base offset overflow in pack for %s",
                                                    sha1_to_hex(entry->sha1));
-                                       c = buf[used++];
+                                       c = buf[used_0++];
                                        ofs = (ofs << 7) + (c & 127);
                                }
                                if (ofs >= entry->in_pack_offset)
@@ -983,6 +1030,7 @@ static void check_object(struct object_entry *entry)
                                            sha1_to_hex(entry->sha1));
                                ofs = entry->in_pack_offset - ofs;
                                base_name = find_packed_object_name(p, ofs);
+                               used += used_0;
                                break;
                        default:
                                base_name = NULL;
@@ -990,7 +1038,7 @@ static void check_object(struct object_entry *entry)
                        if (base_name)
                                base_entry = locate_object_entry(base_name);
                }
-               unuse_packed_git(p);
+               unuse_pack(&w_curs);
                entry->in_pack_header_size = used;
 
                if (base_entry) {
@@ -1176,7 +1224,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
         * on an earlier try, but only when reusing delta data.
         */
        if (!no_reuse_delta && trg_entry->in_pack &&
-           trg_entry->in_pack == src_entry->in_pack)
+           trg_entry->in_pack == src_entry->in_pack &&
+           trg_entry->in_pack_type != OBJ_REF_DELTA &&
+           trg_entry->in_pack_type != OBJ_OFS_DELTA)
                return 0;
 
        /*
@@ -1573,6 +1623,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
                if (!strcmp("--unpacked", arg) ||
                    !strncmp("--unpacked=", arg, 11) ||
+                   !strcmp("--reflog", arg) ||
                    !strcmp("--all", arg)) {
                        use_internal_rev_list = 1;
                        if (ARRAY_SIZE(rp_av) - 1 <= rp_ac)
@@ -1660,7 +1711,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                }
        }
        if (progress)
-               fprintf(stderr, "Total %d, written %d (delta %d), reused %d (delta %d)\n",
-                       nr_result, written, written_delta, reused, reused_delta);
+               fprintf(stderr, "Total %d (delta %d), reused %d (delta %d)\n",
+                       written, written_delta, reused, reused_delta);
        return 0;
 }
index 042d2718f904303600a57cfe8b5de83f5c0d34db..3de9b3eefdaad43f0b0212b93055f26a9542533a 100644 (file)
@@ -1,8 +1,10 @@
 #include "cache.h"
 #include "refs.h"
+#include "object.h"
+#include "tag.h"
 
 static const char builtin_pack_refs_usage[] =
-"git-pack-refs [--all] [--prune]";
+"git-pack-refs [--all] [--prune | --no-prune]";
 
 struct ref_to_prune {
        struct ref_to_prune *next;
@@ -29,12 +31,28 @@ static int handle_one_ref(const char *path, const unsigned char *sha1,
                          int flags, void *cb_data)
 {
        struct pack_refs_cb_data *cb = cb_data;
+       int is_tag_ref;
 
-       if (!cb->all && strncmp(path, "refs/tags/", 10))
-               return 0;
        /* Do not pack the symbolic refs */
-       if (!(flags & REF_ISSYMREF))
-               fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+       if ((flags & REF_ISSYMREF))
+               return 0;
+       is_tag_ref = !strncmp(path, "refs/tags/", 10);
+
+       /* ALWAYS pack refs that were already packed or are tags */
+       if (!cb->all && !is_tag_ref && !(flags & REF_ISPACKED))
+               return 0;
+
+       fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
+       if (is_tag_ref) {
+               struct object *o = parse_object(sha1);
+               if (o->type == OBJ_TAG) {
+                       o = deref_tag(o, path, 0);
+                       if (o)
+                               fprintf(cb->refs_file, "^%s\n",
+                                       sha1_to_hex(o->sha1));
+               }
+       }
+
        if (cb->prune && !do_not_prune(flags)) {
                int namelen = strlen(path) + 1;
                struct ref_to_prune *n = xcalloc(1, sizeof(*n) + namelen);
@@ -74,10 +92,15 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
 
        memset(&cbdata, 0, sizeof(cbdata));
 
+       cbdata.prune = 1;
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
                if (!strcmp(arg, "--prune")) {
-                       cbdata.prune = 1;
+                       cbdata.prune = 1; /* now the default */
+                       continue;
+               }
+               if (!strcmp(arg, "--no-prune")) {
+                       cbdata.prune = 0;
                        continue;
                }
                if (!strcmp(arg, "--all")) {
@@ -95,6 +118,10 @@ int cmd_pack_refs(int argc, const char **argv, const char *prefix)
        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);
index 24e3b0a8c21b43e0e82e5e505353213ca79f01b0..977730064b949c983163e972ef85a63d1fe4c914 100644 (file)
@@ -2,9 +2,12 @@
 #include "cache.h"
 
 static const char prune_packed_usage[] =
-"git-prune-packed [-n]";
+"git-prune-packed [-n] [-q]";
 
-static void prune_dir(int i, DIR *dir, char *pathname, int len, int dryrun)
+#define DRY_RUN 01
+#define VERBOSE 02
+
+static void prune_dir(int i, DIR *dir, char *pathname, int len, int opts)
 {
        struct dirent *de;
        char hex[40];
@@ -20,7 +23,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int dryrun)
                if (!has_sha1_pack(sha1, NULL))
                        continue;
                memcpy(pathname + len, de->d_name, 38);
-               if (dryrun)
+               if (opts & DRY_RUN)
                        printf("rm -f %s\n", pathname);
                else if (unlink(pathname) < 0)
                        error("unable to unlink %s", pathname);
@@ -29,7 +32,7 @@ static void prune_dir(int i, DIR *dir, char *pathname, int len, int dryrun)
        rmdir(pathname);
 }
 
-void prune_packed_objects(int dryrun)
+void prune_packed_objects(int opts)
 {
        int i;
        static char pathname[PATH_MAX];
@@ -46,24 +49,31 @@ void prune_packed_objects(int dryrun)
 
                sprintf(pathname + len, "%02x/", i);
                d = opendir(pathname);
+               if (opts == VERBOSE && (d || i == 255))
+                       fprintf(stderr, "Removing unused objects %d%%...\015",
+                               ((i+1) * 100) / 256);
                if (!d)
                        continue;
-               prune_dir(i, d, pathname, len + 3, dryrun);
+               prune_dir(i, d, pathname, len + 3, opts);
                closedir(d);
        }
+       if (opts == VERBOSE)
+               fprintf(stderr, "\nDone.\n");
 }
 
 int cmd_prune_packed(int argc, const char **argv, const char *prefix)
 {
        int i;
-       int dryrun = 0;
+       int opts = VERBOSE;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
 
                if (*arg == '-') {
                        if (!strcmp(arg, "-n"))
-                               dryrun = 1;
+                               opts |= DRY_RUN;
+                       else if (!strcmp(arg, "-q"))
+                               opts &= ~VERBOSE;
                        else
                                usage(prune_packed_usage);
                        continue;
@@ -72,6 +82,6 @@ int cmd_prune_packed(int argc, const char **argv, const char *prefix)
                usage(prune_packed_usage);
        }
        sync();
-       prune_packed_objects(dryrun);
+       prune_packed_objects(opts);
        return 0;
 }
index d853902c51e78800b76cf4714dffaa79600d3e53..6f0ba0d04d789f17e3f0136a3591a9465af7e71d 100644 (file)
@@ -1,23 +1,24 @@
 #include "cache.h"
-#include "refs.h"
-#include "tag.h"
 #include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "tree-walk.h"
 #include "diff.h"
 #include "revision.h"
 #include "builtin.h"
-#include "cache-tree.h"
+#include "reachable.h"
 
 static const char prune_usage[] = "git-prune [-n]";
 static int show_only;
-static struct rev_info revs;
 
 static int prune_object(char *path, const char *filename, const unsigned char *sha1)
 {
+       char buf[20];
+       const char *type;
+
        if (show_only) {
-               printf("would prune %s/%s\n", path, filename);
+               if (sha1_object_info(sha1, buf, NULL))
+                       type = "unknown";
+               else
+                       type = buf;
+               printf("%s %s\n", sha1_to_hex(sha1), type);
                return 0;
        }
        unlink(mkpath("%s/%s", path, filename));
@@ -78,148 +79,10 @@ static void prune_object_dir(const char *path)
        }
 }
 
-static void process_blob(struct blob *blob,
-                        struct object_array *p,
-                        struct name_path *path,
-                        const char *name)
-{
-       struct object *obj = &blob->object;
-
-       if (obj->flags & SEEN)
-               return;
-       obj->flags |= SEEN;
-       /* Nothing to do, really .. The blob lookup was the important part */
-}
-
-static void process_tree(struct tree *tree,
-                        struct object_array *p,
-                        struct name_path *path,
-                        const char *name)
-{
-       struct object *obj = &tree->object;
-       struct tree_desc desc;
-       struct name_entry entry;
-       struct name_path me;
-
-       if (obj->flags & SEEN)
-               return;
-       obj->flags |= SEEN;
-       if (parse_tree(tree) < 0)
-               die("bad tree object %s", sha1_to_hex(obj->sha1));
-       name = xstrdup(name);
-       add_object(obj, p, path, name);
-       me.up = path;
-       me.elem = name;
-       me.elem_len = strlen(name);
-
-       desc.buf = tree->buffer;
-       desc.size = tree->size;
-
-       while (tree_entry(&desc, &entry)) {
-               if (S_ISDIR(entry.mode))
-                       process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
-               else
-                       process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
-       }
-       free(tree->buffer);
-       tree->buffer = NULL;
-}
-
-static void process_tag(struct tag *tag, struct object_array *p, const char *name)
-{
-       struct object *obj = &tag->object;
-       struct name_path me;
-
-       if (obj->flags & SEEN)
-               return;
-       obj->flags |= SEEN;
-
-       me.up = NULL;
-       me.elem = "tag:/";
-       me.elem_len = 5;
-
-       if (parse_tag(tag) < 0)
-               die("bad tag object %s", sha1_to_hex(obj->sha1));
-       add_object(tag->tagged, p, NULL, name);
-}
-
-static void walk_commit_list(struct rev_info *revs)
-{
-       int i;
-       struct commit *commit;
-       struct object_array objects = { 0, 0, NULL };
-
-       /* Walk all commits, process their trees */
-       while ((commit = get_revision(revs)) != NULL)
-               process_tree(commit->tree, &objects, NULL, "");
-
-       /* Then walk all the pending objects, recursively processing them too */
-       for (i = 0; i < revs->pending.nr; i++) {
-               struct object_array_entry *pending = revs->pending.objects + i;
-               struct object *obj = pending->item;
-               const char *name = pending->name;
-               if (obj->type == OBJ_TAG) {
-                       process_tag((struct tag *) obj, &objects, name);
-                       continue;
-               }
-               if (obj->type == OBJ_TREE) {
-                       process_tree((struct tree *)obj, &objects, NULL, name);
-                       continue;
-               }
-               if (obj->type == OBJ_BLOB) {
-                       process_blob((struct blob *)obj, &objects, NULL, name);
-                       continue;
-               }
-               die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
-       }
-}
-
-static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct object *object = parse_object(sha1);
-       if (!object)
-               die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
-       add_pending_object(&revs, object, "");
-       return 0;
-}
-
-static void add_one_tree(const unsigned char *sha1)
-{
-       struct tree *tree = lookup_tree(sha1);
-       add_pending_object(&revs, &tree->object, "");
-}
-
-static void add_cache_tree(struct cache_tree *it)
-{
-       int i;
-
-       if (it->entry_count >= 0)
-               add_one_tree(it->sha1);
-       for (i = 0; i < it->subtree_nr; i++)
-               add_cache_tree(it->down[i]->cache_tree);
-}
-
-static void add_cache_refs(void)
-{
-       int i;
-
-       read_cache();
-       for (i = 0; i < active_nr; i++) {
-               lookup_blob(active_cache[i]->sha1);
-               /*
-                * We could add the blobs to the pending list, but quite
-                * frankly, we don't care. Once we've looked them up, and
-                * added them as objects, we've really done everything
-                * there is to do for a blob
-                */
-       }
-       if (active_cache_tree)
-               add_cache_tree(active_cache_tree);
-}
-
 int cmd_prune(int argc, const char **argv, const char *prefix)
 {
        int i;
+       struct rev_info revs;
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -230,28 +93,9 @@ int cmd_prune(int argc, const char **argv, const char *prefix)
                usage(prune_usage);
        }
 
-       /*
-        * Set up revision parsing, and mark us as being interested
-        * in all object types, not just commits.
-        */
+       save_commit_buffer = 0;
        init_revisions(&revs, prefix);
-       revs.tag_objects = 1;
-       revs.blob_objects = 1;
-       revs.tree_objects = 1;
-
-       /* Add all external refs */
-       for_each_ref(add_one_ref, NULL);
-
-       /* Add all refs from the index file */
-       add_cache_refs();
-
-       /*
-        * Set up the revision walk - this will move all commits
-        * from the pending list to the commit walking list.
-        */
-       prepare_revision_walk(&revs);
-
-       walk_commit_list(&revs);
+       mark_reachable_objects(&revs, 1);
 
        prune_object_dir(get_object_directory());
 
index d23974e708cb744f75efbb9f1acbf37606e92b05..c45649e26cf5284323aa80a5fe73b5842346eb14 100644 (file)
@@ -8,10 +8,10 @@
 
 #define MAX_URI (16)
 
-static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]";
+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 const char *execute;
+static const char *receivepack;
 
 #define BUF_SIZE (2084)
 static char buffer[BUF_SIZE];
@@ -54,14 +54,85 @@ static void expand_refspecs(void)
        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) {
-               size_t bytes = nr * sizeof(char *);
-
-               refspec = xrealloc(refspec, bytes);
-               memcpy(refspec, refs, bytes);
-               refspec_nr = 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();
 }
@@ -104,8 +175,10 @@ static int get_remotes_uri(const char *repo, const char *uri[MAX_URI])
                        else
                                error("more than %d URL's specified, ignoring the rest", MAX_URI);
                }
-               else if (is_refspec && !has_explicit_refspec)
-                       add_refspec(xstrdup(s));
+               else if (is_refspec && !has_explicit_refspec) {
+                       if (!wildcard_ref(s))
+                               add_refspec(xstrdup(s));
+               }
        }
        fclose(f);
        if (!n)
@@ -118,6 +191,7 @@ 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)
 {
@@ -130,8 +204,19 @@ static int get_remote_config(const char* key, const char* value)
                                error("more than %d URL's specified, ignoring the rest", MAX_URI);
                }
                else if (config_get_refspecs &&
-                        !strcmp(key + 7 + config_repo_len, ".push"))
-                       add_refspec(xstrdup(value));
+                        !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;
 }
@@ -143,6 +228,7 @@ static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI])
        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;
@@ -227,8 +313,8 @@ static int do_push(const char *repo)
                argv[argc++] = "--all";
        if (force)
                argv[argc++] = "--force";
-       if (execute)
-               argv[argc++] = execute;
+       if (receivepack)
+               argv[argc++] = receivepack;
        common_argc = argc;
 
        for (i = 0; i < n; i++) {
@@ -250,7 +336,7 @@ static int do_push(const char *repo)
                argv[dest_argc] = NULL;
                if (verbose)
                        fprintf(stderr, "Pushing to %s\n", dest);
-               err = run_command_v(argc, argv);
+               err = run_command_v(argv);
                if (!err)
                        continue;
                switch (err) {
@@ -311,8 +397,12 @@ int cmd_push(int argc, const char **argv, const char *prefix)
                        thin = 0;
                        continue;
                }
+               if (!strncmp(arg, "--receive-pack=", 15)) {
+                       receivepack = arg;
+                       continue;
+               }
                if (!strncmp(arg, "--exec=", 7)) {
-                       execute = arg;
+                       receivepack = arg;
                        continue;
                }
                usage(push_usage);
index c1867d2a0052ad797481f3d5f9aa544952ff251b..8ba436dbace7ec25ec5768a5c6c84f3202f92159 100644 (file)
@@ -10,6 +10,7 @@
 #include "tree-walk.h"
 #include "cache-tree.h"
 #include "unpack-trees.h"
+#include "dir.h"
 #include "builtin.h"
 
 static struct object_list *trees;
@@ -84,7 +85,7 @@ static void prime_cache_tree(void)
 
 }
 
-static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | [[-m [--aggressive] | --reset | --prefix=<prefix>] [-u | -i]] [--exclude-per-directory=<gitignore>] <sha1> [<sha2> [<sha3>]])";
 
 static struct lock_file lock_file;
 
@@ -178,6 +179,23 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
                        continue;
                }
 
+               if (!strncmp(arg, "--exclude-per-directory=", 24)) {
+                       struct dir_struct *dir;
+
+                       if (opts.dir)
+                               die("more than one --exclude-per-directory are given.");
+
+                       dir = calloc(1, sizeof(*opts.dir));
+                       dir->show_ignored = 1;
+                       dir->exclude_per_dir = arg + 24;
+                       opts.dir = dir;
+                       /* We do not need to nor want to do read-directory
+                        * here; we are merely interested in reusing the
+                        * per directory ignore stack mechanism.
+                        */
+                       continue;
+               }
+
                /* using -u and -i at the same time makes no sense */
                if (1 < opts.index_only + opts.update)
                        usage(read_tree_usage);
@@ -190,6 +208,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
        }
        if ((opts.update||opts.index_only) && !opts.merge)
                usage(read_tree_usage);
+       if ((opts.dir && !opts.update))
+               die("--exclude-per-directory is meaningless unless -u");
 
        if (opts.prefix) {
                int pfxlen = strlen(opts.prefix);
diff --git a/builtin-reflog.c b/builtin-reflog.c
new file mode 100644 (file)
index 0000000..3415551
--- /dev/null
@@ -0,0 +1,387 @@
+#include "cache.h"
+#include "builtin.h"
+#include "commit.h"
+#include "refs.h"
+#include "dir.h"
+#include "tree-walk.h"
+#include "diff.h"
+#include "revision.h"
+#include "reachable.h"
+
+/*
+ * reflog expire
+ */
+
+static const char reflog_expire_usage[] =
+"git-reflog (show|expire) [--verbose] [--dry-run] [--stale-fix] [--expire=<time>] [--expire-unreachable=<time>] [--all] <refs>...";
+
+static unsigned long default_reflog_expire;
+static unsigned long default_reflog_expire_unreachable;
+
+struct cmd_reflog_expire_cb {
+       struct rev_info revs;
+       int dry_run;
+       int stalefix;
+       int verbose;
+       unsigned long expire_total;
+       unsigned long expire_unreachable;
+};
+
+struct expire_reflog_cb {
+       FILE *newlog;
+       const char *ref;
+       struct commit *ref_commit;
+       struct cmd_reflog_expire_cb *cmd;
+};
+
+#define INCOMPLETE     (1u<<10)
+#define STUDYING       (1u<<11)
+
+static int tree_is_complete(const unsigned char *sha1)
+{
+       struct tree_desc desc;
+       struct name_entry entry;
+       int complete;
+       struct tree *tree;
+
+       tree = lookup_tree(sha1);
+       if (!tree)
+               return 0;
+       if (tree->object.flags & SEEN)
+               return 1;
+       if (tree->object.flags & INCOMPLETE)
+               return 0;
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+       if (!desc.buf) {
+               char type[20];
+               void *data = read_sha1_file(sha1, type, &desc.size);
+               if (!data) {
+                       tree->object.flags |= INCOMPLETE;
+                       return 0;
+               }
+               desc.buf = data;
+               tree->buffer = data;
+       }
+       complete = 1;
+       while (tree_entry(&desc, &entry)) {
+               if (!has_sha1_file(entry.sha1) ||
+                   (S_ISDIR(entry.mode) && !tree_is_complete(entry.sha1))) {
+                       tree->object.flags |= INCOMPLETE;
+                       complete = 0;
+               }
+       }
+       free(tree->buffer);
+       tree->buffer = NULL;
+
+       if (complete)
+               tree->object.flags |= SEEN;
+       return complete;
+}
+
+static int commit_is_complete(struct commit *commit)
+{
+       struct object_array study;
+       struct object_array found;
+       int is_incomplete = 0;
+       int i;
+
+       /* early return */
+       if (commit->object.flags & SEEN)
+               return 1;
+       if (commit->object.flags & INCOMPLETE)
+               return 0;
+       /*
+        * Find all commits that are reachable and are not marked as
+        * SEEN.  Then make sure the trees and blobs contained are
+        * complete.  After that, mark these commits also as SEEN.
+        * If some of the objects that are needed to complete this
+        * commit are missing, mark this commit as INCOMPLETE.
+        */
+       memset(&study, 0, sizeof(study));
+       memset(&found, 0, sizeof(found));
+       add_object_array(&commit->object, NULL, &study);
+       add_object_array(&commit->object, NULL, &found);
+       commit->object.flags |= STUDYING;
+       while (study.nr) {
+               struct commit *c;
+               struct commit_list *parent;
+
+               c = (struct commit *)study.objects[--study.nr].item;
+               if (!c->object.parsed && !parse_object(c->object.sha1))
+                       c->object.flags |= INCOMPLETE;
+
+               if (c->object.flags & INCOMPLETE) {
+                       is_incomplete = 1;
+                       break;
+               }
+               else if (c->object.flags & SEEN)
+                       continue;
+               for (parent = c->parents; parent; parent = parent->next) {
+                       struct commit *p = parent->item;
+                       if (p->object.flags & STUDYING)
+                               continue;
+                       p->object.flags |= STUDYING;
+                       add_object_array(&p->object, NULL, &study);
+                       add_object_array(&p->object, NULL, &found);
+               }
+       }
+       if (!is_incomplete) {
+               /*
+                * make sure all commits in "found" array have all the
+                * necessary objects.
+                */
+               for (i = 0; i < found.nr; i++) {
+                       struct commit *c =
+                               (struct commit *)found.objects[i].item;
+                       if (!tree_is_complete(c->tree->object.sha1)) {
+                               is_incomplete = 1;
+                               c->object.flags |= INCOMPLETE;
+                       }
+               }
+               if (!is_incomplete) {
+                       /* mark all found commits as complete, iow SEEN */
+                       for (i = 0; i < found.nr; i++)
+                               found.objects[i].item->flags |= SEEN;
+               }
+       }
+       /* clear flags from the objects we traversed */
+       for (i = 0; i < found.nr; i++)
+               found.objects[i].item->flags &= ~STUDYING;
+       if (is_incomplete)
+               commit->object.flags |= INCOMPLETE;
+       else {
+               /*
+                * If we come here, we have (1) traversed the ancestry chain
+                * from the "commit" until we reach SEEN commits (which are
+                * known to be complete), and (2) made sure that the commits
+                * encountered during the above traversal refer to trees that
+                * are complete.  Which means that we know *all* the commits
+                * we have seen during this process are complete.
+                */
+               for (i = 0; i < found.nr; i++)
+                       found.objects[i].item->flags |= SEEN;
+       }
+       /* free object arrays */
+       free(study.objects);
+       free(found.objects);
+       return !is_incomplete;
+}
+
+static int keep_entry(struct commit **it, unsigned char *sha1)
+{
+       struct commit *commit;
+
+       if (is_null_sha1(sha1))
+               return 1;
+       commit = lookup_commit_reference_gently(sha1, 1);
+       if (!commit)
+               return 0;
+
+       /*
+        * Make sure everything in this commit exists.
+        *
+        * We have walked all the objects reachable from the refs
+        * and cache earlier.  The commits reachable by this commit
+        * must meet SEEN commits -- and then we should mark them as
+        * SEEN as well.
+        */
+       if (!commit_is_complete(commit))
+               return 0;
+       *it = commit;
+       return 1;
+}
+
+static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct expire_reflog_cb *cb = cb_data;
+       struct commit *old, *new;
+
+       if (timestamp < cb->cmd->expire_total)
+               goto prune;
+
+       old = new = NULL;
+       if (cb->cmd->stalefix &&
+           (!keep_entry(&old, osha1) || !keep_entry(&new, nsha1)))
+               goto prune;
+
+       if (timestamp < cb->cmd->expire_unreachable) {
+               if (!cb->ref_commit)
+                       goto prune;
+               if (!old && !is_null_sha1(osha1))
+                       old = lookup_commit_reference_gently(osha1, 1);
+               if (!new && !is_null_sha1(nsha1))
+                       new = lookup_commit_reference_gently(nsha1, 1);
+               if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) ||
+                   (new && !in_merge_bases(new, &cb->ref_commit, 1)))
+                       goto prune;
+       }
+
+       if (cb->newlog) {
+               char sign = (tz < 0) ? '-' : '+';
+               int zone = (tz < 0) ? (-tz) : tz;
+               fprintf(cb->newlog, "%s %s %s %lu %c%04d\t%s",
+                       sha1_to_hex(osha1), sha1_to_hex(nsha1),
+                       email, timestamp, sign, zone,
+                       message);
+       }
+       if (cb->cmd->verbose)
+               printf("keep %s", message);
+       return 0;
+ prune:
+       if (!cb->newlog || cb->cmd->verbose)
+               printf("%sprune %s", cb->newlog ? "" : "would ", message);
+       return 0;
+}
+
+static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, void *cb_data)
+{
+       struct cmd_reflog_expire_cb *cmd = cb_data;
+       struct expire_reflog_cb cb;
+       struct ref_lock *lock;
+       char *log_file, *newlog_path = NULL;
+       int status = 0;
+
+       memset(&cb, 0, sizeof(cb));
+       /* we take the lock for the ref itself to prevent it from
+        * getting updated.
+        */
+       lock = lock_any_ref_for_update(ref, sha1);
+       if (!lock)
+               return error("cannot lock ref '%s'", ref);
+       log_file = xstrdup(git_path("logs/%s", ref));
+       if (!file_exists(log_file))
+               goto finish;
+       if (!cmd->dry_run) {
+               newlog_path = xstrdup(git_path("logs/%s.lock", ref));
+               cb.newlog = fopen(newlog_path, "w");
+       }
+
+       cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
+       cb.ref = ref;
+       cb.cmd = cmd;
+       for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+ finish:
+       if (cb.newlog) {
+               if (fclose(cb.newlog))
+                       status |= error("%s: %s", strerror(errno),
+                                       newlog_path);
+               if (rename(newlog_path, log_file)) {
+                       status |= error("cannot rename %s to %s",
+                                       newlog_path, log_file);
+                       unlink(newlog_path);
+               }
+       }
+       free(newlog_path);
+       free(log_file);
+       unlock_ref(lock);
+       return status;
+}
+
+static int reflog_expire_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "gc.reflogexpire"))
+               default_reflog_expire = approxidate(value);
+       else if (!strcmp(var, "gc.reflogexpireunreachable"))
+               default_reflog_expire_unreachable = approxidate(value);
+       else
+               return git_default_config(var, value);
+       return 0;
+}
+
+static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)
+{
+       struct cmd_reflog_expire_cb cb;
+       unsigned long now = time(NULL);
+       int i, status, do_all;
+
+       git_config(reflog_expire_config);
+
+       save_commit_buffer = 0;
+       do_all = status = 0;
+       memset(&cb, 0, sizeof(cb));
+
+       if (!default_reflog_expire_unreachable)
+               default_reflog_expire_unreachable = now - 30 * 24 * 3600;
+       if (!default_reflog_expire)
+               default_reflog_expire = now - 90 * 24 * 3600;
+       cb.expire_total = default_reflog_expire;
+       cb.expire_unreachable = default_reflog_expire_unreachable;
+
+       /*
+        * We can trust the commits and objects reachable from refs
+        * even in older repository.  We cannot trust what's reachable
+        * from reflog if the repository was pruned with older git.
+        */
+
+       for (i = 1; i < argc; i++) {
+               const char *arg = argv[i];
+               if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n"))
+                       cb.dry_run = 1;
+               else if (!strncmp(arg, "--expire=", 9))
+                       cb.expire_total = approxidate(arg + 9);
+               else if (!strncmp(arg, "--expire-unreachable=", 21))
+                       cb.expire_unreachable = approxidate(arg + 21);
+               else if (!strcmp(arg, "--stale-fix"))
+                       cb.stalefix = 1;
+               else if (!strcmp(arg, "--all"))
+                       do_all = 1;
+               else if (!strcmp(arg, "--verbose"))
+                       cb.verbose = 1;
+               else if (!strcmp(arg, "--")) {
+                       i++;
+                       break;
+               }
+               else if (arg[0] == '-')
+                       usage(reflog_expire_usage);
+               else
+                       break;
+       }
+       if (cb.stalefix) {
+               init_revisions(&cb.revs, prefix);
+               if (cb.verbose)
+                       printf("Marking reachable objects...");
+               mark_reachable_objects(&cb.revs, 0);
+               if (cb.verbose)
+                       putchar('\n');
+       }
+
+       if (do_all)
+               status |= for_each_reflog(expire_reflog, &cb);
+       while (i < argc) {
+               const char *ref = argv[i++];
+               unsigned char sha1[20];
+               if (!resolve_ref(ref, sha1, 1, NULL)) {
+                       status |= error("%s points nowhere!", ref);
+                       continue;
+               }
+               status |= expire_reflog(ref, sha1, 0, &cb);
+       }
+       return status;
+}
+
+/*
+ * main "reflog"
+ */
+
+static const char reflog_usage[] =
+"git-reflog (expire | ...)";
+
+int cmd_reflog(int argc, const char **argv, const char *prefix)
+{
+       /* With no command, we default to showing it. */
+       if (argc < 2 || *argv[1] == '-')
+               return cmd_log_reflog(argc, argv, prefix);
+
+       if (!strcmp(argv[1], "show"))
+               return cmd_log_reflog(argc - 1, argv + 1, prefix);
+
+       if (!strcmp(argv[1], "expire"))
+               return cmd_reflog_expire(argc - 1, argv + 1, prefix);
+
+       /* Not a recognized reflog command..*/
+       usage(reflog_usage);
+}
diff --git a/builtin-rerere.c b/builtin-rerere.c
new file mode 100644 (file)
index 0000000..318d959
--- /dev/null
@@ -0,0 +1,421 @@
+#include "cache.h"
+#include "path-list.h"
+#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
+
+#include <time.h>
+
+static const char git_rerere_usage[] =
+"git-rerere [clear | status | diff | gc]";
+
+/* these values are days */
+static int cutoff_noresolve = 15;
+static int cutoff_resolve = 60;
+
+static char *merge_rr_path;
+
+static const char *rr_path(const char *name, const char *file)
+{
+       return git_path("rr-cache/%s/%s", name, file);
+}
+
+static void read_rr(struct path_list *rr)
+{
+       unsigned char sha1[20];
+       char buf[PATH_MAX];
+       FILE *in = fopen(merge_rr_path, "r");
+       if (!in)
+               return;
+       while (fread(buf, 40, 1, in) == 1) {
+               int i;
+               char *name;
+               if (get_sha1_hex(buf, sha1))
+                       die("corrupt MERGE_RR");
+               buf[40] = '\0';
+               name = xstrdup(buf);
+               if (fgetc(in) != '\t')
+                       die("corrupt MERGE_RR");
+               for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
+                       ; /* do nothing */
+               if (i == sizeof(buf))
+                       die("filename too long");
+               path_list_insert(buf, rr)->util = xstrdup(name);
+       }
+       fclose(in);
+}
+
+static struct lock_file write_lock;
+
+static int write_rr(struct path_list *rr, int out_fd)
+{
+       int i;
+       for (i = 0; i < rr->nr; i++) {
+               const char *path = rr->items[i].path;
+               int length = strlen(path) + 1;
+               if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
+                   write_in_full(out_fd, "\t", 1) != 1 ||
+                   write_in_full(out_fd, path, length) != length)
+                       die("unable to write rerere record");
+       }
+       close(out_fd);
+       return commit_lock_file(&write_lock);
+}
+
+struct buffer {
+       char *ptr;
+       int nr, alloc;
+};
+
+static void append_line(struct buffer *buffer, const char *line)
+{
+       int len = strlen(line);
+
+       if (buffer->nr + len > buffer->alloc) {
+               buffer->alloc = alloc_nr(buffer->nr + len);
+               buffer->ptr = xrealloc(buffer->ptr, buffer->alloc);
+       }
+       memcpy(buffer->ptr + buffer->nr, line, len);
+       buffer->nr += len;
+}
+
+static int handle_file(const char *path,
+        unsigned char *sha1, const char *output)
+{
+       SHA_CTX ctx;
+       char buf[1024];
+       int hunk = 0, hunk_no = 0;
+       struct buffer minus = { NULL, 0, 0 }, plus = { NULL, 0, 0 };
+       struct buffer *one = &minus, *two = &plus;
+       FILE *f = fopen(path, "r");
+       FILE *out;
+
+       if (!f)
+               return error("Could not open %s", path);
+
+       if (output) {
+               out = fopen(output, "w");
+               if (!out) {
+                       fclose(f);
+                       return error("Could not write %s", output);
+               }
+       } else
+               out = NULL;
+
+       if (sha1)
+               SHA1_Init(&ctx);
+
+       while (fgets(buf, sizeof(buf), f)) {
+               if (!strncmp("<<<<<<< ", buf, 8))
+                       hunk = 1;
+               else if (!strncmp("=======", buf, 7))
+                       hunk = 2;
+               else if (!strncmp(">>>>>>> ", buf, 8)) {
+                       hunk_no++;
+                       hunk = 0;
+                       if (memcmp(one->ptr, two->ptr, one->nr < two->nr ?
+                                               one->nr : two->nr) > 0) {
+                               struct buffer *swap = one;
+                               one = two;
+                               two = swap;
+                       }
+                       if (out) {
+                               fputs("<<<<<<<\n", out);
+                               fwrite(one->ptr, one->nr, 1, out);
+                               fputs("=======\n", out);
+                               fwrite(two->ptr, two->nr, 1, out);
+                               fputs(">>>>>>>\n", out);
+                       }
+                       if (sha1) {
+                               SHA1_Update(&ctx, one->ptr, one->nr);
+                               SHA1_Update(&ctx, "\0", 1);
+                               SHA1_Update(&ctx, two->ptr, two->nr);
+                               SHA1_Update(&ctx, "\0", 1);
+                       }
+               } else if (hunk == 1)
+                       append_line(one, buf);
+               else if (hunk == 2)
+                       append_line(two, buf);
+               else if (out)
+                       fputs(buf, out);
+       }
+
+       fclose(f);
+       if (out)
+               fclose(out);
+       if (sha1)
+               SHA1_Final(sha1, &ctx);
+       return hunk_no;
+}
+
+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 &&
+                               ce_stage(e3) == 3 && ce_same_name(e1, e2) &&
+                               ce_same_name(e1, e3)) {
+                       path_list_insert((const char *)e1->name, conflict);
+                       i += 3;
+               }
+       }
+       return 0;
+}
+
+static int merge(const char *name, const char *path)
+{
+       int ret;
+       mmfile_t cur, base, other;
+       mmbuffer_t result = {NULL, 0};
+       xpparam_t xpp = {XDF_NEED_MINIMAL};
+
+       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
+               return 1;
+
+       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
+                       read_mmfile(&base, rr_path(name, "preimage")) ||
+                       read_mmfile(&other, rr_path(name, "postimage")))
+               return 1;
+       ret = xdl_merge(&base, &cur, "", &other, "",
+                       &xpp, XDL_MERGE_ZEALOUS, &result);
+       if (!ret) {
+               FILE *f = fopen(path, "w");
+               if (!f)
+                       return error("Could not write to %s", path);
+               fwrite(result.ptr, result.size, 1, f);
+               fclose(f);
+       }
+
+       free(cur.ptr);
+       free(base.ptr);
+       free(other.ptr);
+       free(result.ptr);
+
+       return ret;
+}
+
+static void unlink_rr_item(const char *name)
+{
+       unlink(rr_path(name, "thisimage"));
+       unlink(rr_path(name, "preimage"));
+       unlink(rr_path(name, "postimage"));
+       rmdir(git_path("rr-cache/%s", name));
+}
+
+static void garbage_collect(struct path_list *rr)
+{
+       struct path_list to_remove = { NULL, 0, 0, 1 };
+       char buf[1024];
+       DIR *dir;
+       struct dirent *e;
+       int len, i, cutoff;
+       time_t now = time(NULL), then;
+
+       strlcpy(buf, git_path("rr-cache"), sizeof(buf));
+       len = strlen(buf);
+       dir = opendir(buf);
+       strcpy(buf + len++, "/");
+       while ((e = readdir(dir))) {
+               const char *name = e->d_name;
+               struct stat st;
+               if (name[0] == '.' && (name[1] == '\0' ||
+                                       (name[1] == '.' && name[2] == '\0')))
+                       continue;
+               i = snprintf(buf + len, sizeof(buf) - len, "%s", name);
+               strlcpy(buf + len + i, "/preimage", sizeof(buf) - len - i);
+               if (stat(buf, &st))
+                       continue;
+               then = st.st_mtime;
+               strlcpy(buf + len + i, "/postimage", sizeof(buf) - len - i);
+               cutoff = stat(buf, &st) ? cutoff_noresolve : cutoff_resolve;
+               if (then < now - cutoff * 86400) {
+                       buf[len + i] = '\0';
+                       path_list_insert(xstrdup(name), &to_remove);
+               }
+       }
+       for (i = 0; i < to_remove.nr; i++)
+               unlink_rr_item(to_remove.items[i].path);
+       path_list_clear(&to_remove, 0);
+}
+
+static int outf(void *dummy, mmbuffer_t *ptr, int nbuf)
+{
+       int i;
+       for (i = 0; i < nbuf; i++)
+               if (write_in_full(1, ptr[i].ptr, ptr[i].size) != ptr[i].size)
+                       return -1;
+       return 0;
+}
+
+static int diff_two(const char *file1, const char *label1,
+               const char *file2, const char *label2)
+{
+       xpparam_t xpp;
+       xdemitconf_t xecfg;
+       xdemitcb_t ecb;
+       mmfile_t minus, plus;
+
+       if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
+               return 1;
+
+       printf("--- a/%s\n+++ b/%s\n", label1, label2);
+       fflush(stdout);
+       xpp.flags = XDF_NEED_MINIMAL;
+       xecfg.ctxlen = 3;
+       xecfg.flags = 0;
+       ecb.outf = outf;
+       xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+
+       free(minus.ptr);
+       free(plus.ptr);
+       return 0;
+}
+
+static int copy_file(const char *src, const char *dest)
+{
+       FILE *in, *out;
+       char buffer[32768];
+       int count;
+
+       if (!(in = fopen(src, "r")))
+               return error("Could not open %s", src);
+       if (!(out = fopen(dest, "w")))
+               return error("Could not open %s", dest);
+       while ((count = fread(buffer, 1, sizeof(buffer), in)))
+               fwrite(buffer, 1, count, out);
+       fclose(in);
+       fclose(out);
+       return 0;
+}
+
+static int do_plain_rerere(struct path_list *rr, int fd)
+{
+       struct path_list conflict = { NULL, 0, 0, 1 };
+       int i;
+
+       find_conflict(&conflict);
+
+       /*
+        * MERGE_RR records paths with conflicts immediately after merge
+        * failed.  Some of the conflicted paths might have been hand resolved
+        * in the working tree since then, but the initial run would catch all
+        * and register their preimages.
+        */
+
+       for (i = 0; i < conflict.nr; i++) {
+               const char *path = conflict.items[i].path;
+               if (!path_list_has_path(rr, path)) {
+                       unsigned char sha1[20];
+                       char *hex;
+                       int ret;
+                       ret = handle_file(path, sha1, NULL);
+                       if (ret < 1)
+                               continue;
+                       hex = xstrdup(sha1_to_hex(sha1));
+                       path_list_insert(path, rr)->util = hex;
+                       if (mkdir(git_path("rr-cache/%s", hex), 0755))
+                               continue;;
+                       handle_file(path, NULL, rr_path(hex, "preimage"));
+                       fprintf(stderr, "Recorded preimage for '%s'\n", path);
+               }
+       }
+
+       /*
+        * Now some of the paths that had conflicts earlier might have been
+        * hand resolved.  Others may be similar to a conflict already that
+        * was resolved before.
+        */
+
+       for (i = 0; i < rr->nr; i++) {
+               struct stat st;
+               int ret;
+               const char *path = rr->items[i].path;
+               const char *name = (const char *)rr->items[i].util;
+
+               if (!stat(rr_path(name, "preimage"), &st) &&
+                               !stat(rr_path(name, "postimage"), &st)) {
+                       if (!merge(name, path)) {
+                               fprintf(stderr, "Resolved '%s' using "
+                                               "previous resolution.\n", path);
+                               goto tail_optimization;
+                       }
+               }
+
+               /* Let's see if we have resolved it. */
+               ret = handle_file(path, NULL, NULL);
+               if (ret)
+                       continue;
+
+               fprintf(stderr, "Recorded resolution for '%s'.\n", path);
+               copy_file(path, rr_path(name, "postimage"));
+tail_optimization:
+               if (i < rr->nr - 1)
+                       memmove(rr->items + i,
+                               rr->items + i + 1,
+                               sizeof(rr->items[0]) * (rr->nr - i - 1));
+               rr->nr--;
+               i--;
+       }
+
+       return write_rr(rr, fd);
+}
+
+static int git_rerere_config(const char *var, const char *value)
+{
+       if (!strcmp(var, "gc.rerereresolved"))
+               cutoff_resolve = git_config_int(var, value);
+       else if (!strcmp(var, "gc.rerereunresolved"))
+               cutoff_noresolve = git_config_int(var, value);
+       else
+               return git_default_config(var, value);
+       return 0;
+}
+
+int cmd_rerere(int argc, const char **argv, const char *prefix)
+{
+       struct path_list merge_rr = { NULL, 0, 0, 1 };
+       int i, fd = -1;
+       struct stat st;
+
+       if (stat(git_path("rr-cache"), &st) || !S_ISDIR(st.st_mode))
+               return 0;
+
+       git_config(git_rerere_config);
+
+       merge_rr_path = xstrdup(git_path("rr-cache/MERGE_RR"));
+       fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
+       read_rr(&merge_rr);
+
+       if (argc < 2)
+               return do_plain_rerere(&merge_rr, fd);
+       else if (!strcmp(argv[1], "clear")) {
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       if (!stat(git_path("rr-cache/%s", name), &st) &&
+                                       S_ISDIR(st.st_mode) &&
+                                       stat(rr_path(name, "postimage"), &st))
+                               unlink_rr_item(name);
+               }
+               unlink(merge_rr_path);
+       } else if (!strcmp(argv[1], "gc"))
+               garbage_collect(&merge_rr);
+       else if (!strcmp(argv[1], "status"))
+               for (i = 0; i < merge_rr.nr; i++)
+                       printf("%s\n", merge_rr.items[i].path);
+       else if (!strcmp(argv[1], "diff"))
+               for (i = 0; i < merge_rr.nr; i++) {
+                       const char *path = merge_rr.items[i].path;
+                       const char *name = (const char *)merge_rr.items[i].util;
+                       diff_two(rr_path(name, "preimage"), path, path, path);
+               }
+       else
+               usage(git_rerere_usage);
+
+       path_list_clear(&merge_rr, 1);
+       return 0;
+}
+
index fb7fc92145b6a8baec340176abf40c6b37f45a8e..1bb3a06680194ce5855185ad1a1c7e39eeebba94 100644 (file)
@@ -54,6 +54,12 @@ static void show_commit(struct commit *commit)
                fputs(header_prefix, stdout);
        if (commit->object.flags & BOUNDARY)
                putchar('-');
+       else if (revs.left_right) {
+               if (commit->object.flags & SYMMETRIC_LEFT)
+                       putchar('<');
+               else
+                       putchar('>');
+       }
        if (revs.abbrev_commit && revs.abbrev)
                fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
                      stdout);
index 3b716fba133ae2f0375ef73d6a50a4d93112b5e4..d53deaa3691c7b57e215f3f33c44881c4f397527 100644 (file)
@@ -347,6 +347,11 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)
                                printf("%s/.git\n", cwd);
                                continue;
                        }
+                       if (!strcmp(arg, "--is-inside-git-dir")) {
+                               printf("%s\n", is_inside_git_dir() ? "true"
+                                               : "false");
+                               continue;
+                       }
                        if (!strncmp(arg, "--since=", 8)) {
                                show_datestring("--max-age=", arg+8);
                                continue;
index 33d04bd015e43965a1bc44bb281908298f152f6c..00dbe399609051647f393e0c543a862a0ba432c8 100644 (file)
@@ -7,9 +7,10 @@
 #include "builtin.h"
 #include "dir.h"
 #include "cache-tree.h"
+#include "tree-walk.h"
 
 static const char builtin_rm_usage[] =
-"git-rm [-n] [-v] [-f] <filepattern>...";
+"git-rm [-f] [-n] [-r] [--cached] [--] <file>...";
 
 static struct {
        int nr, alloc;
@@ -31,6 +32,10 @@ static int remove_file(const char *name)
        char *slash;
 
        ret = unlink(name);
+       if (ret && errno == ENOENT)
+               /* The user has removed it from the filesystem by hand */
+               ret = errno = 0;
+
        if (!ret && (slash = strrchr(name, '/'))) {
                char *n = xstrdup(name);
                do {
@@ -41,12 +46,75 @@ static int remove_file(const char *name)
        return ret;
 }
 
+static int check_local_mod(unsigned char *head)
+{
+       /* items in list are already sorted in the cache order,
+        * so we could do this a lot more efficiently by using
+        * tree_desc based traversal if we wanted to, but I am
+        * lazy, and who cares if removal of files is a tad
+        * slower than the theoretical maximum speed?
+        */
+       int i, no_head;
+       int errs = 0;
+
+       no_head = is_null_sha1(head);
+       for (i = 0; i < list.nr; i++) {
+               struct stat st;
+               int pos;
+               struct cache_entry *ce;
+               const char *name = list.name[i];
+               unsigned char sha1[20];
+               unsigned mode;
+
+               pos = cache_name_pos(name, strlen(name));
+               if (pos < 0)
+                       continue; /* removing unmerged entry */
+               ce = active_cache[pos];
+
+               if (lstat(ce->name, &st) < 0) {
+                       if (errno != ENOENT)
+                               fprintf(stderr, "warning: '%s': %s",
+                                       ce->name, strerror(errno));
+                       /* It already vanished from the working tree */
+                       continue;
+               }
+               else if (S_ISDIR(st.st_mode)) {
+                       /* if a file was removed and it is now a
+                        * directory, that is the same as ENOENT as
+                        * far as git is concerned; we do not track
+                        * directories.
+                        */
+                       continue;
+               }
+               if (ce_match_stat(ce, &st, 0))
+                       errs = error("'%s' has local modifications "
+                                    "(hint: try -f)", ce->name);
+               if (no_head)
+                       continue;
+               /*
+                * It is Ok to remove a newly added path, as long as
+                * it is cache-clean.
+                */
+               if (get_tree_entry(head, name, sha1, &mode))
+                       continue;
+               /*
+                * Otherwise make sure the version from the HEAD
+                * matches the index.
+                */
+               if (ce->ce_mode != create_ce_mode(mode) ||
+                   hashcmp(ce->sha1, sha1))
+                       errs = error("'%s' has changes staged in the index "
+                                    "(hint: try -f)", name);
+       }
+       return errs;
+}
+
 static struct lock_file lock_file;
 
 int cmd_rm(int argc, const char **argv, const char *prefix)
 {
        int i, newfd;
-       int verbose = 0, show_only = 0, force = 0;
+       int show_only = 0, force = 0, index_only = 0, recursive = 0;
        const char **pathspec;
        char *seen;
 
@@ -62,23 +130,20 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
                if (*arg != '-')
                        break;
-               if (!strcmp(arg, "--")) {
+               else if (!strcmp(arg, "--")) {
                        i++;
                        break;
                }
-               if (!strcmp(arg, "-n")) {
+               else if (!strcmp(arg, "-n"))
                        show_only = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-v")) {
-                       verbose = 1;
-                       continue;
-               }
-               if (!strcmp(arg, "-f")) {
+               else if (!strcmp(arg, "--cached"))
+                       index_only = 1;
+               else if (!strcmp(arg, "-f"))
                        force = 1;
-                       continue;
-               }
-               usage(builtin_rm_usage);
+               else if (!strcmp(arg, "-r"))
+                       recursive = 1;
+               else
+                       usage(builtin_rm_usage);
        }
        if (argc <= i)
                usage(builtin_rm_usage);
@@ -99,14 +164,36 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
        if (pathspec) {
                const char *match;
                for (i = 0; (match = pathspec[i]) != NULL ; i++) {
-                       if (*match && !seen[i])
-                               die("pathspec '%s' did not match any files", match);
+                       if (!seen[i])
+                               die("pathspec '%s' did not match any files",
+                                   match);
+                       if (!recursive && seen[i] == MATCHED_RECURSIVELY)
+                               die("not removing '%s' recursively without -r",
+                                   *match ? match : ".");
                }
        }
 
+       /*
+        * If not forced, the file, the index and the HEAD (if exists)
+        * must match; but the file can already been removed, since
+        * this sequence is a natural "novice" way:
+        *
+        *      rm F; git fm F
+        *
+        * Further, if HEAD commit exists, "diff-index --cached" must
+        * report no changes unless forced.
+        */
+       if (!force) {
+               unsigned char sha1[20];
+               if (get_sha1("HEAD", sha1))
+                       hashclr(sha1);
+               if (check_local_mod(sha1))
+                       exit(1);
+       }
+
        /*
         * First remove the names from the index: we won't commit
-        * the index unless all of them succeed
+        * the index unless all of them succeed.
         */
        for (i = 0; i < list.nr; i++) {
                const char *path = list.name[i];
@@ -121,14 +208,14 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
                return 0;
 
        /*
-        * Then, if we used "-f", remove the filenames from the
-        * workspace. If we fail to remove the first one, we
+        * Then, unless we used "--cached", remove the filenames from
+        * the workspace. If we fail to remove the first one, we
         * abort the "git rm" (but once we've successfully removed
         * any file at all, we'll go ahead and commit to it all:
         * by then we've already committed ourselves and can't fail
         * in the middle)
         */
-       if (force) {
+       if (!index_only) {
                int removed = 0;
                for (i = 0; i < list.nr; i++) {
                        const char *path = list.name[i];
index 0b63037dd04e7c66d2e378ee394979cec040fe55..4b489b1214e1ae693a9b0b5166909800f5ca94ac 100644 (file)
@@ -1,5 +1,5 @@
-#include "wt-status.h"
 #include "cache.h"
+#include "wt-status.h"
 
 extern int wt_status_use_color;
 
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
new file mode 100644 (file)
index 0000000..edb4042
--- /dev/null
@@ -0,0 +1,341 @@
+#include "builtin.h"
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "path-list.h"
+#include "revision.h"
+
+static const char shortlog_usage[] =
+"git-shortlog [-n] [-s] [<commit-id>... ]";
+
+static char *common_repo_prefix;
+
+static int compare_by_number(const void *a1, const void *a2)
+{
+       const struct path_list_item *i1 = a1, *i2 = a2;
+       const struct path_list *l1 = i1->util, *l2 = i2->util;
+
+       if (l1->nr < l2->nr)
+               return 1;
+       else if (l1->nr == l2->nr)
+               return 0;
+       else
+               return -1;
+}
+
+static struct path_list mailmap = {NULL, 0, 0, 0};
+
+static int read_mailmap(const char *filename)
+{
+       char buffer[1024];
+       FILE *f = fopen(filename, "r");
+
+       if (f == NULL)
+               return 1;
+       while (fgets(buffer, sizeof(buffer), f) != NULL) {
+               char *end_of_name, *left_bracket, *right_bracket;
+               char *name, *email;
+               int i;
+               if (buffer[0] == '#') {
+                       static const char abbrev[] = "# repo-abbrev:";
+                       int abblen = sizeof(abbrev) - 1;
+                       int len = strlen(buffer);
+
+                       if (len && buffer[len - 1] == '\n')
+                               buffer[--len] = 0;
+                       if (!strncmp(buffer, abbrev, abblen)) {
+                               char *cp;
+
+                               if (common_repo_prefix)
+                                       free(common_repo_prefix);
+                               common_repo_prefix = xmalloc(len);
+
+                               for (cp = buffer + abblen; isspace(*cp); cp++)
+                                       ; /* nothing */
+                               strcpy(common_repo_prefix, cp);
+                       }
+                       continue;
+               }
+               if ((left_bracket = strchr(buffer, '<')) == NULL)
+                       continue;
+               if ((right_bracket = strchr(left_bracket + 1, '>')) == NULL)
+                       continue;
+               if (right_bracket == left_bracket + 1)
+                       continue;
+               for (end_of_name = left_bracket; end_of_name != buffer
+                               && isspace(end_of_name[-1]); end_of_name--)
+                       /* keep on looking */
+               if (end_of_name == buffer)
+                       continue;
+               name = xmalloc(end_of_name - buffer + 1);
+               strlcpy(name, buffer, end_of_name - buffer + 1);
+               email = xmalloc(right_bracket - left_bracket);
+               for (i = 0; i < right_bracket - left_bracket - 1; i++)
+                       email[i] = tolower(left_bracket[i + 1]);
+               email[right_bracket - left_bracket - 1] = '\0';
+               path_list_insert(email, &mailmap)->util = name;
+       }
+       fclose(f);
+       return 0;
+}
+
+static int map_email(char *email, char *name, int maxlen)
+{
+       char *p;
+       struct path_list_item *item;
+
+       /* autocomplete common developers */
+       p = strchr(email, '>');
+       if (!p)
+               return 0;
+
+       *p = '\0';
+       /* downcase the email address */
+       for (p = email; *p; p++)
+               *p = tolower(*p);
+       item = path_list_lookup(email, &mailmap);
+       if (item != NULL) {
+               const char *realname = (const char *)item->util;
+               strncpy(name, realname, maxlen);
+               return 1;
+       }
+       return 0;
+}
+
+static void insert_author_oneline(struct path_list *list,
+               const char *author, int authorlen,
+               const char *oneline, int onelinelen)
+{
+       const char *dot3 = common_repo_prefix;
+       char *buffer, *p;
+       struct path_list_item *item;
+       struct path_list *onelines;
+
+       while (authorlen > 0 && isspace(author[authorlen - 1]))
+               authorlen--;
+
+       buffer = xmalloc(authorlen + 1);
+       memcpy(buffer, author, authorlen);
+       buffer[authorlen] = '\0';
+
+       item = path_list_insert(buffer, list);
+       if (item->util == NULL)
+               item->util = xcalloc(1, sizeof(struct path_list));
+       else
+               free(buffer);
+
+       if (!strncmp(oneline, "[PATCH", 6)) {
+               char *eob = strchr(oneline, ']');
+
+               if (eob) {
+                       while (isspace(eob[1]) && eob[1] != '\n')
+                               eob++;
+                       if (eob - oneline < onelinelen) {
+                               onelinelen -= eob - oneline;
+                               oneline = eob;
+                       }
+               }
+       }
+
+       while (onelinelen > 0 && isspace(oneline[0])) {
+               oneline++;
+               onelinelen--;
+       }
+
+       while (onelinelen > 0 && isspace(oneline[onelinelen - 1]))
+               onelinelen--;
+
+       buffer = xmalloc(onelinelen + 1);
+       memcpy(buffer, oneline, onelinelen);
+       buffer[onelinelen] = '\0';
+
+       if (dot3) {
+               int dot3len = strlen(dot3);
+               if (dot3len > 5) {
+                       while ((p = strstr(buffer, dot3)) != NULL) {
+                               int taillen = strlen(p) - dot3len;
+                               memcpy(p, "/.../", 5);
+                               memmove(p + 5, p + dot3len, taillen + 1);
+                       }
+               }
+       }
+
+       onelines = item->util;
+       if (onelines->nr >= onelines->alloc) {
+               onelines->alloc = alloc_nr(onelines->nr);
+               onelines->items = xrealloc(onelines->items,
+                               onelines->alloc
+                               * sizeof(struct path_list_item));
+       }
+
+       onelines->items[onelines->nr].util = NULL;
+       onelines->items[onelines->nr++].path = buffer;
+}
+
+static void read_from_stdin(struct path_list *list)
+{
+       char buffer[1024];
+
+       while (fgets(buffer, sizeof(buffer), stdin) != NULL) {
+               char *bob;
+               if ((buffer[0] == 'A' || buffer[0] == 'a') &&
+                               !strncmp(buffer + 1, "uthor: ", 7) &&
+                               (bob = strchr(buffer + 7, '<')) != NULL) {
+                       char buffer2[1024], offset = 0;
+
+                       if (map_email(bob + 1, buffer, sizeof(buffer)))
+                               bob = buffer + strlen(buffer);
+                       else {
+                               offset = 8;
+                               while (buffer + offset < bob &&
+                                      isspace(bob[-1]))
+                                       bob--;
+                       }
+
+                       while (fgets(buffer2, sizeof(buffer2), stdin) &&
+                                       buffer2[0] != '\n')
+                               ; /* chomp input */
+                       if (fgets(buffer2, sizeof(buffer2), stdin)) {
+                               int l2 = strlen(buffer2);
+                               int i;
+                               for (i = 0; i < l2; i++)
+                                       if (!isspace(buffer2[i]))
+                                               break;
+                               insert_author_oneline(list,
+                                               buffer + offset,
+                                               bob - buffer - offset,
+                                               buffer2 + i, l2 - i);
+                       }
+               }
+       }
+}
+
+static void get_from_rev(struct rev_info *rev, struct path_list *list)
+{
+       char scratch[1024];
+       struct commit *commit;
+
+       prepare_revision_walk(rev);
+       while ((commit = get_revision(rev)) != NULL) {
+               char *author = NULL, *oneline, *buffer;
+               int authorlen = authorlen, onelinelen;
+
+               /* get author and oneline */
+               for (buffer = commit->buffer; buffer && *buffer != '\0' &&
+                               *buffer != '\n'; ) {
+                       char *eol = strchr(buffer, '\n');
+
+                       if (eol == NULL)
+                               eol = buffer + strlen(buffer);
+                       else
+                               eol++;
+
+                       if (!strncmp(buffer, "author ", 7)) {
+                               char *bracket = strchr(buffer, '<');
+
+                               if (bracket == NULL || bracket > eol)
+                                       die("Invalid commit buffer: %s",
+                                           sha1_to_hex(commit->object.sha1));
+
+                               if (map_email(bracket + 1, scratch,
+                                                       sizeof(scratch))) {
+                                       author = scratch;
+                                       authorlen = strlen(scratch);
+                               } else {
+                                       if (bracket[-1] == ' ')
+                                               bracket--;
+
+                                       author = buffer + 7;
+                                       authorlen = bracket - buffer - 7;
+                               }
+                       }
+                       buffer = eol;
+               }
+
+               if (author == NULL)
+                       die ("Missing author: %s",
+                                       sha1_to_hex(commit->object.sha1));
+
+               if (buffer == NULL || *buffer == '\0') {
+                       oneline = "<none>";
+                       onelinelen = sizeof(oneline) + 1;
+               } else {
+                       char *eol;
+
+                       oneline = buffer + 1;
+                       eol = strchr(oneline, '\n');
+                       if (eol == NULL)
+                               onelinelen = strlen(oneline);
+                       else
+                               onelinelen = eol - oneline;
+               }
+
+               insert_author_oneline(list,
+                               author, authorlen, oneline, onelinelen);
+       }
+
+}
+
+int cmd_shortlog(int argc, const char **argv, const char *prefix)
+{
+       struct rev_info rev;
+       struct path_list list = { NULL, 0, 0, 1 };
+       int i, j, sort_by_number = 0, summary = 0;
+
+       /* since -n is a shadowed rev argument, parse our args first */
+       while (argc > 1) {
+               if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
+                       sort_by_number = 1;
+               else if (!strcmp(argv[1], "-s") ||
+                               !strcmp(argv[1], "--summary"))
+                       summary = 1;
+               else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
+                       usage(shortlog_usage);
+               else
+                       break;
+               argv++;
+               argc--;
+       }
+       init_revisions(&rev, prefix);
+       argc = setup_revisions(argc, argv, &rev, NULL);
+       if (argc > 1)
+               die ("unrecognized argument: %s", argv[1]);
+
+       if (!access(".mailmap", R_OK))
+               read_mailmap(".mailmap");
+
+       if (rev.pending.nr == 0)
+               read_from_stdin(&list);
+       else
+               get_from_rev(&rev, &list);
+
+       if (sort_by_number)
+               qsort(list.items, list.nr, sizeof(struct path_list_item),
+                       compare_by_number);
+
+       for (i = 0; i < list.nr; i++) {
+               struct path_list *onelines = list.items[i].util;
+
+               if (summary) {
+                       printf("%s: %d\n", list.items[i].path, onelines->nr);
+               } else {
+                       printf("%s (%d):\n", list.items[i].path, onelines->nr);
+                       for (j = onelines->nr - 1; j >= 0; j--)
+                               printf("      %s\n", onelines->items[j].path);
+                       printf("\n");
+               }
+
+               onelines->strdup_paths = 1;
+               path_list_clear(onelines, 1);
+               free(onelines);
+               list.items[i].util = NULL;
+       }
+
+       list.strdup_paths = 1;
+       path_list_clear(&list, 1);
+       mailmap.strdup_paths = 1;
+       path_list_clear(&mailmap, 1);
+
+       return 0;
+}
+
index fb1a4000d98e27389904578309b935cbda2a85fe..0d94e40df893ee6ecf146e8987c0696cb4874a36 100644 (file)
@@ -1,12 +1,12 @@
-#include <stdlib.h>
-#include <fnmatch.h>
 #include "cache.h"
 #include "commit.h"
 #include "refs.h"
 #include "builtin.h"
 
 static const char show_branch_usage[] =
-"git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
+"git-show-branch [--sparse] [--current] [--all] [--remotes] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...] | --reflog[=n[,b]] <branch>";
+static const char show_branch_usage_reflog[] =
+"--reflog is incompatible with --all, --remotes, --independent or --merge-base";
 
 static int default_num;
 static int default_alloc;
@@ -17,6 +17,8 @@ static const char **default_arg;
 #define REV_SHIFT       2
 #define MAX_REVS       (FLAG_BITS - REV_SHIFT) /* should not exceed bits_per_int - REV_SHIFT */
 
+#define DEFAULT_REFLOG 4
+
 static struct commit *interesting(struct commit_list *list)
 {
        while (list) {
@@ -346,18 +348,21 @@ static void sort_ref_range(int bottom, int top)
              compare_ref_name);
 }
 
-static int append_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+static int append_ref(const char *refname, const unsigned char *sha1,
+                     int allow_dups)
 {
        struct commit *commit = lookup_commit_reference_gently(sha1, 1);
        int i;
 
        if (!commit)
                return 0;
-       /* Avoid adding the same thing twice */
-       for (i = 0; i < ref_name_cnt; i++)
-               if (!strcmp(refname, ref_name[i]))
-                       return 0;
 
+       if (!allow_dups) {
+               /* Avoid adding the same thing twice */
+               for (i = 0; i < ref_name_cnt; i++)
+                       if (!strcmp(refname, ref_name[i]))
+                               return 0;
+       }
        if (MAX_REVS <= ref_name_cnt) {
                fprintf(stderr, "warning: ignoring %s; "
                        "cannot handle more than %d refs\n",
@@ -380,14 +385,28 @@ static int append_head_ref(const char *refname, const unsigned char *sha1, int f
         */
        if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
                ofs = 5;
-       return append_ref(refname + ofs, sha1, flag, cb_data);
+       return append_ref(refname + ofs, sha1, 0);
+}
+
+static int append_remote_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
+{
+       unsigned char tmp[20];
+       int ofs = 13;
+       if (strncmp(refname, "refs/remotes/", ofs))
+               return 0;
+       /* If both heads/foo and tags/foo exists, get_sha1 would
+        * get confused.
+        */
+       if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1))
+               ofs = 5;
+       return append_ref(refname + ofs, sha1, 0);
 }
 
 static int append_tag_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
        if (strncmp(refname, "refs/tags/", 10))
                return 0;
-       return append_ref(refname + 5, sha1, flag, cb_data);
+       return append_ref(refname + 5, sha1, 0);
 }
 
 static const char *match_ref_pattern = NULL;
@@ -420,19 +439,19 @@ static int append_matching_ref(const char *refname, const unsigned char *sha1, i
                return append_head_ref(refname, sha1, flag, cb_data);
        if (!strncmp("refs/tags/", refname, 10))
                return append_tag_ref(refname, sha1, flag, cb_data);
-       return append_ref(refname, sha1, flag, cb_data);
+       return append_ref(refname, sha1, 0);
 }
 
-static void snarf_refs(int head, int tag)
+static void snarf_refs(int head, int remotes)
 {
        if (head) {
                int orig_cnt = ref_name_cnt;
                for_each_ref(append_head_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
-       if (tag) {
+       if (remotes) {
                int orig_cnt = ref_name_cnt;
-               for_each_ref(append_tag_ref, NULL);
+               for_each_ref(append_remote_ref, NULL);
                sort_ref_range(orig_cnt, ref_name_cnt);
        }
 }
@@ -493,7 +512,7 @@ static void append_one_rev(const char *av)
 {
        unsigned char revkey[20];
        if (!get_sha1(av, revkey)) {
-               append_ref(av, revkey, 0, NULL);
+               append_ref(av, revkey, 0);
                return;
        }
        if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) {
@@ -548,13 +567,28 @@ static int omit_in_dense(struct commit *commit, struct commit **rev, int n)
        return 0;
 }
 
+static void parse_reflog_param(const char *arg, int *cnt, const char **base)
+{
+       char *ep;
+       *cnt = strtoul(arg, &ep, 10);
+       if (*ep == ',')
+               *base = ep + 1;
+       else if (*ep)
+               die("unrecognized reflog param '%s'", arg + 9);
+       else
+               *base = NULL;
+       if (*cnt <= 0)
+               *cnt = DEFAULT_REFLOG;
+}
+
 int cmd_show_branch(int ac, const char **av, const char *prefix)
 {
        struct commit *rev[MAX_REVS], *commit;
+       char *reflog_msg[MAX_REVS];
        struct commit_list *list = NULL, *seen = NULL;
        unsigned int rev_mask[MAX_REVS];
        int num_rev, i, extra = 0;
-       int all_heads = 0, all_tags = 0;
+       int all_heads = 0, all_remotes = 0;
        int all_mask, all_revs;
        int lifo = 1;
        char head[128];
@@ -570,6 +604,8 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
        int head_at = -1;
        int topics = 0;
        int dense = 1;
+       int reflog = 0;
+       const char *reflog_base = NULL;
 
        git_config(git_show_branch_config);
 
@@ -585,12 +621,10 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        ac--; av++;
                        break;
                }
-               else if (!strcmp(arg, "--all"))
-                       all_heads = all_tags = 1;
-               else if (!strcmp(arg, "--heads"))
-                       all_heads = 1;
-               else if (!strcmp(arg, "--tags"))
-                       all_tags = 1;
+               else if (!strcmp(arg, "--all") || !strcmp(arg, "-a"))
+                       all_heads = all_remotes = 1;
+               else if (!strcmp(arg, "--remotes") || !strcmp(arg, "-r"))
+                       all_remotes = 1;
                else if (!strcmp(arg, "--more"))
                        extra = 1;
                else if (!strcmp(arg, "--list"))
@@ -615,25 +649,109 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                        dense = 0;
                else if (!strcmp(arg, "--date-order"))
                        lifo = 0;
+               else if (!strcmp(arg, "--reflog") || !strcmp(arg, "-g")) {
+                       reflog = DEFAULT_REFLOG;
+               }
+               else if (!strncmp(arg, "--reflog=", 9))
+                       parse_reflog_param(arg + 9, &reflog, &reflog_base);
+               else if (!strncmp(arg, "-g=", 3))
+                       parse_reflog_param(arg + 3, &reflog, &reflog_base);
                else
                        usage(show_branch_usage);
                ac--; av++;
        }
        ac--; av++;
 
-       /* Only one of these is allowed */
-       if (1 < independent + merge_base + (extra != 0))
-               usage(show_branch_usage);
+       if (extra || reflog) {
+               /* "listing" mode is incompatible with
+                * independent nor merge-base modes.
+                */
+               if (independent || merge_base)
+                       usage(show_branch_usage);
+               if (reflog && ((0 < extra) || all_heads || all_remotes))
+                       /*
+                        * Asking for --more in reflog mode does not
+                        * make sense.  --list is Ok.
+                        *
+                        * Also --all and --remotes do not make sense either.
+                        */
+                       usage(show_branch_usage_reflog);
+       }
 
        /* If nothing is specified, show all branches by default */
-       if (ac + all_heads + all_tags == 0)
+       if (ac + all_heads + all_remotes == 0)
                all_heads = 1;
 
-       if (all_heads + all_tags)
-               snarf_refs(all_heads, all_tags);
-       while (0 < ac) {
-               append_one_rev(*av);
-               ac--; av++;
+       if (reflog) {
+               unsigned char sha1[20];
+               char nth_desc[256];
+               char *ref;
+               int base = 0;
+
+               if (ac == 0) {
+                       static const char *fake_av[2];
+                       const char *refname;
+
+                       refname = resolve_ref("HEAD", sha1, 1, NULL);
+                       fake_av[0] = xstrdup(refname);
+                       fake_av[1] = NULL;
+                       av = fake_av;
+                       ac = 1;
+               }
+               if (ac != 1)
+                       die("--reflog option needs one branch name");
+
+               if (MAX_REVS < reflog)
+                       die("Only %d entries can be shown at one time.",
+                           MAX_REVS);
+               if (!dwim_ref(*av, strlen(*av), sha1, &ref))
+                       die("No such ref %s", *av);
+
+               /* Has the base been specified? */
+               if (reflog_base) {
+                       char *ep;
+                       base = strtoul(reflog_base, &ep, 10);
+                       if (*ep) {
+                               /* Ah, that is a date spec... */
+                               unsigned long at;
+                               at = approxidate(reflog_base);
+                               read_ref_at(ref, at, -1, sha1, NULL,
+                                           NULL, NULL, &base);
+                       }
+               }
+
+               for (i = 0; i < reflog; i++) {
+                       char *logmsg, *msg, *m;
+                       unsigned long timestamp;
+                       int tz;
+
+                       if (read_ref_at(ref, 0, base+i, sha1, &logmsg,
+                                       &timestamp, &tz, NULL)) {
+                               reflog = i;
+                               break;
+                       }
+                       msg = strchr(logmsg, '\t');
+                       if (!msg)
+                               msg = "(none)";
+                       else
+                               msg++;
+                       m = xmalloc(strlen(msg) + 200);
+                       sprintf(m, "(%s) %s",
+                               show_date(timestamp, tz, 1),
+                               msg);
+                       reflog_msg[i] = m;
+                       free(logmsg);
+                       sprintf(nth_desc, "%s@{%d}", *av, base+i);
+                       append_ref(nth_desc, sha1, 1);
+               }
+       }
+       else if (all_heads + all_remotes)
+               snarf_refs(all_heads, all_remotes);
+       else {
+               while (0 < ac) {
+                       append_one_rev(*av);
+                       ac--; av++;
+               }
        }
 
        head_p = resolve_ref("HEAD", head_sha1, 1, NULL);
@@ -725,8 +843,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix)
                                printf("%c [%s] ",
                                       is_head ? '*' : '!', ref_name[i]);
                        }
-                       /* header lines never need name */
-                       show_one_commit(rev[i], 1);
+
+                       if (!reflog) {
+                               /* header lines never need name */
+                               show_one_commit(rev[i], 1);
+                       }
+                       else
+                               puts(reflog_msg[i]);
+
                        if (is_head)
                                head_at = i;
                }
index 06ec400d7f3f2e589111a561a6b18da067a02f1f..853f13f6ae9df60e340a6988a9d74bb246df4df6 100644 (file)
@@ -2,17 +2,28 @@
 #include "refs.h"
 #include "object.h"
 #include "tag.h"
+#include "path-list.h"
 
-static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*]";
+static const char show_ref_usage[] = "git show-ref [-q|--quiet] [--verify] [-h|--head] [-d|--dereference] [-s|--hash[=<length>]] [--abbrev[=<length>]] [--tags] [--heads] [--] [pattern*] < ref-list";
 
 static int deref_tags = 0, show_head = 0, tags_only = 0, heads_only = 0,
        found_match = 0, verify = 0, quiet = 0, hash_only = 0, abbrev = 0;
 static const char **pattern;
 
+static void show_one(const char *refname, const unsigned char *sha1)
+{
+       const char *hex = find_unique_abbrev(sha1, abbrev);
+       if (hash_only)
+               printf("%s\n", hex);
+       else
+               printf("%s %s\n", hex, refname);
+}
+
 static int show_ref(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
 {
        struct object *obj;
        const char *hex;
+       unsigned char peeled[20];
 
        if (tags_only || heads_only) {
                int match;
@@ -44,24 +55,93 @@ static int show_ref(const char *refname, const unsigned char *sha1, int flag, vo
 
 match:
        found_match++;
-       obj = parse_object(sha1);
-       if (!obj) {
-               if (quiet)
-                       return 0;
-               die("git-show-ref: bad ref %s (%s)", refname, sha1_to_hex(sha1));
-       }
+
+       /* This changes the semantics slightly that even under quiet we
+        * detect and return error if the repository is corrupt and
+        * ref points at a nonexistent object.
+        */
+       if (!has_sha1_file(sha1))
+               die("git-show-ref: bad ref %s (%s)", refname,
+                   sha1_to_hex(sha1));
+
        if (quiet)
                return 0;
 
-       hex = find_unique_abbrev(sha1, abbrev);
-       if (hash_only)
-               printf("%s\n", hex);
-       else
-               printf("%s %s\n", hex, refname);
-       if (deref_tags && obj->type == OBJ_TAG) {
-               obj = deref_tag(obj, refname, 0);
-               hex = find_unique_abbrev(obj->sha1, abbrev);
-               printf("%s %s^{}\n", hex, refname);
+       show_one(refname, sha1);
+
+       if (!deref_tags)
+               return 0;
+
+       if ((flag & REF_ISPACKED) && !peel_ref(refname, peeled)) {
+               if (!is_null_sha1(peeled)) {
+                       hex = find_unique_abbrev(peeled, abbrev);
+                       printf("%s %s^{}\n", hex, refname);
+               }
+       }
+       else {
+               obj = parse_object(sha1);
+               if (!obj)
+                       die("git-show-ref: bad ref %s (%s)", refname,
+                           sha1_to_hex(sha1));
+               if (obj->type == OBJ_TAG) {
+                       obj = deref_tag(obj, refname, 0);
+                       hex = find_unique_abbrev(obj->sha1, abbrev);
+                       printf("%s %s^{}\n", hex, refname);
+               }
+       }
+       return 0;
+}
+
+static int add_existing(const char *refname, const unsigned char *sha1, int flag, void *cbdata)
+{
+       struct path_list *list = (struct path_list *)cbdata;
+       path_list_insert(refname, list);
+       return 0;
+}
+
+/*
+ * read "^(?:<anything>\s)?<refname>(?:\^\{\})?$" from the standard input,
+ * and
+ * (1) strip "^{}" at the end of line if any;
+ * (2) ignore if match is provided and does not head-match refname;
+ * (3) warn if refname is not a well-formed refname and skip;
+ * (4) ignore if refname is a ref that exists in the local repository;
+ * (5) otherwise output the line.
+ */
+static int exclude_existing(const char *match)
+{
+       static struct path_list existing_refs = { NULL, 0, 0, 0 };
+       char buf[1024];
+       int matchlen = match ? strlen(match) : 0;
+
+       for_each_ref(add_existing, &existing_refs);
+       while (fgets(buf, sizeof(buf), stdin)) {
+               char *ref;
+               int len = strlen(buf);
+
+               if (len > 0 && buf[len - 1] == '\n')
+                       buf[--len] = '\0';
+               if (3 <= len && !strcmp(buf + len - 3, "^{}")) {
+                       len -= 3;
+                       buf[len] = '\0';
+               }
+               for (ref = buf + len; buf < ref; ref--)
+                       if (isspace(ref[-1]))
+                               break;
+               if (match) {
+                       int reflen = buf + len - ref;
+                       if (reflen < matchlen)
+                               continue;
+                       if (strncmp(ref, match, matchlen))
+                               continue;
+               }
+               if (check_ref_format(ref)) {
+                       fprintf(stderr, "warning: ref '%s' ignored\n", ref);
+                       continue;
+               }
+               if (!path_list_has_path(&existing_refs, ref)) {
+                       printf("%s\n", buf);
+               }
        }
        return 0;
 }
@@ -101,13 +181,13 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
                if (!strncmp(arg, "--hash=", 7) ||
                    (!strncmp(arg, "--abbrev", 8) &&
                     (arg[8] == '=' || arg[8] == '\0'))) {
-                       if (arg[3] != 'h' && !arg[8])
+                       if (arg[2] != 'h' && !arg[8])
                                /* --abbrev only */
                                abbrev = DEFAULT_ABBREV;
                        else {
                                /* --hash= or --abbrev= */
                                char *end;
-                               if (arg[3] == 'h') {
+                               if (arg[2] == 'h') {
                                        hash_only = 1;
                                        arg += 7;
                                }
@@ -133,8 +213,31 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
                        heads_only = 1;
                        continue;
                }
+               if (!strcmp(arg, "--exclude-existing"))
+                       return exclude_existing(NULL);
+               if (!strncmp(arg, "--exclude-existing=", 19))
+                       return exclude_existing(arg + 19);
                usage(show_ref_usage);
        }
+
+       if (verify) {
+               unsigned char sha1[20];
+
+               while (*pattern) {
+                       if (!strncmp(*pattern, "refs/", 5) &&
+                           resolve_ref(*pattern, sha1, 1, NULL)) {
+                               if (!quiet)
+                                       show_one(*pattern, sha1);
+                       }
+                       else if (!quiet)
+                               die("'%s' - not a valid ref", *pattern);
+                       else
+                               return 1;
+                       pattern++;
+               }
+               return 0;
+       }
+
        if (show_head)
                head_ref(show_ref, NULL);
        for_each_ref(show_ref, NULL);
index 09cc9108cdaf2aa20dc10788bccd2fee0ae9b02a..f0d4d9e2d10446a229094c991363f5a17ce4c666 100644 (file)
@@ -1,6 +1,3 @@
-#include <stdio.h>
-#include <string.h>
-#include <ctype.h>
 #include "builtin.h"
 
 /*
index d8be0527f4752131b0a1276ecedcc82714636c28..d41b40640b96e7d5568174c7d16088c29d361d42 100644 (file)
@@ -3,9 +3,9 @@
 #include "refs.h"
 
 static const char git_symbolic_ref_usage[] =
-"git-symbolic-ref name [ref]";
+"git-symbolic-ref [-q] [-m <reason>] name [ref]";
 
-static void check_symref(const char *HEAD)
+static void check_symref(const char *HEAD, int quiet)
 {
        unsigned char sha1[20];
        int flag;
@@ -13,20 +13,56 @@ static void check_symref(const char *HEAD)
 
        if (!refs_heads_master)
                die("No such ref: %s", HEAD);
-       else if (!(flag & REF_ISSYMREF))
-               die("ref %s is not a symbolic ref", HEAD);
+       else if (!(flag & REF_ISSYMREF)) {
+               if (!quiet)
+                       die("ref %s is not a symbolic ref", HEAD);
+               else
+                       exit(1);
+       }
        puts(refs_heads_master);
 }
 
 int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 {
+       int quiet = 0;
+       const char *msg = NULL;
+
        git_config(git_default_config);
+
+       while (1 < argc) {
+               const char *arg = argv[1];
+               if (arg[0] != '-')
+                       break;
+               else if (!strcmp("-q", arg))
+                       quiet = 1;
+               else if (!strcmp("-m", arg)) {
+                       argc--;
+                       argv++;
+                       if (argc <= 1)
+                               break;
+                       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--;
+                       argv++;
+                       break;
+               }
+               else
+                       die("unknown option %s", arg);
+               argc--;
+               argv++;
+       }
+
        switch (argc) {
        case 2:
-               check_symref(argv[1]);
+               check_symref(argv[1], quiet);
                break;
        case 3:
-               create_symref(argv[1], argv[2]);
+               create_symref(argv[1], argv[2], msg);
                break;
        default:
                usage(git_symbolic_ref_usage);
index 4d4cfec878e468b8ab8a7f055c836c3569443914..8055ddab9b07e017a832e2fedf31b35fa0788a13 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2005, 2006 Rene Scharfe
  */
-#include <time.h>
 #include "cache.h"
 #include "commit.h"
 #include "tar.h"
@@ -75,7 +74,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
        char *content = buffer + RECORDSIZE;
        ssize_t n;
 
-       n = xread(0, buffer, HEADERSIZE);
+       n = read_in_full(0, buffer, HEADERSIZE);
        if (n < HEADERSIZE)
                die("git-get-tar-commit-id: read error");
        if (header->typeflag[0] != 'g')
@@ -83,7 +82,7 @@ int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix)
        if (memcmp(content, "52 comment=", 11))
                return 1;
 
-       n = xwrite(1, content + 11, 41);
+       n = write_in_full(1, content + 11, 41);
        if (n < 41)
                die("git-get-tar-commit-id: write error");
 
index e6d75748444ef4fc263970d4fe87b4623d2790fc..d351e02649401364e7384d0e784901e6f3fcb119 100644 (file)
@@ -8,8 +8,6 @@
 #include "tag.h"
 #include "tree.h"
 
-#include <sys/time.h>
-
 static int dry_run, quiet, recover, has_errors;
 static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] < pack-file";
 
index 7f9c638466f79fee7d2914b8ec76d713c3670e7e..1ac613a78869e5b0a2ac45ca6ab72adc18a55575 100644 (file)
@@ -112,13 +112,13 @@ static int add_file_to_cache(const char *path)
        ce->ce_mode = create_ce_mode(st.st_mode);
        if (!trust_executable_bit) {
                /* If there is an existing entry, pick the mode bits
-                * from it, otherwise force to 644.
+                * from it, otherwise assume unexecutable.
                 */
                int pos = cache_name_pos(path, namelen);
                if (0 <= pos)
                        ce->ce_mode = active_cache[pos]->ce_mode;
-               else
-                       ce->ce_mode = create_ce_mode(S_IFREG | 0644);
+               else if (S_ISREG(st.st_mode))
+                       ce->ce_mode = create_ce_mode(S_IFREG | 0666);
        }
 
        if (index_path(ce->sha1, path, &st, !info_only))
@@ -501,6 +501,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
 
        for (i = 1 ; i < argc; i++) {
                const char *path = argv[i];
+               const char *p;
 
                if (allow_options && *path == '-') {
                        if (!strcmp(path, "--")) {
@@ -616,9 +617,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
                                usage(update_index_usage);
                        die("unknown option %s", path);
                }
-               update_one(path, prefix, prefix_length);
+               p = prefix_path(prefix, prefix_length, path);
+               update_one(p, NULL, 0);
                if (set_executable_bit)
-                       chmod_path(set_executable_bit, path);
+                       chmod_path(set_executable_bit, p);
+               if (p < path || p > path + strlen(path))
+                       free((char*)p);
        }
        if (read_from_stdin) {
                struct strbuf buf;
index b34e5987dd256e0b7d9fae46fe89f66dd18ad91f..5ee960bf41c4518b6c5530acb123a7ed668538fc 100644 (file)
@@ -13,7 +13,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        int i, delete;
 
        delete = 0;
-       setup_ident();
        git_config(git_default_config);
 
        for (i = 1; i < argc; i++) {
@@ -62,10 +61,8 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 
        lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL);
        if (!lock)
-               return 1;
+               die("%s: cannot lock the ref", refname);
        if (write_ref_sha1(lock, sha1, msg) < 0)
-               return 1;
-
-       /* write_ref_sha1 always unlocks the ref, no need to do it explicitly */
+               die("%s: cannot update the ref", refname);
        return 0;
 }
index 45c92e163c477af4ec7c36c8ee6fcfd9242a0b2d..48ae09e9b5268ce1f11cfba433680a147ca39f7e 100644 (file)
@@ -1,9 +1,6 @@
 /*
  * Copyright (c) 2006 Franck Bui-Huu
  */
-#include <time.h>
-#include <sys/wait.h>
-#include <sys/poll.h>
 #include "cache.h"
 #include "builtin.h"
 #include "archive.h"
@@ -94,7 +91,7 @@ static void process_input(int child_fd, int band)
        char buf[16384];
        ssize_t sz = read(child_fd, buf, sizeof(buf));
        if (sz < 0) {
-               if (errno != EINTR)
+               if (errno != EAGAIN && errno != EINTR)
                        error_clnt("read error: %s\n", strerror(errno));
                return;
        }
index 7d39d9bcd178a65686d6757f8a89065d25f55b23..4e31c273f48e3983aaf99dc6525982d34b6fed06 100644 (file)
@@ -55,6 +55,7 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix)
        int no_more_options = 0;
        int nothing_done = 1;
 
+       git_config(git_default_config);
        while (1 < argc) {
                if (!no_more_options && argv[1][0] == '-') {
                        if (!strcmp("-v", argv[1]))
index 43fed329ba58a8adcdac9cb2260ad3370d9ad6cc..57e8741ff0569a349a48e386fae2664fe474a546 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -25,23 +25,27 @@ extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix)
 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_count_objects(int argc, const char **argv, const char *prefix);
+extern int cmd_describe(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
-extern int cmd_diff_stages(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
 extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
 extern int cmd_format_patch(int argc, const char **argv, const char *prefix);
+extern int cmd_fsck(int argc, const char **argv, const char *prefix);
 extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix);
 extern int cmd_grep(int argc, const char **argv, const char *prefix);
 extern int cmd_help(int argc, const char **argv, const char *prefix);
 extern int cmd_init_db(int argc, const char **argv, const char *prefix);
 extern int cmd_log(int argc, const char **argv, const char *prefix);
+extern int cmd_log_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_files(int argc, const char **argv, const char *prefix);
 extern int cmd_ls_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_mailinfo(int argc, const char **argv, const char *prefix);
 extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
 extern int cmd_mv(int argc, const char **argv, const char *prefix);
 extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
 extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
@@ -50,11 +54,14 @@ extern int cmd_prune(int argc, const char **argv, const char *prefix);
 extern int cmd_prune_packed(int argc, const char **argv, const char *prefix);
 extern int cmd_push(int argc, const char **argv, const char *prefix);
 extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
-extern int cmd_repo_config(int argc, const char **argv, const char *prefix);
+extern int cmd_reflog(int argc, const char **argv, const char *prefix);
+extern int cmd_config(int argc, const char **argv, const char *prefix);
+extern int cmd_rerere(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
 extern int cmd_runstatus(int argc, const char **argv, const char *prefix);
+extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
 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);
diff --git a/cache.h b/cache.h
index f2ec5c8c1416e3167d49813965212d414e6a5a7a..c62b0b090d2b3e628764557127f1629d26459fbf 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -122,7 +122,14 @@ extern int cache_errno;
 #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
 #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
 #define GRAFT_ENVIRONMENT "GIT_GRAFT_FILE"
-
+#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
+#define CONFIG_ENVIRONMENT "GIT_CONFIG"
+#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
+#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
+
+extern int is_bare_repository_cfg;
+extern int is_bare_repository(void);
+extern int is_inside_git_dir(void);
 extern const char *get_git_dir(void);
 extern char *get_object_directory(void);
 extern char *get_refs_directory(void);
@@ -174,6 +181,7 @@ extern int refresh_cache(unsigned int flags);
 
 struct lock_file {
        struct lock_file *next;
+       char on_list;
        char filename[PATH_MAX];
 };
 extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
@@ -191,6 +199,8 @@ extern int warn_ambiguous_refs;
 extern int shared_repository;
 extern const char *apply_default_whitespace;
 extern int zlib_compression_level;
+extern size_t packed_git_window_size;
+extern size_t packed_git_limit;
 
 #define GIT_REPO_VERSION 0
 extern int repository_format_version;
@@ -247,6 +257,7 @@ extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, uns
 extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
 extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1);
 extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
+extern int pretend_sha1_file(void *, unsigned long, const char *, unsigned char *);
 
 extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
 
@@ -290,8 +301,11 @@ extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer result! */
 extern int read_ref(const char *filename, unsigned char *sha1);
 extern const char *resolve_ref(const char *path, unsigned char *sha1, int, int *);
-extern int create_symref(const char *ref, const char *refs_heads_master);
-extern int validate_symref(const char *ref);
+extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
+extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
+
+extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
+extern int validate_headref(const char *ref);
 
 extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
 extern int cache_name_compare(const char *name1, int len1, const char *name2, int len2);
@@ -307,9 +321,9 @@ int parse_date(const char *date, char *buf, int bufsize);
 void datestamp(char *buf, int bufsize);
 unsigned long approxidate(const char *);
 
-extern int setup_ident(void);
 extern const char *git_author_info(int);
 extern const char *git_committer_info(int);
+extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int);
 
 struct checkout {
        const char *base_dir;
@@ -329,14 +343,22 @@ extern struct alternate_object_database {
 } *alt_odb_list;
 extern void prepare_alt_odb(void);
 
+struct pack_window {
+       struct pack_window *next;
+       unsigned char *base;
+       off_t offset;
+       size_t len;
+       unsigned int last_used;
+       unsigned int inuse_cnt;
+};
+
 extern struct packed_git {
        struct packed_git *next;
-       unsigned long index_size;
-       unsigned long pack_size;
-       unsigned int *index_base;
-       void *pack_base;
-       unsigned int pack_last_used;
-       unsigned int pack_use_cnt;
+       struct pack_window *windows;
+       uint32_t *index_base;
+       off_t index_size;
+       off_t pack_size;
+       int pack_fd;
        int pack_local;
        unsigned char sha1[20];
        /* something like ".git/objects/pack/xxxxx.pack" */
@@ -382,13 +404,14 @@ extern void install_packed_git(struct packed_git *pack);
 extern struct packed_git *find_sha1_pack(const unsigned char *sha1, 
                                         struct packed_git *packs);
 
-extern int use_packed_git(struct packed_git *);
-extern void unuse_packed_git(struct packed_git *);
+extern void pack_report(void);
+extern unsigned char* use_pack(struct packed_git *, struct pack_window **, unsigned long, unsigned int *);
+extern void unuse_pack(struct pack_window **);
 extern struct packed_git *add_packed_git(char *, int, int);
 extern int num_packed_objects(const struct packed_git *p);
 extern int nth_packed_object_sha1(const struct packed_git *, int, unsigned char*);
 extern unsigned long find_pack_entry_one(const unsigned char *, struct packed_git *);
-extern void *unpack_entry_gently(struct packed_git *, unsigned long, char *, unsigned long *);
+extern void *unpack_entry(struct packed_git *, unsigned long, char *, unsigned long *);
 extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep);
 extern void packed_object_info_detail(struct packed_git *, unsigned long, char *, unsigned long *, unsigned long *, unsigned int *, unsigned char *);
 
@@ -403,18 +426,22 @@ extern int git_config_int(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);
+extern int git_config_rename_section(const char *, const char *);
 extern int check_repository_format_version(const char *var, const char *value);
 
 #define MAX_GITNAME (1000)
 extern char git_default_email[MAX_GITNAME];
 extern char git_default_name[MAX_GITNAME];
 
-#define MAX_ENCODING_LENGTH 64
-extern char git_commit_encoding[MAX_ENCODING_LENGTH];
+extern char *git_commit_encoding;
+extern char *git_log_output_encoding;
 
 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);
 extern void write_or_die(int fd, const void *buf, size_t count);
 extern int write_or_whine(int fd, const void *buf, size_t count, const char *msg);
+extern int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg);
 
 /* pager.c */
 extern void setup_pager(void);
diff --git a/color.c b/color.c
index d8c8399d5916432eaa4122175f5e5d7ce209001b..09d82eec3d0adf5b7bd8828b0c8df0554695f75c 100644 (file)
--- a/color.c
+++ b/color.c
@@ -1,8 +1,5 @@
-#include "color.h"
 #include "cache.h"
-#include "git-compat-util.h"
-
-#include <stdarg.h>
+#include "color.h"
 
 #define COLOR_RESET "\033[m"
 
index 29d0c9cf953fdc13bd44bef451d242c762e1c611..a5f2c8dd4a414e513d0421f9a499d06c73ecaf5a 100644 (file)
@@ -482,11 +482,11 @@ static int make_hunks(struct sline *sline, unsigned long cnt,
        return has_interesting;
 }
 
-static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n)
+static void show_parent_lno(struct sline *sline, unsigned long l0, unsigned long l1, int n, unsigned long null_context)
 {
        l0 = sline[l0].p_lno[n];
        l1 = sline[l1].p_lno[n];
-       printf(" -%lu,%lu", l0, l1-l0);
+       printf(" -%lu,%lu", l0, l1-l0-null_context);
 }
 
 static int hunk_comment_line(const char *bol)
@@ -519,6 +519,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                unsigned long hunk_end;
                unsigned long rlines;
                const char *hunk_comment = NULL;
+               unsigned long null_context = 0;
 
                while (lno <= cnt && !(sline[lno].flag & mark)) {
                        if (hunk_comment_line(sline[lno].bol))
@@ -535,10 +536,28 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                rlines = hunk_end - lno;
                if (cnt < hunk_end)
                        rlines--; /* pointing at the last delete hunk */
+
+               if (!context) {
+                       /*
+                        * Even when running with --unified=0, all
+                        * lines in the hunk needs to be processed in
+                        * the loop below in order to show the
+                        * deletion recorded in lost_head.  However,
+                        * we do not want to show the resulting line
+                        * with all blank context markers in such a
+                        * case.  Compensate.
+                        */
+                       unsigned long j;
+                       for (j = lno; j < hunk_end; j++)
+                               if (!(sline[j].flag & (mark-1)))
+                                       null_context++;
+                       rlines -= null_context;
+               }
+
                fputs(c_frag, stdout);
                for (i = 0; i <= num_parent; i++) putchar(combine_marker);
                for (i = 0; i < num_parent; i++)
-                       show_parent_lno(sline, lno, hunk_end, i);
+                       show_parent_lno(sline, lno, hunk_end, i, null_context);
                printf(" +%lu,%lu ", lno+1, rlines);
                for (i = 0; i <= num_parent; i++) putchar(combine_marker);
 
@@ -578,8 +597,15 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                        if (cnt < lno)
                                break;
                        p_mask = 1;
-                       if (!(sl->flag & (mark-1)))
+                       if (!(sl->flag & (mark-1))) {
+                               /*
+                                * This sline was here to hang the
+                                * lost lines in front of it.
+                                */
+                               if (!context)
+                                       continue;
                                fputs(c_plain, stdout);
+                       }
                        else
                                fputs(c_new, stdout);
                        for (j = 0; j < num_parent; j++) {
index a6d543eee7831cd6a479d4cf8b2ba81a1438b298..8d279b0b635f58482b5a45c6a8a0086744b0735d 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -1,6 +1,8 @@
 #include "cache.h"
 #include "tag.h"
 #include "commit.h"
+#include "pkt-line.h"
+#include "utf8.h"
 
 int save_commit_buffer = 1;
 
@@ -45,7 +47,8 @@ enum cmit_fmt get_commit_format(const char *arg)
        if (*arg == '=')
                arg++;
        for (i = 0; i < ARRAY_SIZE(cmt_fmts); i++) {
-               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len))
+               if (!strncmp(arg, cmt_fmts[i].n, cmt_fmts[i].cmp_len) &&
+                   !strncmp(arg, cmt_fmts[i].n, strlen(arg)))
                        return cmt_fmts[i].v;
        }
 
@@ -221,6 +224,8 @@ static void prepare_commit_graft(void)
                return;
        graft_file = get_graft_file();
        read_graft_file(graft_file);
+       /* make sure shallows are read */
+       is_repository_shallow();
        commit_graft_prepared = 1;
 }
 
@@ -234,6 +239,39 @@ static struct commit_graft *lookup_commit_graft(const unsigned char *sha1)
        return commit_graft[pos];
 }
 
+int write_shallow_commits(int fd, int use_pack_protocol)
+{
+       int i, count = 0;
+       for (i = 0; i < commit_graft_nr; i++)
+               if (commit_graft[i]->nr_parent < 0) {
+                       const char *hex =
+                               sha1_to_hex(commit_graft[i]->sha1);
+                       count++;
+                       if (use_pack_protocol)
+                               packet_write(fd, "shallow %s", hex);
+                       else {
+                               if (write_in_full(fd, hex,  40) != 40)
+                                       break;
+                               if (write_in_full(fd, "\n", 1) != 1)
+                                       break;
+                       }
+               }
+       return count;
+}
+
+int unregister_shallow(const unsigned char *sha1)
+{
+       int pos = commit_graft_pos(sha1);
+       if (pos < 0)
+               return -1;
+       if (pos + 1 < commit_graft_nr)
+               memcpy(commit_graft + pos, commit_graft + pos + 1,
+                               sizeof(struct commit_graft *)
+                               * (commit_graft_nr - pos - 1));
+       commit_graft_nr--;
+       return 0;
+}
+
 int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size)
 {
        char *tail = buffer;
@@ -427,20 +465,29 @@ static int get_one_line(const char *msg, unsigned long len)
        return ret;
 }
 
+/* High bit set, or ISO-2022-INT */
+static int non_ascii(int ch)
+{
+       ch = (ch & 0xff);
+       return ((ch & 0x80) || (ch == 0x1b));
+}
+
 static int is_rfc2047_special(char ch)
 {
-       return ((ch & 0x80) || (ch == '=') || (ch == '?') || (ch == '_'));
+       return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
 }
 
-static int add_rfc2047(char *buf, const char *line, int len)
+static int add_rfc2047(char *buf, const char *line, int len,
+                      const char *encoding)
 {
        char *bp = buf;
        int i, needquote;
-       static const char q_utf8[] = "=?utf-8?q?";
+       char q_encoding[128];
+       const char *q_encoding_fmt = "=?%s?q?";
 
        for (i = needquote = 0; !needquote && i < len; i++) {
-               unsigned ch = line[i];
-               if (ch & 0x80)
+               int ch = line[i];
+               if (non_ascii(ch))
                        needquote++;
                if ((i + 1 < len) &&
                    (ch == '=' && line[i+1] == '?'))
@@ -449,8 +496,11 @@ static int add_rfc2047(char *buf, const char *line, int len)
        if (!needquote)
                return sprintf(buf, "%.*s", len, line);
 
-       memcpy(bp, q_utf8, sizeof(q_utf8)-1);
-       bp += sizeof(q_utf8)-1;
+       i = snprintf(q_encoding, sizeof(q_encoding), q_encoding_fmt, encoding);
+       if (sizeof(q_encoding) < i)
+               die("Insanely long encoding name %s", encoding);
+       memcpy(bp, q_encoding, i);
+       bp += i;
        for (i = 0; i < len; i++) {
                unsigned ch = line[i] & 0xFF;
                if (is_rfc2047_special(ch)) {
@@ -468,7 +518,8 @@ static int add_rfc2047(char *buf, const char *line, int len)
 }
 
 static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
-                        const char *line, int relative_date)
+                        const char *line, int relative_date,
+                        const char *encoding)
 {
        char *date;
        int namelen;
@@ -496,7 +547,8 @@ static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
                filler = "";
                strcpy(buf, "From: ");
                ret = strlen(buf);
-               ret += add_rfc2047(buf + ret, line, display_name_length);
+               ret += add_rfc2047(buf + ret, line, display_name_length,
+                                  encoding);
                memcpy(buf + ret, name_tail, namelen - display_name_length);
                ret += namelen - display_name_length;
                buf[ret++] = '\n';
@@ -563,17 +615,126 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com
        return offset;
 }
 
-unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
-                                 unsigned long len, char *buf, unsigned long space,
+static char *get_header(const struct commit *commit, const char *key)
+{
+       int key_len = strlen(key);
+       const char *line = commit->buffer;
+
+       for (;;) {
+               const char *eol = strchr(line, '\n'), *next;
+
+               if (line == eol)
+                       return NULL;
+               if (!eol) {
+                       eol = line + strlen(line);
+                       next = NULL;
+               } else
+                       next = eol + 1;
+               if (!strncmp(line, key, key_len) && line[key_len] == ' ') {
+                       int len = eol - line - key_len;
+                       char *ret = xmalloc(len);
+                       memcpy(ret, line + key_len + 1, len - 1);
+                       ret[len - 1] = '\0';
+                       return ret;
+               }
+               line = next;
+       }
+}
+
+static char *replace_encoding_header(char *buf, char *encoding)
+{
+       char *encoding_header = strstr(buf, "\nencoding ");
+       char *end_of_encoding_header;
+       int encoding_header_pos;
+       int encoding_header_len;
+       int new_len;
+       int need_len;
+       int buflen = strlen(buf) + 1;
+
+       if (!encoding_header)
+               return buf; /* should not happen but be defensive */
+       encoding_header++;
+       end_of_encoding_header = strchr(encoding_header, '\n');
+       if (!end_of_encoding_header)
+               return buf; /* should not happen but be defensive */
+       end_of_encoding_header++;
+
+       encoding_header_len = end_of_encoding_header - encoding_header;
+       encoding_header_pos = encoding_header - buf;
+
+       if (is_encoding_utf8(encoding)) {
+               /* we have re-coded to UTF-8; drop the header */
+               memmove(encoding_header, end_of_encoding_header,
+                       buflen - (encoding_header_pos + encoding_header_len));
+               return buf;
+       }
+       new_len = strlen(encoding);
+       need_len = new_len + strlen("encoding \n");
+       if (encoding_header_len < need_len) {
+               buf = xrealloc(buf, buflen + (need_len - encoding_header_len));
+               encoding_header = buf + encoding_header_pos;
+               end_of_encoding_header = encoding_header + encoding_header_len;
+       }
+       memmove(end_of_encoding_header + (need_len - encoding_header_len),
+               end_of_encoding_header,
+               buflen - (encoding_header_pos + encoding_header_len));
+       memcpy(encoding_header + 9, encoding, strlen(encoding));
+       encoding_header[9 + new_len] = '\n';
+       return buf;
+}
+
+static char *logmsg_reencode(const struct commit *commit,
+                            char *output_encoding)
+{
+       char *encoding;
+       char *out;
+       char *utf8 = "utf-8";
+
+       if (!*output_encoding)
+               return NULL;
+       encoding = get_header(commit, "encoding");
+       if (!encoding)
+               encoding = utf8;
+       if (!strcmp(encoding, output_encoding))
+               out = strdup(commit->buffer);
+       else
+               out = reencode_string(commit->buffer,
+                                     output_encoding, encoding);
+       if (out)
+               out = replace_encoding_header(out, output_encoding);
+
+       if (encoding != utf8)
+               free(encoding);
+       if (!out)
+               return NULL;
+       return out;
+}
+
+unsigned long pretty_print_commit(enum cmit_fmt fmt,
+                                 const struct commit *commit,
+                                 unsigned long len,
+                                 char *buf, unsigned long space,
                                  int abbrev, const char *subject,
-                                 const char *after_subject, int relative_date)
+                                 const char *after_subject,
+                                 int relative_date)
 {
-       int hdr = 1, body = 0;
+       int hdr = 1, body = 0, seen_title = 0;
        unsigned long offset = 0;
        int indent = 4;
        int parents_shown = 0;
        const char *msg = commit->buffer;
        int plain_non_ascii = 0;
+       char *reencoded;
+       char *encoding;
+
+       encoding = (git_log_output_encoding
+                   ? git_log_output_encoding
+                   : git_commit_encoding);
+       if (!encoding)
+               encoding = "utf-8";
+       reencoded = logmsg_reencode(commit, encoding);
+       if (reencoded)
+               msg = reencoded;
 
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
                indent = 0;
@@ -590,14 +751,14 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                for (in_body = i = 0; (ch = msg[i]) && i < len; i++) {
                        if (!in_body) {
                                /* author could be non 7-bit ASCII but
-                                * the log may so; skip over the
+                                * the log may be so; skip over the
                                 * header part first.
                                 */
                                if (ch == '\n' &&
                                    i + 1 < len && msg[i+1] == '\n')
                                        in_body = 1;
                        }
-                       else if (ch & 0x80) {
+                       else if (non_ascii(ch)) {
                                plain_non_ascii = 1;
                                break;
                        }
@@ -656,13 +817,15 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                                offset += add_user_info("Author", fmt,
                                                        buf + offset,
                                                        line + 7,
-                                                       relative_date);
+                                                       relative_date,
+                                                       encoding);
                        if (!memcmp(line, "committer ", 10) &&
                            (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER))
                                offset += add_user_info("Commit", fmt,
                                                        buf + offset,
                                                        line + 10,
-                                                       relative_date);
+                                                       relative_date,
+                                                       encoding);
                        continue;
                }
 
@@ -670,6 +833,8 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                        body = 1;
 
                if (is_empty_line(line, &linelen)) {
+                       if (!seen_title)
+                               continue;
                        if (!body)
                                continue;
                        if (subject)
@@ -678,11 +843,13 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                                break;
                }
 
+               seen_title = 1;
                if (subject) {
                        int slen = strlen(subject);
                        memcpy(buf + offset, subject, slen);
                        offset += slen;
-                       offset += add_rfc2047(buf + offset, line, linelen);
+                       offset += add_rfc2047(buf + offset, line, linelen,
+                                             encoding);
                }
                else {
                        memset(buf + offset, ' ', indent);
@@ -693,11 +860,17 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
                if (fmt == CMIT_FMT_ONELINE)
                        break;
                if (subject && plain_non_ascii) {
-                       static const char header[] =
-                               "Content-Type: text/plain; charset=UTF-8\n"
+                       int sz;
+                       char header[512];
+                       const char *header_fmt =
+                               "Content-Type: text/plain; charset=%s\n"
                                "Content-Transfer-Encoding: 8bit\n";
-                       memcpy(buf + offset, header, sizeof(header)-1);
-                       offset += sizeof(header)-1;
+                       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);
@@ -721,6 +894,8 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit
        if (fmt == CMIT_FMT_EMAIL && !body)
                buf[offset++] = '\n';
        buf[offset] = '\0';
+
+       free(reencoded);
        return offset;
 }
 
@@ -866,13 +1041,15 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
        free(nodes);
 }
 
-/* merge-rebase stuff */
+/* merge-base stuff */
 
-/* bits #0..7 in revision.h */
-#define PARENT1                (1u<< 8)
-#define PARENT2                (1u<< 9)
-#define STALE          (1u<<10)
-#define RESULT         (1u<<11)
+/* bits #0..15 in revision.h */
+#define PARENT1                (1u<<16)
+#define PARENT2                (1u<<17)
+#define STALE          (1u<<18)
+#define RESULT         (1u<<19)
+
+static const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
 
 static struct commit *interesting(struct commit_list *list)
 {
@@ -938,6 +1115,7 @@ static struct commit_list *merge_bases(struct commit *one, struct commit *two)
        }
 
        /* Clean up the result to remove stale ones */
+       free_commit_list(list);
        list = result; result = NULL;
        while (list) {
                struct commit_list *n = list->next;
@@ -953,7 +1131,6 @@ struct commit_list *get_merge_bases(struct commit *one,
                                    struct commit *two,
                                     int cleanup)
 {
-       const unsigned all_flags = (PARENT1 | PARENT2 | STALE | RESULT);
        struct commit_list *list;
        struct commit **rslt;
        struct commit_list *result;
@@ -1009,3 +1186,23 @@ struct commit_list *get_merge_bases(struct commit *one,
        free(rslt);
        return result;
 }
+
+int in_merge_bases(struct commit *commit, struct commit **reference, int num)
+{
+       struct commit_list *bases, *b;
+       int ret = 0;
+
+       if (num == 1)
+               bases = get_merge_bases(commit, *reference, 1);
+       else
+               die("not yet");
+       for (b = bases; b; b = b->next) {
+               if (!hashcmp(commit->object.sha1, b->item->object.sha1)) {
+                       ret = 1;
+                       break;
+               }
+       }
+
+       free_commit_list(bases);
+       return ret;
+}
index fc13de9780f98c3bd9f330ef6177fd47a4da3d80..c73744463ca55ce0df3623dffbb994bd4a86d76e 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -97,7 +97,7 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo,
 
 struct commit_graft {
        unsigned char sha1[20];
-       int nr_parent;
+       int nr_parent; /* < 0 if shallow commit */
        unsigned char parent[FLEX_ARRAY][20]; /* more */
 };
 
@@ -107,4 +107,12 @@ int read_graft_file(const char *graft_file);
 
 extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup);
 
+extern int register_shallow(const unsigned char *sha1);
+extern int unregister_shallow(const unsigned char *sha1);
+extern int write_shallow_commits(int fd, int use_pack_protocol);
+extern int is_repository_shallow(void);
+extern struct commit_list *get_shallow_commits(struct object_array *heads,
+               int depth, int shallow_flag, int not_shallow_flag);
+
+int in_merge_bases(struct commit *, struct commit **, int);
 #endif /* COMMIT_H */
index ec8c1bff539232c581fd10ef1d01c345442a2e35..4d7ab9d9758428c003a74d9f85699d7fc6922e05 100644 (file)
@@ -93,7 +93,7 @@ inet_ntop6(src, dst, size)
         */
        char tmp[sizeof "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], *tp;
        struct { int base, len; } best, cur;
-       u_int words[NS_IN6ADDRSZ / NS_INT16SZ];
+       unsigned int words[NS_IN6ADDRSZ / NS_INT16SZ];
        int i;
 
        /*
index a4d2e507f73c5595a2ca76c0369349cc11e2426f..4cfaee31361d98d2c8f68d250609b09130335baa 100644 (file)
@@ -1,21 +1,11 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
 #include "../git-compat-util.h"
 
-void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset)
+void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
 {
-       int n = 0;
-       off_t current_offset = lseek(fd, 0, SEEK_CUR);
+       size_t n = 0;
 
        if (start != NULL || !(flags & MAP_PRIVATE))
-               die("Invalid usage of gitfakemmap.");
-
-       if (lseek(fd, offset, SEEK_SET) < 0) {
-               errno = EINVAL;
-               return MAP_FAILED;
-       }
+               die("Invalid usage of mmap when built with NO_MMAP");
 
        start = xmalloc(length);
        if (start == NULL) {
@@ -24,14 +14,16 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_
        }
 
        while (n < length) {
-               int count = read(fd, start+n, length-n);
+               ssize_t count = pread(fd, (char *)start + n, length - n, offset + n);
 
                if (count == 0) {
-                       memset(start+n, 0, length-n);
+                       memset((char *)start+n, 0, length-n);
                        break;
                }
 
                if (count < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
                        free(start);
                        errno = EACCES;
                        return MAP_FAILED;
@@ -40,15 +32,10 @@ void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_
                n += count;
        }
 
-       if (current_offset != lseek(fd, current_offset, SEEK_SET)) {
-               errno = EINVAL;
-               return MAP_FAILED;
-       }
-
        return start;
 }
 
-int gitfakemunmap(void *start, size_t length)
+int git_munmap(void *start, size_t length)
 {
        free(start);
        return 0;
diff --git a/compat/pread.c b/compat/pread.c
new file mode 100644 (file)
index 0000000..978cac4
--- /dev/null
@@ -0,0 +1,18 @@
+#include "../git-compat-util.h"
+
+ssize_t git_pread(int fd, void *buf, size_t count, off_t offset)
+{
+        off_t current_offset;
+        ssize_t rc;
+
+        current_offset = lseek(fd, 0, SEEK_CUR);
+
+        if (lseek(fd, offset, SEEK_SET) < 0)
+                return -1;
+
+        rc = read_in_full(fd, buf, count);
+
+        if (current_offset != lseek(fd, current_offset, SEEK_SET))
+                return -1;
+        return rc;
+}
index b7d76785980b81a6f1057d678d34a732f45ca4cd..3a22ea7b751efb768d72afa2f97fd963e10eec7e 100644 (file)
@@ -1,5 +1,4 @@
-#include <stdlib.h>
-#include <string.h>
+#include "../git-compat-util.h"
 
 int gitsetenv(const char *name, const char *value, int replace)
 {
index b66856a3a50a93262ee4b5bd0294bb0c7fc85b15..4024c360301ebe7d58ac5b84dcbb692341b649ed 100644 (file)
@@ -1,4 +1,4 @@
-#include <string.h>
+#include "../git-compat-util.h"
 
 size_t gitstrlcpy(char *dest, const char *src, size_t size)
 {
diff --git a/compat/subprocess.py b/compat/subprocess.py
deleted file mode 100644 (file)
index 6474eab..0000000
+++ /dev/null
@@ -1,1149 +0,0 @@
-# subprocess - Subprocesses with accessible I/O streams
-#
-# For more information about this module, see PEP 324.
-#
-# This module should remain compatible with Python 2.2, see PEP 291.
-#
-# Copyright (c) 2003-2005 by Peter Astrand <astrand@lysator.liu.se>
-#
-# Licensed to PSF under a Contributor Agreement.
-# See http://www.python.org/2.4/license for licensing details.
-
-r"""subprocess - Subprocesses with accessible I/O streams
-
-This module allows you to spawn processes, connect to their
-input/output/error pipes, and obtain their return codes.  This module
-intends to replace several other, older modules and functions, like:
-
-os.system
-os.spawn*
-os.popen*
-popen2.*
-commands.*
-
-Information about how the subprocess module can be used to replace these
-modules and functions can be found below.
-
-
-
-Using the subprocess module
-===========================
-This module defines one class called Popen:
-
-class Popen(args, bufsize=0, executable=None,
-            stdin=None, stdout=None, stderr=None,
-            preexec_fn=None, close_fds=False, shell=False,
-            cwd=None, env=None, universal_newlines=False,
-            startupinfo=None, creationflags=0):
-
-
-Arguments are:
-
-args should be a string, or a sequence of program arguments.  The
-program to execute is normally the first item in the args sequence or
-string, but can be explicitly set by using the executable argument.
-
-On UNIX, with shell=False (default): In this case, the Popen class
-uses os.execvp() to execute the child program.  args should normally
-be a sequence.  A string will be treated as a sequence with the string
-as the only item (the program to execute).
-
-On UNIX, with shell=True: If args is a string, it specifies the
-command string to execute through the shell.  If args is a sequence,
-the first item specifies the command string, and any additional items
-will be treated as additional shell arguments.
-
-On Windows: the Popen class uses CreateProcess() to execute the child
-program, which operates on strings.  If args is a sequence, it will be
-converted to a string using the list2cmdline method.  Please note that
-not all MS Windows applications interpret the command line the same
-way: The list2cmdline is designed for applications using the same
-rules as the MS C runtime.
-
-bufsize, if given, has the same meaning as the corresponding argument
-to the built-in open() function: 0 means unbuffered, 1 means line
-buffered, any other positive value means use a buffer of
-(approximately) that size.  A negative bufsize means to use the system
-default, which usually means fully buffered.  The default value for
-bufsize is 0 (unbuffered).
-
-stdin, stdout and stderr specify the executed programs' standard
-input, standard output and standard error file handles, respectively.
-Valid values are PIPE, an existing file descriptor (a positive
-integer), an existing file object, and None.  PIPE indicates that a
-new pipe to the child should be created.  With None, no redirection
-will occur; the child's file handles will be inherited from the
-parent.  Additionally, stderr can be STDOUT, which indicates that the
-stderr data from the applications should be captured into the same
-file handle as for stdout.
-
-If preexec_fn is set to a callable object, this object will be called
-in the child process just before the child is executed.
-
-If close_fds is true, all file descriptors except 0, 1 and 2 will be
-closed before the child process is executed.
-
-if shell is true, the specified command will be executed through the
-shell.
-
-If cwd is not None, the current directory will be changed to cwd
-before the child is executed.
-
-If env is not None, it defines the environment variables for the new
-process.
-
-If universal_newlines is true, the file objects stdout and stderr are
-opened as a text files, but lines may be terminated by any of '\n',
-the Unix end-of-line convention, '\r', the Macintosh convention or
-'\r\n', the Windows convention.  All of these external representations
-are seen as '\n' by the Python program.  Note: This feature is only
-available if Python is built with universal newline support (the
-default).  Also, the newlines attribute of the file objects stdout,
-stdin and stderr are not updated by the communicate() method.
-
-The startupinfo and creationflags, if given, will be passed to the
-underlying CreateProcess() function.  They can specify things such as
-appearance of the main window and priority for the new process.
-(Windows only)
-
-
-This module also defines two shortcut functions:
-
-call(*args, **kwargs):
-    Run command with arguments.  Wait for command to complete, then
-    return the returncode attribute.
-
-    The arguments are the same as for the Popen constructor.  Example:
-
-    retcode = call(["ls", "-l"])
-
-
-Exceptions
-----------
-Exceptions raised in the child process, before the new program has
-started to execute, will be re-raised in the parent.  Additionally,
-the exception object will have one extra attribute called
-'child_traceback', which is a string containing traceback information
-from the childs point of view.
-
-The most common exception raised is OSError.  This occurs, for
-example, when trying to execute a non-existent file.  Applications
-should prepare for OSErrors.
-
-A ValueError will be raised if Popen is called with invalid arguments.
-
-
-Security
---------
-Unlike some other popen functions, this implementation will never call
-/bin/sh implicitly.  This means that all characters, including shell
-metacharacters, can safely be passed to child processes.
-
-
-Popen objects
-=============
-Instances of the Popen class have the following methods:
-
-poll()
-    Check if child process has terminated.  Returns returncode
-    attribute.
-
-wait()
-    Wait for child process to terminate.  Returns returncode attribute.
-
-communicate(input=None)
-    Interact with process: Send data to stdin.  Read data from stdout
-    and stderr, until end-of-file is reached.  Wait for process to
-    terminate.  The optional stdin argument should be a string to be
-    sent to the child process, or None, if no data should be sent to
-    the child.
-
-    communicate() returns a tuple (stdout, stderr).
-
-    Note: The data read is buffered in memory, so do not use this
-    method if the data size is large or unlimited.
-
-The following attributes are also available:
-
-stdin
-    If the stdin argument is PIPE, this attribute is a file object
-    that provides input to the child process.  Otherwise, it is None.
-
-stdout
-    If the stdout argument is PIPE, this attribute is a file object
-    that provides output from the child process.  Otherwise, it is
-    None.
-
-stderr
-    If the stderr argument is PIPE, this attribute is file object that
-    provides error output from the child process.  Otherwise, it is
-    None.
-
-pid
-    The process ID of the child process.
-
-returncode
-    The child return code.  A None value indicates that the process
-    hasn't terminated yet.  A negative value -N indicates that the
-    child was terminated by signal N (UNIX only).
-
-
-Replacing older functions with the subprocess module
-====================================================
-In this section, "a ==> b" means that b can be used as a replacement
-for a.
-
-Note: All functions in this section fail (more or less) silently if
-the executed program cannot be found; this module raises an OSError
-exception.
-
-In the following examples, we assume that the subprocess module is
-imported with "from subprocess import *".
-
-
-Replacing /bin/sh shell backquote
----------------------------------
-output=`mycmd myarg`
-==>
-output = Popen(["mycmd", "myarg"], stdout=PIPE).communicate()[0]
-
-
-Replacing shell pipe line
--------------------------
-output=`dmesg | grep hda`
-==>
-p1 = Popen(["dmesg"], stdout=PIPE)
-p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
-output = p2.communicate()[0]
-
-
-Replacing os.system()
----------------------
-sts = os.system("mycmd" + " myarg")
-==>
-p = Popen("mycmd" + " myarg", shell=True)
-sts = os.waitpid(p.pid, 0)
-
-Note:
-
-* Calling the program through the shell is usually not required.
-
-* It's easier to look at the returncode attribute than the
-  exitstatus.
-
-A more real-world example would look like this:
-
-try:
-    retcode = call("mycmd" + " myarg", shell=True)
-    if retcode < 0:
-        print >>sys.stderr, "Child was terminated by signal", -retcode
-    else:
-        print >>sys.stderr, "Child returned", retcode
-except OSError, e:
-    print >>sys.stderr, "Execution failed:", e
-
-
-Replacing os.spawn*
--------------------
-P_NOWAIT example:
-
-pid = os.spawnlp(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-pid = Popen(["/bin/mycmd", "myarg"]).pid
-
-
-P_WAIT example:
-
-retcode = os.spawnlp(os.P_WAIT, "/bin/mycmd", "mycmd", "myarg")
-==>
-retcode = call(["/bin/mycmd", "myarg"])
-
-
-Vector example:
-
-os.spawnvp(os.P_NOWAIT, path, args)
-==>
-Popen([path] + args[1:])
-
-
-Environment example:
-
-os.spawnlpe(os.P_NOWAIT, "/bin/mycmd", "mycmd", "myarg", env)
-==>
-Popen(["/bin/mycmd", "myarg"], env={"PATH": "/usr/bin"})
-
-
-Replacing os.popen*
--------------------
-pipe = os.popen(cmd, mode='r', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdout=PIPE).stdout
-
-pipe = os.popen(cmd, mode='w', bufsize)
-==>
-pipe = Popen(cmd, shell=True, bufsize=bufsize, stdin=PIPE).stdin
-
-
-(child_stdin, child_stdout) = os.popen2(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdin, child_stdout) = (p.stdin, p.stdout)
-
-
-(child_stdin,
- child_stdout,
- child_stderr) = os.popen3(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, stderr=PIPE, close_fds=True)
-(child_stdin,
- child_stdout,
- child_stderr) = (p.stdin, p.stdout, p.stderr)
-
-
-(child_stdin, child_stdout_and_stderr) = os.popen4(cmd, mode, bufsize)
-==>
-p = Popen(cmd, shell=True, bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True)
-(child_stdin, child_stdout_and_stderr) = (p.stdin, p.stdout)
-
-
-Replacing popen2.*
-------------------
-Note: If the cmd argument to popen2 functions is a string, the command
-is executed through /bin/sh.  If it is a list, the command is directly
-executed.
-
-(child_stdout, child_stdin) = popen2.popen2("somestring", bufsize, mode)
-==>
-p = Popen(["somestring"], shell=True, bufsize=bufsize
-          stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-
-(child_stdout, child_stdin) = popen2.popen2(["mycmd", "myarg"], bufsize, mode)
-==>
-p = Popen(["mycmd", "myarg"], bufsize=bufsize,
-          stdin=PIPE, stdout=PIPE, close_fds=True)
-(child_stdout, child_stdin) = (p.stdout, p.stdin)
-
-The popen2.Popen3 and popen3.Popen4 basically works as subprocess.Popen,
-except that:
-
-* subprocess.Popen raises an exception if the execution fails
-* the capturestderr argument is replaced with the stderr argument.
-* stdin=PIPE and stdout=PIPE must be specified.
-* popen2 closes all filedescriptors by default, but you have to specify
-  close_fds=True with subprocess.Popen.
-
-
-"""
-
-import sys
-mswindows = (sys.platform == "win32")
-
-import os
-import types
-import traceback
-
-if mswindows:
-    import threading
-    import msvcrt
-    if 0: # <-- change this to use pywin32 instead of the _subprocess driver
-        import pywintypes
-        from win32api import GetStdHandle, STD_INPUT_HANDLE, \
-                             STD_OUTPUT_HANDLE, STD_ERROR_HANDLE
-        from win32api import GetCurrentProcess, DuplicateHandle, \
-                             GetModuleFileName, GetVersion
-        from win32con import DUPLICATE_SAME_ACCESS, SW_HIDE
-        from win32pipe import CreatePipe
-        from win32process import CreateProcess, STARTUPINFO, \
-                                 GetExitCodeProcess, STARTF_USESTDHANDLES, \
-                                 STARTF_USESHOWWINDOW, CREATE_NEW_CONSOLE
-        from win32event import WaitForSingleObject, INFINITE, WAIT_OBJECT_0
-    else:
-        from _subprocess import *
-        class STARTUPINFO:
-            dwFlags = 0
-            hStdInput = None
-            hStdOutput = None
-            hStdError = None
-        class pywintypes:
-            error = IOError
-else:
-    import select
-    import errno
-    import fcntl
-    import pickle
-
-__all__ = ["Popen", "PIPE", "STDOUT", "call"]
-
-try:
-    MAXFD = os.sysconf("SC_OPEN_MAX")
-except:
-    MAXFD = 256
-
-# True/False does not exist on 2.2.0
-try:
-    False
-except NameError:
-    False = 0
-    True = 1
-
-_active = []
-
-def _cleanup():
-    for inst in _active[:]:
-        inst.poll()
-
-PIPE = -1
-STDOUT = -2
-
-
-def call(*args, **kwargs):
-    """Run command with arguments.  Wait for command to complete, then
-    return the returncode attribute.
-
-    The arguments are the same as for the Popen constructor.  Example:
-
-    retcode = call(["ls", "-l"])
-    """
-    return Popen(*args, **kwargs).wait()
-
-
-def list2cmdline(seq):
-    """
-    Translate a sequence of arguments into a command line
-    string, using the same rules as the MS C runtime:
-
-    1) Arguments are delimited by white space, which is either a
-       space or a tab.
-
-    2) A string surrounded by double quotation marks is
-       interpreted as a single argument, regardless of white space
-       contained within.  A quoted string can be embedded in an
-       argument.
-
-    3) A double quotation mark preceded by a backslash is
-       interpreted as a literal double quotation mark.
-
-    4) Backslashes are interpreted literally, unless they
-       immediately precede a double quotation mark.
-
-    5) If backslashes immediately precede a double quotation mark,
-       every pair of backslashes is interpreted as a literal
-       backslash.  If the number of backslashes is odd, the last
-       backslash escapes the next double quotation mark as
-       described in rule 3.
-    """
-
-    # See
-    # http://msdn.microsoft.com/library/en-us/vccelng/htm/progs_12.asp
-    result = []
-    needquote = False
-    for arg in seq:
-        bs_buf = []
-
-        # Add a space to separate this argument from the others
-        if result:
-            result.append(' ')
-
-        needquote = (" " in arg) or ("\t" in arg)
-        if needquote:
-            result.append('"')
-
-        for c in arg:
-            if c == '\\':
-                # Don't know if we need to double yet.
-                bs_buf.append(c)
-            elif c == '"':
-                # Double backspaces.
-                result.append('\\' * len(bs_buf)*2)
-                bs_buf = []
-                result.append('\\"')
-            else:
-                # Normal char
-                if bs_buf:
-                    result.extend(bs_buf)
-                    bs_buf = []
-                result.append(c)
-
-        # Add remaining backspaces, if any.
-        if bs_buf:
-            result.extend(bs_buf)
-
-        if needquote:
-            result.extend(bs_buf)
-            result.append('"')
-
-    return ''.join(result)
-
-
-class Popen(object):
-    def __init__(self, args, bufsize=0, executable=None,
-                 stdin=None, stdout=None, stderr=None,
-                 preexec_fn=None, close_fds=False, shell=False,
-                 cwd=None, env=None, universal_newlines=False,
-                 startupinfo=None, creationflags=0):
-        """Create new Popen instance."""
-        _cleanup()
-
-        if not isinstance(bufsize, (int, long)):
-            raise TypeError("bufsize must be an integer")
-
-        if mswindows:
-            if preexec_fn is not None:
-                raise ValueError("preexec_fn is not supported on Windows "
-                                 "platforms")
-            if close_fds:
-                raise ValueError("close_fds is not supported on Windows "
-                                 "platforms")
-        else:
-            # POSIX
-            if startupinfo is not None:
-                raise ValueError("startupinfo is only supported on Windows "
-                                 "platforms")
-            if creationflags != 0:
-                raise ValueError("creationflags is only supported on Windows "
-                                 "platforms")
-
-        self.stdin = None
-        self.stdout = None
-        self.stderr = None
-        self.pid = None
-        self.returncode = None
-        self.universal_newlines = universal_newlines
-
-        # Input and output objects. The general principle is like
-        # this:
-        #
-        # Parent                   Child
-        # ------                   -----
-        # p2cwrite   ---stdin--->  p2cread
-        # c2pread    <--stdout---  c2pwrite
-        # errread    <--stderr---  errwrite
-        #
-        # On POSIX, the child objects are file descriptors.  On
-        # Windows, these are Windows file handles.  The parent objects
-        # are file descriptors on both platforms.  The parent objects
-        # are None when not using PIPEs. The child objects are None
-        # when not redirecting.
-
-        (p2cread, p2cwrite,
-         c2pread, c2pwrite,
-         errread, errwrite) = self._get_handles(stdin, stdout, stderr)
-
-        self._execute_child(args, executable, preexec_fn, close_fds,
-                            cwd, env, universal_newlines,
-                            startupinfo, creationflags, shell,
-                            p2cread, p2cwrite,
-                            c2pread, c2pwrite,
-                            errread, errwrite)
-
-        if p2cwrite:
-            self.stdin = os.fdopen(p2cwrite, 'wb', bufsize)
-        if c2pread:
-            if universal_newlines:
-                self.stdout = os.fdopen(c2pread, 'rU', bufsize)
-            else:
-                self.stdout = os.fdopen(c2pread, 'rb', bufsize)
-        if errread:
-            if universal_newlines:
-                self.stderr = os.fdopen(errread, 'rU', bufsize)
-            else:
-                self.stderr = os.fdopen(errread, 'rb', bufsize)
-
-        _active.append(self)
-
-
-    def _translate_newlines(self, data):
-        data = data.replace("\r\n", "\n")
-        data = data.replace("\r", "\n")
-        return data
-
-
-    if mswindows:
-        #
-        # Windows methods
-        #
-        def _get_handles(self, stdin, stdout, stderr):
-            """Construct and return tuple with IO objects:
-            p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
-            """
-            if stdin == None and stdout == None and stderr == None:
-                return (None, None, None, None, None, None)
-
-            p2cread, p2cwrite = None, None
-            c2pread, c2pwrite = None, None
-            errread, errwrite = None, None
-
-            if stdin == None:
-                p2cread = GetStdHandle(STD_INPUT_HANDLE)
-            elif stdin == PIPE:
-                p2cread, p2cwrite = CreatePipe(None, 0)
-                # Detach and turn into fd
-                p2cwrite = p2cwrite.Detach()
-                p2cwrite = msvcrt.open_osfhandle(p2cwrite, 0)
-            elif type(stdin) == types.IntType:
-                p2cread = msvcrt.get_osfhandle(stdin)
-            else:
-                # Assuming file-like object
-                p2cread = msvcrt.get_osfhandle(stdin.fileno())
-            p2cread = self._make_inheritable(p2cread)
-
-            if stdout == None:
-                c2pwrite = GetStdHandle(STD_OUTPUT_HANDLE)
-            elif stdout == PIPE:
-                c2pread, c2pwrite = CreatePipe(None, 0)
-                # Detach and turn into fd
-                c2pread = c2pread.Detach()
-                c2pread = msvcrt.open_osfhandle(c2pread, 0)
-            elif type(stdout) == types.IntType:
-                c2pwrite = msvcrt.get_osfhandle(stdout)
-            else:
-                # Assuming file-like object
-                c2pwrite = msvcrt.get_osfhandle(stdout.fileno())
-            c2pwrite = self._make_inheritable(c2pwrite)
-
-            if stderr == None:
-                errwrite = GetStdHandle(STD_ERROR_HANDLE)
-            elif stderr == PIPE:
-                errread, errwrite = CreatePipe(None, 0)
-                # Detach and turn into fd
-                errread = errread.Detach()
-                errread = msvcrt.open_osfhandle(errread, 0)
-            elif stderr == STDOUT:
-                errwrite = c2pwrite
-            elif type(stderr) == types.IntType:
-                errwrite = msvcrt.get_osfhandle(stderr)
-            else:
-                # Assuming file-like object
-                errwrite = msvcrt.get_osfhandle(stderr.fileno())
-            errwrite = self._make_inheritable(errwrite)
-
-            return (p2cread, p2cwrite,
-                    c2pread, c2pwrite,
-                    errread, errwrite)
-
-
-        def _make_inheritable(self, handle):
-            """Return a duplicate of handle, which is inheritable"""
-            return DuplicateHandle(GetCurrentProcess(), handle,
-                                   GetCurrentProcess(), 0, 1,
-                                   DUPLICATE_SAME_ACCESS)
-
-
-        def _find_w9xpopen(self):
-            """Find and return absolute path to w9xpopen.exe"""
-            w9xpopen = os.path.join(os.path.dirname(GetModuleFileName(0)),
-                                    "w9xpopen.exe")
-            if not os.path.exists(w9xpopen):
-                # Eeek - file-not-found - possibly an embedding
-                # situation - see if we can locate it in sys.exec_prefix
-                w9xpopen = os.path.join(os.path.dirname(sys.exec_prefix),
-                                        "w9xpopen.exe")
-                if not os.path.exists(w9xpopen):
-                    raise RuntimeError("Cannot locate w9xpopen.exe, which is "
-                                       "needed for Popen to work with your "
-                                       "shell or platform.")
-            return w9xpopen
-
-
-        def _execute_child(self, args, executable, preexec_fn, close_fds,
-                           cwd, env, universal_newlines,
-                           startupinfo, creationflags, shell,
-                           p2cread, p2cwrite,
-                           c2pread, c2pwrite,
-                           errread, errwrite):
-            """Execute program (MS Windows version)"""
-
-            if not isinstance(args, types.StringTypes):
-                args = list2cmdline(args)
-
-            # Process startup details
-            default_startupinfo = STARTUPINFO()
-            if startupinfo == None:
-                startupinfo = default_startupinfo
-            if not None in (p2cread, c2pwrite, errwrite):
-                startupinfo.dwFlags |= STARTF_USESTDHANDLES
-                startupinfo.hStdInput = p2cread
-                startupinfo.hStdOutput = c2pwrite
-                startupinfo.hStdError = errwrite
-
-            if shell:
-                default_startupinfo.dwFlags |= STARTF_USESHOWWINDOW
-                default_startupinfo.wShowWindow = SW_HIDE
-                comspec = os.environ.get("COMSPEC", "cmd.exe")
-                args = comspec + " /c " + args
-                if (GetVersion() >= 0x80000000L or
-                        os.path.basename(comspec).lower() == "command.com"):
-                    # Win9x, or using command.com on NT. We need to
-                    # use the w9xpopen intermediate program. For more
-                    # information, see KB Q150956
-                    # (http://web.archive.org/web/20011105084002/http://support.microsoft.com/support/kb/articles/Q150/9/56.asp)
-                    w9xpopen = self._find_w9xpopen()
-                    args = '"%s" %s' % (w9xpopen, args)
-                    # Not passing CREATE_NEW_CONSOLE has been known to
-                    # cause random failures on win9x.  Specifically a
-                    # dialog: "Your program accessed mem currently in
-                    # use at xxx" and a hopeful warning about the
-                    # stability of your system.  Cost is Ctrl+C wont
-                    # kill children.
-                    creationflags |= CREATE_NEW_CONSOLE
-
-            # Start the process
-            try:
-                hp, ht, pid, tid = CreateProcess(executable, args,
-                                         # no special security
-                                         None, None,
-                                         # must inherit handles to pass std
-                                         # handles
-                                         1,
-                                         creationflags,
-                                         env,
-                                         cwd,
-                                         startupinfo)
-            except pywintypes.error, e:
-                # Translate pywintypes.error to WindowsError, which is
-                # a subclass of OSError.  FIXME: We should really
-                # translate errno using _sys_errlist (or simliar), but
-                # how can this be done from Python?
-                raise WindowsError(*e.args)
-
-            # Retain the process handle, but close the thread handle
-            self._handle = hp
-            self.pid = pid
-            ht.Close()
-
-            # Child is launched. Close the parent's copy of those pipe
-            # handles that only the child should have open.  You need
-            # to make sure that no handles to the write end of the
-            # output pipe are maintained in this process or else the
-            # pipe will not close when the child process exits and the
-            # ReadFile will hang.
-            if p2cread != None:
-                p2cread.Close()
-            if c2pwrite != None:
-                c2pwrite.Close()
-            if errwrite != None:
-                errwrite.Close()
-
-
-        def poll(self):
-            """Check if child process has terminated.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                if WaitForSingleObject(self._handle, 0) == WAIT_OBJECT_0:
-                    self.returncode = GetExitCodeProcess(self._handle)
-                    _active.remove(self)
-            return self.returncode
-
-
-        def wait(self):
-            """Wait for child process to terminate.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                obj = WaitForSingleObject(self._handle, INFINITE)
-                self.returncode = GetExitCodeProcess(self._handle)
-                _active.remove(self)
-            return self.returncode
-
-
-        def _readerthread(self, fh, buffer):
-            buffer.append(fh.read())
-
-
-        def communicate(self, input=None):
-            """Interact with process: Send data to stdin.  Read data from
-            stdout and stderr, until end-of-file is reached.  Wait for
-            process to terminate.  The optional input argument should be a
-            string to be sent to the child process, or None, if no data
-            should be sent to the child.
-
-            communicate() returns a tuple (stdout, stderr)."""
-            stdout = None # Return
-            stderr = None # Return
-
-            if self.stdout:
-                stdout = []
-                stdout_thread = threading.Thread(target=self._readerthread,
-                                                 args=(self.stdout, stdout))
-                stdout_thread.setDaemon(True)
-                stdout_thread.start()
-            if self.stderr:
-                stderr = []
-                stderr_thread = threading.Thread(target=self._readerthread,
-                                                 args=(self.stderr, stderr))
-                stderr_thread.setDaemon(True)
-                stderr_thread.start()
-
-            if self.stdin:
-                if input != None:
-                    self.stdin.write(input)
-                self.stdin.close()
-
-            if self.stdout:
-                stdout_thread.join()
-            if self.stderr:
-                stderr_thread.join()
-
-            # All data exchanged.  Translate lists into strings.
-            if stdout != None:
-                stdout = stdout[0]
-            if stderr != None:
-                stderr = stderr[0]
-
-            # Translate newlines, if requested.  We cannot let the file
-            # object do the translation: It is based on stdio, which is
-            # impossible to combine with select (unless forcing no
-            # buffering).
-            if self.universal_newlines and hasattr(open, 'newlines'):
-                if stdout:
-                    stdout = self._translate_newlines(stdout)
-                if stderr:
-                    stderr = self._translate_newlines(stderr)
-
-            self.wait()
-            return (stdout, stderr)
-
-    else:
-        #
-        # POSIX methods
-        #
-        def _get_handles(self, stdin, stdout, stderr):
-            """Construct and return tuple with IO objects:
-            p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite
-            """
-            p2cread, p2cwrite = None, None
-            c2pread, c2pwrite = None, None
-            errread, errwrite = None, None
-
-            if stdin == None:
-                pass
-            elif stdin == PIPE:
-                p2cread, p2cwrite = os.pipe()
-            elif type(stdin) == types.IntType:
-                p2cread = stdin
-            else:
-                # Assuming file-like object
-                p2cread = stdin.fileno()
-
-            if stdout == None:
-                pass
-            elif stdout == PIPE:
-                c2pread, c2pwrite = os.pipe()
-            elif type(stdout) == types.IntType:
-                c2pwrite = stdout
-            else:
-                # Assuming file-like object
-                c2pwrite = stdout.fileno()
-
-            if stderr == None:
-                pass
-            elif stderr == PIPE:
-                errread, errwrite = os.pipe()
-            elif stderr == STDOUT:
-                errwrite = c2pwrite
-            elif type(stderr) == types.IntType:
-                errwrite = stderr
-            else:
-                # Assuming file-like object
-                errwrite = stderr.fileno()
-
-            return (p2cread, p2cwrite,
-                    c2pread, c2pwrite,
-                    errread, errwrite)
-
-
-        def _set_cloexec_flag(self, fd):
-            try:
-                cloexec_flag = fcntl.FD_CLOEXEC
-            except AttributeError:
-                cloexec_flag = 1
-
-            old = fcntl.fcntl(fd, fcntl.F_GETFD)
-            fcntl.fcntl(fd, fcntl.F_SETFD, old | cloexec_flag)
-
-
-        def _close_fds(self, but):
-            for i in range(3, MAXFD):
-                if i == but:
-                    continue
-                try:
-                    os.close(i)
-                except:
-                    pass
-
-
-        def _execute_child(self, args, executable, preexec_fn, close_fds,
-                           cwd, env, universal_newlines,
-                           startupinfo, creationflags, shell,
-                           p2cread, p2cwrite,
-                           c2pread, c2pwrite,
-                           errread, errwrite):
-            """Execute program (POSIX version)"""
-
-            if isinstance(args, types.StringTypes):
-                args = [args]
-
-            if shell:
-                args = ["/bin/sh", "-c"] + args
-
-            if executable == None:
-                executable = args[0]
-
-            # For transferring possible exec failure from child to parent
-            # The first char specifies the exception type: 0 means
-            # OSError, 1 means some other error.
-            errpipe_read, errpipe_write = os.pipe()
-            self._set_cloexec_flag(errpipe_write)
-
-            self.pid = os.fork()
-            if self.pid == 0:
-                # Child
-                try:
-                    # Close parent's pipe ends
-                    if p2cwrite:
-                        os.close(p2cwrite)
-                    if c2pread:
-                        os.close(c2pread)
-                    if errread:
-                        os.close(errread)
-                    os.close(errpipe_read)
-
-                    # Dup fds for child
-                    if p2cread:
-                        os.dup2(p2cread, 0)
-                    if c2pwrite:
-                        os.dup2(c2pwrite, 1)
-                    if errwrite:
-                        os.dup2(errwrite, 2)
-
-                    # Close pipe fds.  Make sure we doesn't close the same
-                    # fd more than once.
-                    if p2cread:
-                        os.close(p2cread)
-                    if c2pwrite and c2pwrite not in (p2cread,):
-                        os.close(c2pwrite)
-                    if errwrite and errwrite not in (p2cread, c2pwrite):
-                        os.close(errwrite)
-
-                    # Close all other fds, if asked for
-                    if close_fds:
-                        self._close_fds(but=errpipe_write)
-
-                    if cwd != None:
-                        os.chdir(cwd)
-
-                    if preexec_fn:
-                        apply(preexec_fn)
-
-                    if env == None:
-                        os.execvp(executable, args)
-                    else:
-                        os.execvpe(executable, args, env)
-
-                except:
-                    exc_type, exc_value, tb = sys.exc_info()
-                    # Save the traceback and attach it to the exception object
-                    exc_lines = traceback.format_exception(exc_type,
-                                                           exc_value,
-                                                           tb)
-                    exc_value.child_traceback = ''.join(exc_lines)
-                    os.write(errpipe_write, pickle.dumps(exc_value))
-
-                # This exitcode won't be reported to applications, so it
-                # really doesn't matter what we return.
-                os._exit(255)
-
-            # Parent
-            os.close(errpipe_write)
-            if p2cread and p2cwrite:
-                os.close(p2cread)
-            if c2pwrite and c2pread:
-                os.close(c2pwrite)
-            if errwrite and errread:
-                os.close(errwrite)
-
-            # Wait for exec to fail or succeed; possibly raising exception
-            data = os.read(errpipe_read, 1048576) # Exceptions limited to 1 MB
-            os.close(errpipe_read)
-            if data != "":
-                os.waitpid(self.pid, 0)
-                child_exception = pickle.loads(data)
-                raise child_exception
-
-
-        def _handle_exitstatus(self, sts):
-            if os.WIFSIGNALED(sts):
-                self.returncode = -os.WTERMSIG(sts)
-            elif os.WIFEXITED(sts):
-                self.returncode = os.WEXITSTATUS(sts)
-            else:
-                # Should never happen
-                raise RuntimeError("Unknown child exit status!")
-
-            _active.remove(self)
-
-
-        def poll(self):
-            """Check if child process has terminated.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                try:
-                    pid, sts = os.waitpid(self.pid, os.WNOHANG)
-                    if pid == self.pid:
-                        self._handle_exitstatus(sts)
-                except os.error:
-                    pass
-            return self.returncode
-
-
-        def wait(self):
-            """Wait for child process to terminate.  Returns returncode
-            attribute."""
-            if self.returncode == None:
-                pid, sts = os.waitpid(self.pid, 0)
-                self._handle_exitstatus(sts)
-            return self.returncode
-
-
-        def communicate(self, input=None):
-            """Interact with process: Send data to stdin.  Read data from
-            stdout and stderr, until end-of-file is reached.  Wait for
-            process to terminate.  The optional input argument should be a
-            string to be sent to the child process, or None, if no data
-            should be sent to the child.
-
-            communicate() returns a tuple (stdout, stderr)."""
-            read_set = []
-            write_set = []
-            stdout = None # Return
-            stderr = None # Return
-
-            if self.stdin:
-                # Flush stdio buffer.  This might block, if the user has
-                # been writing to .stdin in an uncontrolled fashion.
-                self.stdin.flush()
-                if input:
-                    write_set.append(self.stdin)
-                else:
-                    self.stdin.close()
-            if self.stdout:
-                read_set.append(self.stdout)
-                stdout = []
-            if self.stderr:
-                read_set.append(self.stderr)
-                stderr = []
-
-            while read_set or write_set:
-                rlist, wlist, xlist = select.select(read_set, write_set, [])
-
-                if self.stdin in wlist:
-                    # When select has indicated that the file is writable,
-                    # we can write up to PIPE_BUF bytes without risk
-                    # blocking.  POSIX defines PIPE_BUF >= 512
-                    bytes_written = os.write(self.stdin.fileno(), input[:512])
-                    input = input[bytes_written:]
-                    if not input:
-                        self.stdin.close()
-                        write_set.remove(self.stdin)
-
-                if self.stdout in rlist:
-                    data = os.read(self.stdout.fileno(), 1024)
-                    if data == "":
-                        self.stdout.close()
-                        read_set.remove(self.stdout)
-                    stdout.append(data)
-
-                if self.stderr in rlist:
-                    data = os.read(self.stderr.fileno(), 1024)
-                    if data == "":
-                        self.stderr.close()
-                        read_set.remove(self.stderr)
-                    stderr.append(data)
-
-            # All data exchanged.  Translate lists into strings.
-            if stdout != None:
-                stdout = ''.join(stdout)
-            if stderr != None:
-                stderr = ''.join(stderr)
-
-            # Translate newlines, if requested.  We cannot let the file
-            # object do the translation: It is based on stdio, which is
-            # impossible to combine with select (unless forcing no
-            # buffering).
-            if self.universal_newlines and hasattr(open, 'newlines'):
-                if stdout:
-                    stdout = self._translate_newlines(stdout)
-                if stderr:
-                    stderr = self._translate_newlines(stderr)
-
-            self.wait()
-            return (stdout, stderr)
-
-
-def _demo_posix():
-    #
-    # Example 1: Simple redirection: Get process list
-    #
-    plist = Popen(["ps"], stdout=PIPE).communicate()[0]
-    print "Process list:"
-    print plist
-
-    #
-    # Example 2: Change uid before executing child
-    #
-    if os.getuid() == 0:
-        p = Popen(["id"], preexec_fn=lambda: os.setuid(100))
-        p.wait()
-
-    #
-    # Example 3: Connecting several subprocesses
-    #
-    print "Looking for 'hda'..."
-    p1 = Popen(["dmesg"], stdout=PIPE)
-    p2 = Popen(["grep", "hda"], stdin=p1.stdout, stdout=PIPE)
-    print repr(p2.communicate()[0])
-
-    #
-    # Example 4: Catch execution error
-    #
-    print
-    print "Trying a weird file..."
-    try:
-        print Popen(["/this/path/does/not/exist"]).communicate()
-    except OSError, e:
-        if e.errno == errno.ENOENT:
-            print "The file didn't exist.  I thought so..."
-            print "Child traceback:"
-            print e.child_traceback
-        else:
-            print "Error", e.errno
-    else:
-        print >>sys.stderr, "Gosh.  No error."
-
-
-def _demo_windows():
-    #
-    # Example 1: Connecting several subprocesses
-    #
-    print "Looking for 'PROMPT' in set output..."
-    p1 = Popen("set", stdout=PIPE, shell=True)
-    p2 = Popen('find "PROMPT"', stdin=p1.stdout, stdout=PIPE)
-    print repr(p2.communicate()[0])
-
-    #
-    # Example 2: Simple execution of program
-    #
-    print "Executing calc..."
-    p = Popen("calc")
-    p.wait()
-
-
-if __name__ == "__main__":
-    if mswindows:
-        _demo_windows()
-    else:
-        _demo_posix()
index 3a5e4ec04ae4a523823e633031b85559bf5dc973..eb29f5e0849370afe90c400271fea12e0f9090aa 100644 (file)
@@ -1,5 +1,4 @@
-#include <stdlib.h>
-#include <string.h>
+#include "../git-compat-util.h"
 
 void gitunsetenv (const char *name)
 {
index 3cae3901aa9b50e8d04a8ce2633a1a1ef8ed0f6c..d82107124a53bca99ee65ea31dceb3871bf6796f 100644 (file)
--- a/config.c
+++ b/config.c
@@ -6,7 +6,6 @@
  *
  */
 #include "cache.h"
-#include <regex.h>
 
 #define MAXNAME (256)
 
@@ -239,6 +238,12 @@ int git_config_int(const char *name, const char *value)
                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);
 }
@@ -264,6 +269,11 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.bare")) {
+               is_bare_repository_cfg = git_config_bool(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "core.ignorestat")) {
                assume_unchanged = git_config_bool(var, value);
                return 0;
@@ -299,6 +309,21 @@ int git_default_config(const char *var, const char *value)
                return 0;
        }
 
+       if (!strcmp(var, "core.packedgitwindowsize")) {
+               int pgsz = getpagesize();
+               packed_git_window_size = git_config_int(var, value);
+               packed_git_window_size /= pgsz;
+               if (packed_git_window_size < 2)
+                       packed_git_window_size = 2;
+               packed_git_window_size *= pgsz;
+               return 0;
+       }
+
+       if (!strcmp(var, "core.packedgitlimit")) {
+               packed_git_limit = git_config_int(var, value);
+               return 0;
+       }
+
        if (!strcmp(var, "user.name")) {
                strlcpy(git_default_name, value, sizeof(git_default_name));
                return 0;
@@ -310,11 +335,17 @@ int git_default_config(const char *var, const char *value)
        }
 
        if (!strcmp(var, "i18n.commitencoding")) {
-               strlcpy(git_commit_encoding, value, sizeof(git_commit_encoding));
+               git_commit_encoding = strdup(value);
+               return 0;
+       }
+
+       if (!strcmp(var, "i18n.logoutputencoding")) {
+               git_log_output_encoding = strdup(value);
                return 0;
        }
 
-       if (!strcmp(var, "pager.color")) {
+
+       if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) {
                pager_use_color = git_config_bool(var,value);
                return 0;
        }
@@ -350,10 +381,10 @@ int git_config(config_fn_t fn)
         * $GIT_CONFIG_LOCAL will make it process it in addition to the
         * global config file, the same way it would the per-repository
         * config file otherwise. */
-       filename = getenv("GIT_CONFIG");
+       filename = getenv(CONFIG_ENVIRONMENT);
        if (!filename) {
                home = getenv("HOME");
-               filename = getenv("GIT_CONFIG_LOCAL");
+               filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!filename)
                        filename = repo_config = xstrdup(git_path("config"));
        }
@@ -438,7 +469,15 @@ static int store_aux(const char* key, const char* value)
        return 0;
 }
 
-static void store_write_section(int fd, const char* key)
+static int write_error()
+{
+       fprintf(stderr, "Failed to write new configuration file\n");
+
+       /* Same error code as "failed to rename". */
+       return 4;
+}
+
+static int store_write_section(int fd, const char* key)
 {
        const char *dot = strchr(key, '.');
        int len1 = store.baselen, len2 = -1;
@@ -452,37 +491,74 @@ static void store_write_section(int fd, const char* key)
                }
        }
 
-       write(fd, "[", 1);
-       write(fd, key, len1);
+       if (write_in_full(fd, "[", 1) != 1 ||
+           write_in_full(fd, key, len1) != len1)
+               return 0;
        if (len2 >= 0) {
-               write(fd, " \"", 2);
+               if (write_in_full(fd, " \"", 2) != 2)
+                       return 0;
                while (--len2 >= 0) {
                        unsigned char c = *++dot;
                        if (c == '"')
-                               write(fd, "\\", 1);
-                       write(fd, &c, 1);
+                               if (write_in_full(fd, "\\", 1) != 1)
+                                       return 0;
+                       if (write_in_full(fd, &c, 1) != 1)
+                               return 0;
                }
-               write(fd, "\"", 1);
+               if (write_in_full(fd, "\"", 1) != 1)
+                       return 0;
        }
-       write(fd, "]\n", 2);
+       if (write_in_full(fd, "]\n", 2) != 2)
+               return 0;
+
+       return 1;
 }
 
-static void store_write_pair(int fd, const char* key, const char* value)
+static int store_write_pair(int fd, const char* key, const char* value)
 {
        int i;
+       int length = strlen(key+store.baselen+1);
+       int quote = 0;
 
-       write(fd, "\t", 1);
-       write(fd, key+store.baselen+1,
-               strlen(key+store.baselen+1));
-       write(fd, " = ", 3);
+       /* Check to see if the value needs to be quoted. */
+       if (value[0] == ' ')
+               quote = 1;
+       for (i = 0; value[i]; i++)
+               if (value[i] == ';' || value[i] == '#')
+                       quote = 1;
+       if (value[i-1] == ' ')
+               quote = 1;
+
+       if (write_in_full(fd, "\t", 1) != 1 ||
+           write_in_full(fd, key+store.baselen+1, length) != length ||
+           write_in_full(fd, " = ", 3) != 3)
+               return 0;
+       if (quote && write_in_full(fd, "\"", 1) != 1)
+               return 0;
        for (i = 0; value[i]; i++)
                switch (value[i]) {
-               case '\n': write(fd, "\\n", 2); break;
-               case '\t': write(fd, "\\t", 2); break;
-               case '"': case '\\': write(fd, "\\", 1);
-               default: write(fd, value+i, 1);
-       }
-       write(fd, "\n", 1);
+               case '\n':
+                       if (write_in_full(fd, "\\n", 2) != 2)
+                               return 0;
+                       break;
+               case '\t':
+                       if (write_in_full(fd, "\\t", 2) != 2)
+                               return 0;
+                       break;
+               case '"':
+               case '\\':
+                       if (write_in_full(fd, "\\", 1) != 1)
+                               return 0;
+               default:
+                       if (write_in_full(fd, value+i, 1) != 1)
+                               return 0;
+                       break;
+               }
+       if (quote && write_in_full(fd, "\"", 1) != 1)
+               return 0;
+       if (write_in_full(fd, "\n", 1) != 1)
+               return 0;
+       return 1;
 }
 
 static int find_beginning_of_line(const char* contents, int size,
@@ -544,9 +620,9 @@ int git_config_set_multivar(const char* key, const char* value,
        char* lock_file;
        const char* last_dot = strrchr(key, '.');
 
-       config_filename = getenv("GIT_CONFIG");
+       config_filename = getenv(CONFIG_ENVIRONMENT);
        if (!config_filename) {
-               config_filename = getenv("GIT_CONFIG_LOCAL");
+               config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
                if (!config_filename)
                        config_filename  = git_path("config");
        }
@@ -585,6 +661,11 @@ int git_config_set_multivar(const char* key, const char* value,
                                goto out_free;
                        }
                        c = tolower(c);
+               } else if (c == '\n') {
+                       fprintf(stderr, "invalid key (newline): %s\n", key);
+                       free(store.key);
+                       ret = 1;
+                       goto out_free;
                }
                store.key[i] = c;
        }
@@ -622,9 +703,10 @@ int git_config_set_multivar(const char* key, const char* value,
                }
 
                store.key = (char*)key;
-               store_write_section(fd, key);
-               store_write_pair(fd, key, value);
-       } else{
+               if (!store_write_section(fd, key) ||
+                   !store_write_pair(fd, key, value))
+                       goto write_err_out;
+       } else {
                struct stat st;
                char* contents;
                int i, copy_begin, copy_end, new_line = 0;
@@ -684,7 +766,7 @@ int git_config_set_multivar(const char* key, const char* value,
                }
 
                fstat(in_fd, &st);
-               contents = mmap(NULL, st.st_size, PROT_READ,
+               contents = xmmap(NULL, st.st_size, PROT_READ,
                        MAP_PRIVATE, in_fd, 0);
                close(in_fd);
 
@@ -703,25 +785,33 @@ int git_config_set_multivar(const char* key, const char* value,
 
                        /* write the first part of the config */
                        if (copy_end > copy_begin) {
-                               write(fd, contents + copy_begin,
-                               copy_end - copy_begin);
-                               if (new_line)
-                                       write(fd, "\n", 1);
+                               if (write_in_full(fd, contents + copy_begin,
+                                                 copy_end - copy_begin) <
+                                   copy_end - copy_begin)
+                                       goto write_err_out;
+                               if (new_line &&
+                                   write_in_full(fd, "\n", 1) != 1)
+                                       goto write_err_out;
                        }
                        copy_begin = store.offset[i];
                }
 
                /* write the pair (value == NULL means unset) */
                if (value != NULL) {
-                       if (store.state == START)
-                               store_write_section(fd, key);
-                       store_write_pair(fd, key, value);
+                       if (store.state == START) {
+                               if (!store_write_section(fd, key))
+                                       goto write_err_out;
+                       }
+                       if (!store_write_pair(fd, key, value))
+                               goto write_err_out;
                }
 
                /* write the rest of the config */
                if (copy_begin < st.st_size)
-                       write(fd, contents + copy_begin,
-                               st.st_size - copy_begin);
+                       if (write_in_full(fd, contents + copy_begin,
+                                         st.st_size - copy_begin) <
+                           st.st_size - copy_begin)
+                               goto write_err_out;
 
                munmap(contents, st.st_size);
                unlink(config_filename);
@@ -744,6 +834,90 @@ out_free:
                free(lock_file);
        }
        return ret;
+
+write_err_out:
+       ret = write_error();
+       goto out_free;
+
 }
 
+int git_config_rename_section(const char *old_name, const char *new_name)
+{
+       int ret = 0;
+       char *config_filename;
+       struct lock_file *lock = xcalloc(sizeof(struct lock_file), 1);
+       int out_fd;
+       char buf[1024];
+
+       config_filename = getenv(CONFIG_ENVIRONMENT);
+       if (!config_filename) {
+               config_filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
+               if (!config_filename)
+                       config_filename  = git_path("config");
+       }
+       config_filename = xstrdup(config_filename);
+       out_fd = hold_lock_file_for_update(lock, config_filename, 0);
+       if (out_fd < 0) {
+               ret = error("Could not lock config file!");
+               goto out;
+       }
+
+       if (!(config_file = fopen(config_filename, "rb"))) {
+               ret = error("Could not open config file!");
+               goto out;
+       }
+
+       while (fgets(buf, sizeof(buf), config_file)) {
+               int i;
+               int length;
+               for (i = 0; buf[i] && isspace(buf[i]); i++)
+                       ; /* do nothing */
+               if (buf[i] == '[') {
+                       /* it's a section */
+                       int j = 0, dot = 0;
+                       for (i++; buf[i] && buf[i] != ']'; i++) {
+                               if (!dot && isspace(buf[i])) {
+                                       dot = 1;
+                                       if (old_name[j++] != '.')
+                                               break;
+                                       for (i++; isspace(buf[i]); i++)
+                                               ; /* do nothing */
+                                       if (buf[i] != '"')
+                                               break;
+                                       continue;
+                               }
+                               if (buf[i] == '\\' && dot)
+                                       i++;
+                               else if (buf[i] == '"' && dot) {
+                                       for (i++; isspace(buf[i]); i++)
+                                               ; /* do_nothing */
+                                       break;
+                               }
+                               if (buf[i] != old_name[j++])
+                                       break;
+                       }
+                       if (buf[i] == ']' && old_name[j] == 0) {
+                               /* old_name matches */
+                               ret++;
+                               store.baselen = strlen(new_name);
+                               if (!store_write_section(out_fd, new_name)) {
+                                       ret = write_error();
+                                       goto out;
+                               }
+                               continue;
+                       }
+               }
+               length = strlen(buf);
+               if (write_in_full(out_fd, buf, length) != length) {
+                       ret = write_error();
+                       goto out;
+               }
+       }
+       fclose(config_file);
+       if (close(out_fd) || commit_lock_file(lock) < 0)
+                       ret = error("Cannot commit config file!");
+ out:
+       free(config_filename);
+       return ret;
+}
 
index 1cafa19ed4e4b9ecb683e4bc6c4aaa3ac3322b74..9a578405d856c3633e7137a24964b7b80fd96485 100644 (file)
@@ -13,7 +13,6 @@ bindir = @bindir@
 #gitexecdir = @libexecdir@/git-core/
 datarootdir = @datarootdir@
 template_dir = @datadir@/git-core/templates/
-GIT_PYTHON_DIR = @datadir@/git-core/python
 
 mandir=@mandir@
 
@@ -23,7 +22,6 @@ VPATH = @srcdir@
 export exec_prefix mandir
 export srcdir VPATH
 
-NO_PYTHON=@NO_PYTHON@
 NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@
 NO_OPENSSL=@NO_OPENSSL@
 NO_CURL=@NO_CURL@
index cff5722eb9927ec652e3db731fc033508bc44ef2..7cfb3a0666ef88dca43598ebe879f53787b4efab 100644 (file)
@@ -75,20 +75,6 @@ GIT_ARG_SET_PATH(shell)
 # Define PERL_PATH to provide path to Perl.
 GIT_ARG_SET_PATH(perl)
 #
-# Define PYTHON_PATH to provide path to Python.
-AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python])
-AS_HELP_STRING([--without-python], [don't use python scripts])],
- [if test "$withval" = "no"; then \
-    NO_PYTHON=YesPlease; \
-  elif test "$withval" = "yes"; then \
-    NO_PYTHON=; \
-  else \
-    NO_PYTHON=; \
-    PYTHON_PATH=$withval; \
-  fi; \
- ])
-AC_SUBST(NO_PYTHON)
-AC_SUBST(PYTHON_PATH)
 
 
 ## Checks for programs.
@@ -98,18 +84,6 @@ AC_PROG_CC([cc gcc])
 #AC_PROG_INSTALL               # needs install-sh or install.sh in sources
 AC_CHECK_TOOL(AR, ar, :)
 AC_CHECK_PROGS(TAR, [gtar tar])
-#
-# Define PYTHON_PATH to provide path to Python.
-if test -z "$NO_PYTHON"; then
-       if test -z "$PYTHON_PATH"; then
-               AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2])
-       fi
-       if test -n "$PYTHON_PATH"; then
-               GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@])
-               NO_PYTHON=""
-       fi
-fi
-
 
 ## Checks for libraries.
 AC_MSG_NOTICE([CHECKS for libraries])
@@ -261,22 +235,6 @@ AC_SUBST(NO_SETENV)
 #
 # Define NO_SYMLINK_HEAD if you never want .git/HEAD to be a symbolic link.
 # Enable it on Windows.  By default, symrefs are still used.
-#
-# Define WITH_OWN_SUBPROCESS_PY if you want to use with python 2.3.
-AC_CACHE_CHECK([for subprocess.py],
- [ac_cv_python_has_subprocess_py],
-[if $PYTHON_PATH -c 'import subprocess' 2>/dev/null; then
-       ac_cv_python_has_subprocess_py=yes
-else
-       ac_cv_python_has_subprocess_py=no
-fi])
-if test $ac_cv_python_has_subprocess_py != yes; then
-       GIT_CONF_APPEND_LINE([WITH_OWN_SUBPROCESS_PY=YesPlease])
-fi
-#
-# Define NO_ACCURATE_DIFF if your diff program at least sometimes misses
-# a missing newline at the end of the file.
-
 
 ## Site configuration (override autodetection)
 ## --with-PACKAGE[=ARG] and --without-PACKAGE
@@ -355,10 +313,6 @@ GIT_PARSE_WITH(iconv))
 
 ## --enable-FEATURE[=ARG] and --disable-FEATURE
 #
-# Define COLLISION_CHECK below if you believe that SHA1's
-# 1461501637330902918203684832716283019655932542976 hashes do not give you
-# sufficient guarantee that no collisions between objects will ever happen.
-#
 # Define USE_NSEC below if you want git to care about sub-second file mtimes
 # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
 # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
index c55a20a4aa31e7cf1bbf0dcec6b4ebccb655d850..78448889da3f11fa28aacad17d3044a1c0df7e54 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -3,12 +3,6 @@
 #include "pkt-line.h"
 #include "quote.h"
 #include "refs.h"
-#include <sys/wait.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
-#include <signal.h>
 
 static char *server_capabilities;
 
@@ -144,6 +138,7 @@ struct refspec {
  * +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)
 {
@@ -174,21 +169,58 @@ static int count_refspec_match(const char *pattern,
                               struct ref *refs,
                               struct ref **matched_ref)
 {
-       int match;
        int patlen = strlen(pattern);
+       struct ref *matched_weak = NULL;
+       struct ref *matched = NULL;
+       int weak_match = 0;
+       int match = 0;
 
-       for (match = 0; refs; refs = refs->next) {
+       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;
-               match++;
-               *matched_ref = refs;
+
+               /* 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 &&
+                   strncmp(name, "refs/heads/", 11) &&
+                   strncmp(name, "refs/tags/", 10)) {
+                       /* 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;
        }
-       return match;
 }
 
 static void link_dst_tail(struct ref *ref, struct ref ***tail)
@@ -203,6 +235,13 @@ 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;
@@ -225,7 +264,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst,
                        break;
                case 0:
                        /* The source could be in the get_sha1() format
-                        * not a reference name.
+                        * 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)
@@ -489,7 +529,7 @@ static void git_tcp_connect(int fd[2], char *host)
        int sockfd = git_tcp_connect_sock(host);
 
        fd[0] = sockfd;
-       fd[1] = sockfd;
+       fd[1] = dup(sockfd);
 }
 
 
diff --git a/contrib/blameview/README b/contrib/blameview/README
new file mode 100644 (file)
index 0000000..50a6f67
--- /dev/null
@@ -0,0 +1,10 @@
+This is a sample program to use 'git-blame --incremental', based
+on this message.
+
+From: Jeff King <peff@peff.net>
+Subject: Re: More precise tag following
+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>
+
diff --git a/contrib/blameview/blameview.perl b/contrib/blameview/blameview.perl
new file mode 100755 (executable)
index 0000000..a9a509f
--- /dev/null
@@ -0,0 +1,155 @@
+#!/usr/bin/perl
+
+use Gtk2 -init;
+use Gtk2::SimpleList;
+
+my $hash;
+my $fn;
+if ( @ARGV == 1 ) {
+       $hash = "HEAD";
+       $fn = shift;
+} elsif ( @ARGV == 2 ) {
+       $hash = shift;
+       $fn = shift;
+} else {
+       die "Usage blameview [<rev>] <filename>";
+}
+
+Gtk2::Rc->parse_string(<<'EOS');
+style "treeview_style"
+{
+  GtkTreeView::vertical-separator = 0
+}
+class "GtkTreeView" style "treeview_style"
+EOS
+
+my $window = Gtk2::Window->new('toplevel');
+$window->signal_connect(destroy => sub { Gtk2->main_quit });
+my $vpan = Gtk2::VPaned->new();
+$window->add($vpan);
+my $scrolled_window = Gtk2::ScrolledWindow->new;
+$vpan->pack1($scrolled_window, 1, 1);
+my $fileview = Gtk2::SimpleList->new(
+    'Commit' => 'text',
+    'FileLine' => 'text',
+    'Data' => 'text'
+);
+$scrolled_window->add($fileview);
+$fileview->get_column(0)->set_spacing(0);
+$fileview->set_size_request(1024, 768);
+$fileview->set_rules_hint(1);
+$fileview->signal_connect (row_activated => sub {
+               my ($sl, $path, $column) = @_;
+               my $row_ref = $sl->get_row_data_from_path ($path);
+               system("blameview @$row_ref[0] $fn &");
+               });
+
+my $commitwindow = Gtk2::ScrolledWindow->new();
+$commitwindow->set_policy ('GTK_POLICY_AUTOMATIC','GTK_POLICY_AUTOMATIC');
+$vpan->pack2($commitwindow, 1, 1);
+my $commit_text = Gtk2::TextView->new();
+my $commit_buffer = Gtk2::TextBuffer->new();
+$commit_text->set_buffer($commit_buffer);
+$commitwindow->add($commit_text);
+
+$fileview->signal_connect (cursor_changed => sub {
+               my ($sl) = @_;
+               my ($path, $focus_column) = $sl->get_cursor();
+               my $row_ref = $sl->get_row_data_from_path ($path);
+               my $c_fh;
+               open($c_fh,  '-|', "git cat-file commit @$row_ref[0]")
+                                       or die "unable to find commit @$row_ref[0]";
+               my @buffer = <$c_fh>;
+               $commit_buffer->set_text("@buffer");
+               close($c_fh);
+               });
+
+my $fh;
+open($fh, '-|', "git cat-file blob $hash:$fn")
+  or die "unable to open $fn: $!";
+
+while(<$fh>) {
+  chomp;
+  $fileview->{data}->[$.] = ['HEAD', "$fn:$.", $_];
+}
+
+my $blame;
+open($blame, '-|', qw(git blame --incremental --), $fn, $hash)
+    or die "cannot start git-blame $fn";
+
+Glib::IO->add_watch(fileno($blame), 'in', \&read_blame_line);
+
+$window->show_all;
+Gtk2->main;
+exit 0;
+
+my %commitinfo = ();
+
+sub flush_blame_line {
+       my ($attr) = @_;
+
+       return unless defined $attr;
+
+       my ($commit, $s_lno, $lno, $cnt) =
+           @{$attr}{qw(COMMIT S_LNO LNO CNT)};
+
+       my ($filename, $author, $author_time, $author_tz) =
+           @{$commitinfo{$commit}}{qw(FILENAME AUTHOR AUTHOR-TIME AUTHOR-TZ)};
+       my $info = $author . ' ' . format_time($author_time, $author_tz);
+
+       for(my $i = 0; $i < $cnt; $i++) {
+               @{$fileview->{data}->[$lno+$i-1]}[0,1,2] =
+               (substr($commit, 0, 8), $filename . ':' . ($s_lno+$i));
+       }
+}
+
+my $buf;
+my $current;
+sub read_blame_line {
+
+       my $r = sysread($blame, $buf, 1024, length($buf));
+       die "I/O error" unless defined $r;
+
+       if ($r == 0) {
+               flush_blame_line($current);
+               $current = undef;
+               return 0;
+       }
+
+       while ($buf =~ s/([^\n]*)\n//) {
+               my $line = $1;
+
+               if (($commit, $s_lno, $lno, $cnt) =
+                   ($line =~ /^([0-9a-f]{40}) (\d+) (\d+) (\d+)$/)) {
+                       flush_blame_line($current);
+                       $current = +{
+                               COMMIT => $1,
+                               S_LNO => $2,
+                               LNO => $3,
+                               CNT => $4,
+                       };
+                       next;
+               }
+
+               # extended attribute values
+               if ($line =~ /^(author|author-mail|author-time|author-tz|committer|committer-mail|committer-time|committer-tz|summary|filename) (.*)$/) {
+                       my $commit = $current->{COMMIT};
+                       $commitinfo{$commit}{uc($1)} = $2;
+                       next;
+               }
+       }
+       return 1;
+}
+
+sub format_time {
+  my $time = shift;
+  my $tz = shift;
+
+  my $minutes = $tz < 0 ? 0-$tz : $tz;
+  $minutes = ($minutes / 100)*60 + ($minutes % 100);
+  $minutes = $tz < 0 ? 0-$minutes : $minutes;
+  $time += $minutes * 60;
+  my @t = gmtime($time);
+  return sprintf('%04d-%02d-%02d %02d:%02d:%02d %s',
+                $t[5] + 1900, @t[4,3,2,1,0], $tz);
+}
diff --git a/contrib/colordiff/README b/contrib/colordiff/README
deleted file mode 100644 (file)
index 2678fdf..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-This is "colordiff" (http://colordiff.sourceforge.net/) by Dave
-Ewart <davee@sungate.co.uk>, modified specifically for git.
diff --git a/contrib/colordiff/colordiff.perl b/contrib/colordiff/colordiff.perl
deleted file mode 100755 (executable)
index 9566a76..0000000
+++ /dev/null
@@ -1,196 +0,0 @@
-#!/usr/bin/perl -w
-#
-# $Id: colordiff.pl,v 1.4.2.10 2004/01/04 15:02:59 daveewart Exp $
-
-########################################################################
-#                                                                      #
-# ColorDiff - a wrapper/replacment for 'diff' producing                #
-#             colourful output                                         #
-#                                                                      #
-# Copyright (C)2002-2004 Dave Ewart (davee@sungate.co.uk)              #
-#                                                                      #
-########################################################################
-#                                                                      #
-# 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., 675 Mass Ave, Cambridge, MA 02139, USA.            #
-#                                                                      #
-########################################################################
-
-use strict;
-use Getopt::Long qw(:config pass_through);
-use IPC::Open2;
-
-my $app_name     = 'colordiff';
-my $version      = '1.0.4';
-my $author       = 'Dave Ewart';
-my $author_email = 'davee@sungate.co.uk';
-my $app_www      = 'http://colordiff.sourceforge.net/';
-my $copyright    = '(C)2002-2004';
-my $show_banner  = 1;
-
-# ANSI sequences for colours
-my %colour;
-$colour{white}       = "\033[1;37m";
-$colour{yellow}      = "\033[1;33m";
-$colour{green}       = "\033[1;32m";
-$colour{blue}        = "\033[1;34m";
-$colour{cyan}        = "\033[1;36m";
-$colour{red}         = "\033[1;31m";
-$colour{magenta}     = "\033[1;35m";
-$colour{black}       = "\033[1;30m";
-$colour{darkwhite}   = "\033[0;37m";
-$colour{darkyellow}  = "\033[0;33m";
-$colour{darkgreen}   = "\033[0;32m";
-$colour{darkblue}    = "\033[0;34m";
-$colour{darkcyan}    = "\033[0;36m";
-$colour{darkred}     = "\033[0;31m";
-$colour{darkmagenta} = "\033[0;35m";
-$colour{darkblack}   = "\033[0;30m";
-$colour{OFF}         = "\033[0;0m";
-
-# Default colours if /etc/colordiffrc or ~/.colordiffrc do not exist
-my $plain_text = $colour{OFF};
-my $file_old   = $colour{red};
-my $file_new   = $colour{blue};
-my $diff_stuff = $colour{magenta};
-
-# Locations for personal and system-wide colour configurations
-my $HOME   = $ENV{HOME};
-my $etcdir = '/etc';
-
-my ($setting, $value);
-my @config_files = ("$etcdir/colordiffrc", "$HOME/.colordiffrc");
-my $config_file;
-
-foreach $config_file (@config_files) {
-    if (open(COLORDIFFRC, "<$config_file")) {
-        while (<COLORDIFFRC>) {
-            chop;
-            next if (/^#/ || /^$/);
-            s/\s+//g;
-            ($setting, $value) = split ('=');
-            if ($setting eq 'banner') {
-                if ($value eq 'no') {
-                    $show_banner = 0;
-                }
-                next;
-            }
-            if (!defined $colour{$value}) {
-                print "Invalid colour specification ($value) in $config_file\n";
-                next;
-            }
-            if ($setting eq 'plain') {
-                $plain_text = $colour{$value};
-            }
-            elsif ($setting eq 'oldtext') {
-                $file_old = $colour{$value};
-            }
-            elsif ($setting eq 'newtext') {
-                $file_new = $colour{$value};
-            }
-            elsif ($setting eq 'diffstuff') {
-                $diff_stuff = $colour{$value};
-            }
-            else {
-                print "Unknown option in $etcdir/colordiffrc: $setting\n";
-            }
-        }
-        close COLORDIFFRC;
-    }
-}
-
-# colordiff specific options here.  Need to pre-declare if using variables
-GetOptions(
-    "no-banner" => sub { $show_banner = 0 },
-    "plain-text=s" => \&set_color,
-    "file-old=s"   => \&set_color,
-    "file-new=s"   => \&set_color,
-    "diff-stuff=s" => \&set_color
-);
-
-if ($show_banner == 1) {
-    print STDERR "$app_name $version ($app_www)\n";
-    print STDERR "$copyright $author, $author_email\n\n";
-}
-
-if (defined $ARGV[0]) {
-    # More reliable way of pulling in arguments
-    open2(\*INPUTSTREAM, undef, "git", "diff", @ARGV);
-}
-else {
-    *INPUTSTREAM = \*STDIN;
-}
-
-my $record;
-my $nrecs           = 0;
-my $inside_file_old = 1;
-my $nparents        = undef;
-
-while (<INPUTSTREAM>) {
-    $nrecs++;
-    if (/^(\@\@+) -[-+0-9, ]+ \1/) {
-           print "$diff_stuff";
-           $nparents = length($1) - 1;
-    }
-    elsif (/^diff -/ || /^index / ||
-          /^old mode / || /^new mode / ||
-          /^deleted file mode / || /^new file mode / ||
-          /^similarity index / || /^dissimilarity index / ||
-          /^copy from / || /^copy to / ||
-          /^rename from / || /^rename to /) {
-           $nparents = undef;
-           print "$diff_stuff";
-    }
-    elsif (defined $nparents) {
-           if ($nparents == 1) {
-                   if (/^\+/) {
-                           print $file_new;
-                   }
-                   elsif (/^-/) {
-                           print $file_old;
-                   }
-                   else {
-                           print $plain_text;
-                   }
-           }
-           elsif (/^ {$nparents}/) {
-                   print "$plain_text";
-           }
-           elsif (/^[+ ]{$nparents}/) {
-                   print "$file_new";
-           }
-           elsif (/^[- ]{$nparents}/) {
-                   print "$file_old";
-           }
-           else {
-                   print $plain_text;
-           }
-    }
-    elsif (/^--- / || /^\+\+\+ /) {
-           print $diff_stuff;
-    }
-    else {
-           print "$plain_text";
-    }
-    s/$/$colour{OFF}/;
-    print "$_";
-}
-close INPUTSTREAM;
-
-sub set_color {
-    my ($type, $color) = @_;
-
-    $type =~ s/-/_/;
-    eval "\$$type = \$colour{$color}";
-}
index a43a177160b3b2952e4025bebc47c820ff4003f7..7c03403484f3a52c9588aa3bfc58dea4f394dabe 100755 (executable)
@@ -1,7 +1,7 @@
 #
 # bash completion support for core Git.
 #
-# Copyright (C) 2006 Shawn Pearce
+# Copyright (C) 2006,2007 Shawn Pearce
 # Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
 #
 # The contained completion routines provide support for completing:
 #    2) Added the following line to your .bashrc:
 #        source ~/.git-completion.sh
 #
+#    3) You may want to make sure the git executable is available
+#       in your PATH before this script is sourced, as some caching
+#       is performed while the script loads.  If git isn't found
+#       at source time then all lookups will be done on demand,
+#       which may be slightly slower.
+#
+#    4) Consider changing your PS1 to also show the current branch:
+#        PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+#
+#       The argument to __git_ps1 will be displayed only if you
+#       are currently in a git repository.  The %s token will be
+#       the name of the current branch.
+#
 
 __gitdir ()
 {
-       echo "${__git_dir:-$(git rev-parse --git-dir 2>/dev/null)}"
+       if [ -z "$1" ]; then
+               if [ -n "$__git_dir" ]; then
+                       echo "$__git_dir"
+               elif [ -d .git ]; then
+                       echo .git
+               else
+                       git rev-parse --git-dir 2>/dev/null
+               fi
+       elif [ -d "$1/.git" ]; then
+               echo "$1/.git"
+       else
+               echo "$1"
+       fi
+}
+
+__git_ps1 ()
+{
+       local b="$(git symbolic-ref HEAD 2>/dev/null)"
+       if [ -n "$b" ]; then
+               if [ -n "$1" ]; then
+                       printf "$1" "${b##refs/heads/}"
+               else
+                       printf " (%s)" "${b##refs/heads/}"
+               fi
+       fi
+}
+
+__gitcomp ()
+{
+       local all c s=$'\n' IFS=' '$'\t'$'\n'
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       if [ $# -gt 2 ]; then
+               cur="$3"
+       fi
+       for c in $1; do
+               case "$c$4" in
+               --*=*) all="$all$c$4$s" ;;
+               *.)    all="$all$c$4$s" ;;
+               *)     all="$all$c$4 $s" ;;
+               esac
+       done
+       IFS=$s
+       COMPREPLY=($(compgen -P "$2" -W "$all" -- "$cur"))
+       return
+}
+
+__git_heads ()
+{
+       local cmd i is_hash=y dir="$(__gitdir "$1")"
+       if [ -d "$dir" ]; then
+               for i in $(git --git-dir="$dir" \
+                       for-each-ref --format='%(refname)' \
+                       refs/heads ); do
+                       echo "${i#refs/heads/}"
+               done
+               return
+       fi
+       for i in $(git-ls-remote "$1" 2>/dev/null); do
+               case "$is_hash,$i" in
+               y,*) is_hash=n ;;
+               n,*^{}) is_hash=y ;;
+               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+               n,*) is_hash=y; echo "$i" ;;
+               esac
+       done
 }
 
 __git_refs ()
 {
-       local cmd i is_hash=y dir="${1:-$(__gitdir)}"
+       local cmd i is_hash=y dir="$(__gitdir "$1")"
        if [ -d "$dir" ]; then
-               cmd=git-peek-remote
-       else
-               cmd=git-ls-remote
+               if [ -e "$dir/HEAD" ]; then echo HEAD; fi
+               for i in $(git --git-dir="$dir" \
+                       for-each-ref --format='%(refname)' \
+                       refs/tags refs/heads refs/remotes); do
+                       case "$i" in
+                               refs/tags/*)    echo "${i#refs/tags/}" ;;
+                               refs/heads/*)   echo "${i#refs/heads/}" ;;
+                               refs/remotes/*) echo "${i#refs/remotes/}" ;;
+                               *)              echo "$i" ;;
+                       esac
+               done
+               return
        fi
-       for i in $($cmd "$dir" 2>/dev/null); do
+       for i in $(git-ls-remote "$dir" 2>/dev/null); do
                case "$is_hash,$i" in
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
                n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
                n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+               n,refs/remotes/*) is_hash=y; echo "${i#refs/remotes/}" ;;
                n,*) is_hash=y; echo "$i" ;;
                esac
        done
@@ -45,19 +132,25 @@ __git_refs ()
 
 __git_refs2 ()
 {
-       local cmd i is_hash=y dir="${1:-$(__gitdir)}"
-       if [ -d "$dir" ]; then
-               cmd=git-peek-remote
-       else
-               cmd=git-ls-remote
-       fi
-       for i in $($cmd "$dir" 2>/dev/null); do
+       local i
+       for i in $(__git_refs "$1"); do
+               echo "$i:$i"
+       done
+}
+
+__git_refs_remotes ()
+{
+       local cmd i is_hash=y
+       for i in $(git-ls-remote "$1" 2>/dev/null); do
                case "$is_hash,$i" in
+               n,refs/heads/*)
+                       is_hash=y
+                       echo "$i:refs/remotes/$1/${i#refs/heads/}"
+                       ;;
                y,*) is_hash=n ;;
                n,*^{}) is_hash=y ;;
-               n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;;
-               n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;;
-               n,*) is_hash=y; echo "$i:$i" ;;
+               n,refs/tags/*) is_hash=y;;
+               n,*) is_hash=y; ;;
                esac
        done
 }
@@ -71,7 +164,7 @@ __git_remotes ()
                echo ${i#$d/remotes/}
        done
        [ "$ngoff" ] && shopt -u nullglob
-       for i in $(git --git-dir="$d" repo-config --list); do
+       for i in $(git --git-dir="$d" config --list); do
                case "$i" in
                remote.*.url=*)
                        i="${i#remote.}"
@@ -81,6 +174,22 @@ __git_remotes ()
        done
 }
 
+__git_merge_strategies ()
+{
+       if [ -n "$__git_merge_strategylist" ]; then
+               echo "$__git_merge_strategylist"
+               return
+       fi
+       sed -n "/^all_strategies='/{
+               s/^all_strategies='//
+               s/'//
+               p
+               q
+               }" "$(git --exec-path)/git-merge"
+}
+__git_merge_strategylist=
+__git_merge_strategylist="$(__git_merge_strategies 2>/dev/null)"
+
 __git_complete_file ()
 {
        local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
@@ -110,15 +219,117 @@ __git_complete_file ()
                        -- "$cur"))
                ;;
        *)
-               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+               __gitcomp "$(__git_refs)"
                ;;
        esac
 }
 
+__git_complete_revlist ()
+{
+       local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       *...*)
+               pfx="${cur%...*}..."
+               cur="${cur#*...}"
+               __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               ;;
+       *..*)
+               pfx="${cur%..*}.."
+               cur="${cur#*..}"
+               __gitcomp "$(__git_refs)" "$pfx" "$cur"
+               ;;
+       *.)
+               __gitcomp "$cur."
+               ;;
+       *)
+               __gitcomp "$(__git_refs)"
+               ;;
+       esac
+}
+
+__git_commands ()
+{
+       if [ -n "$__git_commandlist" ]; then
+               echo "$__git_commandlist"
+               return
+       fi
+       local i IFS=" "$'\n'
+       for i in $(git help -a|egrep '^ ')
+       do
+               case $i in
+               add--interactive) : plumbing;;
+               applymbox)        : ask gittus;;
+               applypatch)       : ask gittus;;
+               archimport)       : import;;
+               cat-file)         : plumbing;;
+               check-ref-format) : plumbing;;
+               commit-tree)      : plumbing;;
+               convert-objects)  : plumbing;;
+               cvsexportcommit)  : export;;
+               cvsimport)        : import;;
+               cvsserver)        : daemon;;
+               daemon)           : daemon;;
+               fast-import)      : import;;
+               fsck-objects)     : plumbing;;
+               fetch-pack)       : plumbing;;
+               fmt-merge-msg)    : plumbing;;
+               hash-object)      : plumbing;;
+               http-*)           : transport;;
+               index-pack)       : plumbing;;
+               init-db)          : deprecated;;
+               local-fetch)      : plumbing;;
+               mailinfo)         : plumbing;;
+               mailsplit)        : plumbing;;
+               merge-*)          : plumbing;;
+               mktree)           : plumbing;;
+               mktag)            : plumbing;;
+               pack-objects)     : plumbing;;
+               pack-redundant)   : plumbing;;
+               pack-refs)        : plumbing;;
+               parse-remote)     : plumbing;;
+               patch-id)         : plumbing;;
+               peek-remote)      : plumbing;;
+               prune)            : plumbing;;
+               prune-packed)     : plumbing;;
+               quiltimport)      : import;;
+               read-tree)        : plumbing;;
+               receive-pack)     : plumbing;;
+               reflog)           : plumbing;;
+               repo-config)      : plumbing;;
+               rerere)           : plumbing;;
+               rev-list)         : plumbing;;
+               rev-parse)        : plumbing;;
+               runstatus)        : plumbing;;
+               sh-setup)         : internal;;
+               shell)            : daemon;;
+               send-pack)        : plumbing;;
+               show-index)       : plumbing;;
+               ssh-*)            : transport;;
+               stripspace)       : plumbing;;
+               svn)              : import export;;
+               svnimport)        : import;;
+               symbolic-ref)     : plumbing;;
+               tar-tree)         : deprecated;;
+               unpack-file)      : plumbing;;
+               unpack-objects)   : plumbing;;
+               update-index)     : plumbing;;
+               update-ref)       : plumbing;;
+               update-server-info) : daemon;;
+               upload-archive)   : plumbing;;
+               upload-pack)      : plumbing;;
+               write-tree)       : plumbing;;
+               verify-tag)       : plumbing;;
+               *) echo $i;;
+               esac
+       done
+}
+__git_commandlist=
+__git_commandlist="$(__git_commands 2>/dev/null)"
+
 __git_aliases ()
 {
        local i IFS=$'\n'
-       for i in $(git --git-dir="$(__gitdir)" repo-config --list); do
+       for i in $(git --git-dir="$(__gitdir)" config --list); do
                case "$i" in
                alias.*)
                        i="${i#alias.}"
@@ -131,7 +342,7 @@ __git_aliases ()
 __git_aliased_command ()
 {
        local word cmdline=$(git --git-dir="$(__gitdir)" \
-               repo-config --get "alias.$1")
+               config --get "alias.$1")
        for word in $cmdline; do
                if [ "${word##-*}" ]; then
                        echo $word
@@ -140,32 +351,130 @@ __git_aliased_command ()
        done
 }
 
-_git_branch ()
+__git_whitespacelist="nowarn warn error error-all strip"
+
+_git_am ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs)" -- "$cur"))
+       if [ -d .dotest ]; then
+               __gitcomp "--skip --resolved"
+               return
+       fi
+       case "$cur" in
+       --whitespace=*)
+               __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
+               return
+               ;;
+       --*)
+               __gitcomp "
+                       --signoff --utf8 --binary --3way --interactive
+                       --whitespace=
+                       "
+               return
+       esac
+       COMPREPLY=()
 }
 
-_git_cat_file ()
+_git_apply ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       case "${COMP_WORDS[0]},$COMP_CWORD" in
-       git-cat-file*,1)
-               COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+       case "$cur" in
+       --whitespace=*)
+               __gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
+               return
                ;;
-       git,2)
-               COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+       --*)
+               __gitcomp "
+                       --stat --numstat --summary --check --index
+                       --cached --index-info --reverse --reject --unidiff-zero
+                       --apply --no-add --exclude=
+                       --whitespace= --inaccurate-eof --verbose
+                       "
+               return
+       esac
+       COMPREPLY=()
+}
+
+_git_add ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "--interactive"
+               return
+       esac
+       COMPREPLY=()
+}
+
+_git_bisect ()
+{
+       local i c=1 command
+       while [ $c -lt $COMP_CWORD ]; do
+               i="${COMP_WORDS[c]}"
+               case "$i" in
+               start|bad|good|reset|visualize|replay|log)
+                       command="$i"
+                       break
+                       ;;
+               esac
+               c=$((++c))
+       done
+
+       if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
+               __gitcomp "start bad good reset visualize replay log"
+               return
+       fi
+
+       case "$command" in
+       bad|good|reset)
+               __gitcomp "$(__git_refs)"
                ;;
        *)
-               __git_complete_file
+               COMPREPLY=()
                ;;
        esac
 }
 
+_git_branch ()
+{
+       __gitcomp "$(__git_refs)"
+}
+
 _git_checkout ()
+{
+       __gitcomp "$(__git_refs)"
+}
+
+_git_cherry ()
+{
+       __gitcomp "$(__git_refs)"
+}
+
+_git_cherry_pick ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "--edit --no-commit"
+               ;;
+       *)
+               __gitcomp "$(__git_refs)"
+               ;;
+       esac
+}
+
+_git_commit ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "-l -b $(__git_refs)" -- "$cur"))
+       case "$cur" in
+       --*)
+               __gitcomp "
+                       --all --author= --signoff --verify --no-verify
+                       --edit --amend --include --only
+                       "
+               return
+       esac
+       COMPREPLY=()
 }
 
 _git_diff ()
@@ -175,8 +484,7 @@ _git_diff ()
 
 _git_diff_tree ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "-r -p -M $(__git_refs)" -- "$cur"))
+       __gitcomp "$(__git_refs)"
 }
 
 _git_fetch ()
@@ -185,16 +493,15 @@ _git_fetch ()
 
        case "${COMP_WORDS[0]},$COMP_CWORD" in
        git-fetch*,1)
-               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               __gitcomp "$(__git_remotes)"
                ;;
        git,2)
-               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               __gitcomp "$(__git_remotes)"
                ;;
        *)
                case "$cur" in
                *:*)
-                       cur="${cur#*:}"
-                       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+                       __gitcomp "$(__git_refs)" "" "${cur#*:}"
                        ;;
                *)
                        local remote
@@ -202,17 +509,49 @@ _git_fetch ()
                        git-fetch) remote="${COMP_WORDS[1]}" ;;
                        git)       remote="${COMP_WORDS[2]}" ;;
                        esac
-                       COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur"))
+                       __gitcomp "$(__git_refs2 "$remote")"
                        ;;
                esac
                ;;
        esac
 }
 
-_git_ls_remote ()
+_git_format_patch ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+       case "$cur" in
+       --*)
+               __gitcomp "
+                       --stdout --attach --thread
+                       --output-directory
+                       --numbered --start-number
+                       --keep-subject
+                       --signoff
+                       --in-reply-to=
+                       --full-index --binary
+                       --not --all
+                       "
+               return
+               ;;
+       esac
+       __git_complete_revlist
+}
+
+_git_gc ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "$cur" in
+       --*)
+               __gitcomp "--prune"
+               return
+               ;;
+       esac
+       COMPREPLY=()
+}
+
+_git_ls_remote ()
+{
+       __gitcomp "$(__git_remotes)"
 }
 
 _git_ls_tree ()
@@ -222,28 +561,63 @@ _git_ls_tree ()
 
 _git_log ()
 {
-       local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+       local cur="${COMP_WORDS[COMP_CWORD]}"
        case "$cur" in
-       *...*)
-               pfx="${cur%...*}..."
-               cur="${cur#*...}"
-               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+       --pretty=*)
+               __gitcomp "
+                       oneline short medium full fuller email raw
+                       " "" "${cur##--pretty=}"
+               return
                ;;
-       *..*)
-               pfx="${cur%..*}.."
-               cur="${cur#*..}"
-               COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs)" -- "$cur"))
+       --*)
+               __gitcomp "
+                       --max-count= --max-age= --since= --after=
+                       --min-age= --before= --until=
+                       --root --not --topo-order --date-order
+                       --no-merges
+                       --abbrev-commit --abbrev=
+                       --relative-date
+                       --author= --committer= --grep=
+                       --all-match
+                       --pretty= --name-status --name-only
+                       --not --all
+                       "
+               return
                ;;
-       *)
-               COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+       esac
+       __git_complete_revlist
+}
+
+_git_merge ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -s|--strategy)
+               __gitcomp "$(__git_merge_strategies)"
+               return
+       esac
+       case "$cur" in
+       --strategy=*)
+               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+               return
                ;;
+       --*)
+               __gitcomp "
+                       --no-commit --no-summary --squash --strategy
+                       "
+               return
        esac
+       __gitcomp "$(__git_refs)"
 }
 
 _git_merge_base ()
 {
-       local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+       __gitcomp "$(__git_refs)"
+}
+
+_git_name_rev ()
+{
+       __gitcomp "--tags --all --stdin"
 }
 
 _git_pull ()
@@ -252,10 +626,10 @@ _git_pull ()
 
        case "${COMP_WORDS[0]},$COMP_CWORD" in
        git-pull*,1)
-               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               __gitcomp "$(__git_remotes)"
                ;;
        git,2)
-               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               __gitcomp "$(__git_remotes)"
                ;;
        *)
                local remote
@@ -263,7 +637,7 @@ _git_pull ()
                git-pull)  remote="${COMP_WORDS[1]}" ;;
                git)       remote="${COMP_WORDS[2]}" ;;
                esac
-               COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+               __gitcomp "$(__git_refs "$remote")"
                ;;
        esac
 }
@@ -274,10 +648,10 @@ _git_push ()
 
        case "${COMP_WORDS[0]},$COMP_CWORD" in
        git-push*,1)
-               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               __gitcomp "$(__git_remotes)"
                ;;
        git,2)
-               COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+               __gitcomp "$(__git_remotes)"
                ;;
        *)
                case "$cur" in
@@ -287,28 +661,250 @@ _git_push ()
                        git-push)  remote="${COMP_WORDS[1]}" ;;
                        git)       remote="${COMP_WORDS[2]}" ;;
                        esac
-                       cur="${cur#*:}"
-                       COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+                       __gitcomp "$(__git_refs "$remote")" "" "${cur#*:}"
                        ;;
                *)
-                       COMPREPLY=($(compgen -W "$(__git_refs2)" -- "$cur"))
+                       __gitcomp "$(__git_refs2)"
                        ;;
                esac
                ;;
        esac
 }
 
+_git_rebase ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       if [ -d .dotest ] || [ -d .git/.dotest-merge ]; then
+               __gitcomp "--continue --skip --abort"
+               return
+       fi
+       case "${COMP_WORDS[COMP_CWORD-1]}" in
+       -s|--strategy)
+               __gitcomp "$(__git_merge_strategies)"
+               return
+       esac
+       case "$cur" in
+       --strategy=*)
+               __gitcomp "$(__git_merge_strategies)" "" "${cur##--strategy=}"
+               return
+               ;;
+       --*)
+               __gitcomp "--onto --merge --strategy"
+               return
+       esac
+       __gitcomp "$(__git_refs)"
+}
+
+_git_config ()
+{
+       local cur="${COMP_WORDS[COMP_CWORD]}"
+       local prv="${COMP_WORDS[COMP_CWORD-1]}"
+       case "$prv" in
+       branch.*.remote)
+               __gitcomp "$(__git_remotes)"
+               return
+               ;;
+       branch.*.merge)
+               __gitcomp "$(__git_refs)"
+               return
+               ;;
+       remote.*.fetch)
+               local remote="${prv#remote.}"
+               remote="${remote%.fetch}"
+               __gitcomp "$(__git_refs_remotes "$remote")"
+               return
+               ;;
+       remote.*.push)
+               local remote="${prv#remote.}"
+               remote="${remote%.push}"
+               __gitcomp "$(git --git-dir="$(__gitdir)" \
+                       for-each-ref --format='%(refname):%(refname)' \
+                       refs/heads)"
+               return
+               ;;
+       pull.twohead|pull.octopus)
+               __gitcomp "$(__git_merge_strategies)"
+               return
+               ;;
+       color.branch|color.diff|color.status)
+               __gitcomp "always never auto"
+               return
+               ;;
+       color.*.*)
+               __gitcomp "
+                       black red green yellow blue magenta cyan white
+                       bold dim ul blink reverse
+                       "
+               return
+               ;;
+       *.*)
+               COMPREPLY=()
+               return
+               ;;
+       esac
+       case "$cur" in
+       --*)
+               __gitcomp "
+                       --global --list --replace-all
+                       --get --get-all --get-regexp
+                       --add --unset --unset-all
+                       "
+               return
+               ;;
+       branch.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               __gitcomp "remote merge" "$pfx" "$cur"
+               return
+               ;;
+       branch.*)
+               local pfx="${cur%.*}."
+               cur="${cur#*.}"
+               __gitcomp "$(__git_heads)" "$pfx" "$cur" "."
+               return
+               ;;
+       remote.*.*)
+               local pfx="${cur%.*}."
+               cur="${cur##*.}"
+               __gitcomp "url fetch push" "$pfx" "$cur"
+               return
+               ;;
+       remote.*)
+               local pfx="${cur%.*}."
+               cur="${cur#*.}"
+               __gitcomp "$(__git_remotes)" "$pfx" "$cur" "."
+               return
+               ;;
+       esac
+       __gitcomp "
+               apply.whitespace
+               core.fileMode
+               core.gitProxy
+               core.ignoreStat
+               core.preferSymlinkRefs
+               core.logAllRefUpdates
+               core.repositoryFormatVersion
+               core.sharedRepository
+               core.warnAmbiguousRefs
+               core.compression
+               core.legacyHeaders
+               core.packedGitWindowSize
+               core.packedGitLimit
+               color.branch
+               color.branch.current
+               color.branch.local
+               color.branch.remote
+               color.branch.plain
+               color.diff
+               color.diff.plain
+               color.diff.meta
+               color.diff.frag
+               color.diff.old
+               color.diff.new
+               color.diff.commit
+               color.diff.whitespace
+               color.pager
+               color.status
+               color.status.header
+               color.status.added
+               color.status.changed
+               color.status.untracked
+               diff.renameLimit
+               diff.renames
+               fetch.unpackLimit
+               format.headers
+               gitcvs.enabled
+               gitcvs.logfile
+               gc.reflogexpire
+               gc.reflogexpireunreachable
+               gc.rerereresolved
+               gc.rerereunresolved
+               http.sslVerify
+               http.sslCert
+               http.sslKey
+               http.sslCAInfo
+               http.sslCAPath
+               http.maxRequests
+               http.lowSpeedLimit
+               http.lowSpeedTime
+               http.noEPSV
+               i18n.commitEncoding
+               i18n.logOutputEncoding
+               log.showroot
+               merge.summary
+               merge.verbosity
+               pack.window
+               pull.octopus
+               pull.twohead
+               repack.useDeltaBaseOffset
+               show.difftree
+               showbranch.default
+               tar.umask
+               transfer.unpackLimit
+               receive.unpackLimit
+               receive.denyNonFastForwards
+               user.name
+               user.email
+               user.signingkey
+               whatchanged.difftree
+               branch. remote.
+       "
+}
+
+_git_remote ()
+{
+       local i c=1 command
+       while [ $c -lt $COMP_CWORD ]; do
+               i="${COMP_WORDS[c]}"
+               case "$i" in
+               add|show|prune) command="$i"; break ;;
+               esac
+               c=$((++c))
+       done
+
+       if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
+               __gitcomp "add show prune"
+               return
+       fi
+
+       case "$command" in
+       show|prune)
+               __gitcomp "$(__git_remotes)"
+               ;;
+       *)
+               COMPREPLY=()
+               ;;
+       esac
+}
+
 _git_reset ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       local opt="--mixed --hard --soft"
-       COMPREPLY=($(compgen -W "$opt $(__git_refs)" -- "$cur"))
+       case "$cur" in
+       --*)
+               __gitcomp "--mixed --hard --soft"
+               return
+               ;;
+       esac
+       __gitcomp "$(__git_refs)"
 }
 
 _git_show ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "$(__git_refs)" -- "$cur"))
+       case "$cur" in
+       --pretty=*)
+               __gitcomp "
+                       oneline short medium full fuller email raw
+                       " "" "${cur##--pretty=}"
+               return
+               ;;
+       --*)
+               __gitcomp "--pretty="
+               return
+               ;;
+       esac
+       __git_complete_file
 }
 
 _git ()
@@ -327,30 +923,43 @@ _git ()
        done
 
        if [ $c -eq $COMP_CWORD -a -z "$command" ]; then
-               COMPREPLY=($(compgen \
-                       -W "--git-dir= --version \
-                               $(git help -a|egrep '^ ') \
-                           $(__git_aliases)" \
-                       -- "${COMP_WORDS[COMP_CWORD]}"))
-               return;
+               case "${COMP_WORDS[COMP_CWORD]}" in
+               --*=*) COMPREPLY=() ;;
+               --*)   __gitcomp "--git-dir= --bare --version --exec-path" ;;
+               *)     __gitcomp "$(__git_commands) $(__git_aliases)" ;;
+               esac
+               return
        fi
 
        local expansion=$(__git_aliased_command "$command")
        [ "$expansion" ] && command="$expansion"
 
        case "$command" in
+       am)          _git_am ;;
+       add)         _git_add ;;
+       apply)       _git_apply ;;
+       bisect)      _git_bisect ;;
        branch)      _git_branch ;;
-       cat-file)    _git_cat_file ;;
        checkout)    _git_checkout ;;
+       cherry)      _git_cherry ;;
+       cherry-pick) _git_cherry_pick ;;
+       commit)      _git_commit ;;
+       config)      _git_config ;;
        diff)        _git_diff ;;
        diff-tree)   _git_diff_tree ;;
        fetch)       _git_fetch ;;
+       format-patch) _git_format_patch ;;
+       gc)          _git_gc ;;
        log)         _git_log ;;
        ls-remote)   _git_ls_remote ;;
        ls-tree)     _git_ls_tree ;;
+       merge)       _git_merge;;
        merge-base)  _git_merge_base ;;
+       name-rev)    _git_name_rev ;;
        pull)        _git_pull ;;
        push)        _git_push ;;
+       rebase)      _git_rebase ;;
+       remote)      _git_remote ;;
        reset)       _git_reset ;;
        show)        _git_show ;;
        show-branch) _git_log ;;
@@ -362,25 +971,43 @@ _git ()
 _gitk ()
 {
        local cur="${COMP_WORDS[COMP_CWORD]}"
-       COMPREPLY=($(compgen -W "--all $(__git_refs)" -- "$cur"))
+       case "$cur" in
+       --*)
+               __gitcomp "--not --all"
+               return
+               ;;
+       esac
+       __git_complete_revlist
 }
 
 complete -o default -o nospace -F _git git
-complete -o default            -F _gitk gitk
-complete -o default            -F _git_branch git-branch
-complete -o default -o nospace -F _git_cat_file git-cat-file
-complete -o default            -F _git_checkout git-checkout
+complete -o default -o nospace -F _gitk gitk
+complete -o default -o nospace -F _git_am git-am
+complete -o default -o nospace -F _git_apply git-apply
+complete -o default -o nospace -F _git_bisect git-bisect
+complete -o default -o nospace -F _git_branch git-branch
+complete -o default -o nospace -F _git_checkout git-checkout
+complete -o default -o nospace -F _git_cherry git-cherry
+complete -o default -o nospace -F _git_cherry_pick git-cherry-pick
+complete -o default -o nospace -F _git_commit git-commit
 complete -o default -o nospace -F _git_diff git-diff
-complete -o default            -F _git_diff_tree git-diff-tree
+complete -o default -o nospace -F _git_diff_tree git-diff-tree
 complete -o default -o nospace -F _git_fetch git-fetch
+complete -o default -o nospace -F _git_format_patch git-format-patch
+complete -o default -o nospace -F _git_gc git-gc
 complete -o default -o nospace -F _git_log git-log
-complete -o default            -F _git_ls_remote git-ls-remote
+complete -o default -o nospace -F _git_ls_remote git-ls-remote
 complete -o default -o nospace -F _git_ls_tree git-ls-tree
-complete -o default            -F _git_merge_base git-merge-base
+complete -o default -o nospace -F _git_merge git-merge
+complete -o default -o nospace -F _git_merge_base git-merge-base
+complete -o default -o nospace -F _git_name_rev git-name-rev
 complete -o default -o nospace -F _git_pull git-pull
 complete -o default -o nospace -F _git_push git-push
-complete -o default            -F _git_reset git-reset
-complete -o default            -F _git_show git-show
+complete -o default -o nospace -F _git_rebase git-rebase
+complete -o default -o nospace -F _git_config git-config
+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_show git-show
 complete -o default -o nospace -F _git_log git-show-branch
 complete -o default -o nospace -F _git_log git-whatchanged
 
@@ -389,15 +1016,21 @@ complete -o default -o nospace -F _git_log git-whatchanged
 # included the '.exe' suffix.
 #
 if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
+complete -o default -o nospace -F _git_add git-add.exe
+complete -o default -o nospace -F _git_apply git-apply.exe
 complete -o default -o nospace -F _git git.exe
-complete -o default            -F _git_branch git-branch.exe
-complete -o default -o nospace -F _git_cat_file git-cat-file.exe
+complete -o default -o nospace -F _git_branch git-branch.exe
+complete -o default -o nospace -F _git_cherry git-cherry.exe
 complete -o default -o nospace -F _git_diff git-diff.exe
 complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
+complete -o default -o nospace -F _git_format_patch git-format-patch.exe
 complete -o default -o nospace -F _git_log git-log.exe
 complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
-complete -o default            -F _git_merge_base git-merge-base.exe
+complete -o default -o nospace -F _git_merge_base git-merge-base.exe
+complete -o default -o nospace -F _git_name_rev git-name-rev.exe
 complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -o nospace -F _git_config git-config
+complete -o default -o nospace -F _git_show git-show.exe
 complete -o default -o nospace -F _git_log git-show-branch.exe
 complete -o default -o nospace -F _git_log git-whatchanged.exe
 fi
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
new file mode 100644 (file)
index 0000000..64ad50b
--- /dev/null
@@ -0,0 +1,380 @@
+;;; git-blame.el --- Minor mode for incremental blame for Git  -*- coding: utf-8 -*-
+;;
+;; Copyright (C) 2007  David Kågedal
+;;
+;; Authors:    David Kågedal <davidk@lysator.liu.se>
+;; Created:    31 Jan 2007
+;; Message-ID: <87iren2vqx.fsf@morpheus.local>
+;; License:    GPL
+;; Keywords:   git, version control, release management
+;;
+;; Compatibility: Emacs21
+
+
+;; This file is *NOT* part of GNU Emacs.
+;; This file is distributed under the same terms as GNU Emacs.
+
+;; 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
+
+;; http://www.fsf.org/copyleft/gpl.html
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Commentary:
+;;
+;; Here is an Emacs implementation of incremental git-blame.  When you
+;; turn it on while viewing a file, the editor buffer will be updated by
+;; setting the background of individual lines to a color that reflects
+;; which commit it comes from.  And when you move around the buffer, a
+;; one-line summary will be shown in the echo area.
+
+;;; Installation:
+;;
+;; To use this package, put it somewhere in `load-path' (or add
+;; directory with git-blame.el to `load-path'), and add the following
+;; line to your .emacs:
+;;
+;;    (require 'git-blame)
+;;
+;; If you do not want to load this package before it is necessary, you
+;; can make use of the `autoload' feature, e.g. by adding to your .emacs
+;; the following lines
+;;
+;;    (autoload 'git-blame-mode "git-blame"
+;;              "Minor mode for incremental blame for Git." t)
+;;
+;; Then first use of `M-x git-blame-mode' would load the package.
+
+;;; Compatibility:
+;;
+;; It requires GNU Emacs 21.  If you'are using Emacs 20, try
+;; changing this:
+;;
+;;            (overlay-put ovl 'face (list :background
+;;                                         (cdr (assq 'color (cddddr info)))))
+;;
+;; to
+;;
+;;            (overlay-put ovl 'face (cons 'background-color
+;;                                         (cdr (assq 'color (cddddr info)))))
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Code:
+
+(require 'cl)                        ; to use `push', `pop'
+
+(defun color-scale (l)
+  (let* ((colors ())
+         r g b)
+    (setq r l)
+    (while r
+      (setq g l)
+      (while g
+        (setq b l)
+        (while b
+          (push (concat "#" (car r) (car g) (car b)) colors)
+          (pop b))
+        (pop g))
+      (pop r))
+    colors))
+
+(defvar git-blame-dark-colors
+  (color-scale '("0c" "04" "24" "1c" "2c" "34" "14" "3c")))
+
+(defvar git-blame-light-colors
+  (color-scale '("c4" "d4" "cc" "dc" "f4" "e4" "fc" "ec")))
+
+(defvar git-blame-ancient-color "dark green")
+
+(defvar git-blame-autoupdate t
+  "*Automatically update the blame display while editing")
+
+(defvar git-blame-proc nil
+  "The running git-blame process")
+(make-variable-buffer-local 'git-blame-proc)
+
+(defvar git-blame-overlays nil
+  "The git-blame overlays used in the current buffer.")
+(make-variable-buffer-local 'git-blame-overlays)
+
+(defvar git-blame-cache nil
+  "A cache of git-blame information for the current buffer")
+(make-variable-buffer-local 'git-blame-cache)
+
+(defvar git-blame-idle-timer nil
+  "An idle timer that updates the blame")
+(make-variable-buffer-local 'git-blame-cache)
+
+(defvar git-blame-update-queue nil
+  "A queue of update requests")
+(make-variable-buffer-local 'git-blame-update-queue)
+
+(defvar git-blame-mode nil)
+(make-variable-buffer-local 'git-blame-mode)
+(unless (assq 'git-blame-mode minor-mode-alist)
+  (setq minor-mode-alist
+       (cons (list 'git-blame-mode " blame")
+             minor-mode-alist)))
+
+;;;###autoload
+(defun git-blame-mode (&optional arg)
+  "Minor mode for displaying Git blame"
+  (interactive "P")
+  (if arg
+      (setq git-blame-mode (eq arg 1))
+    (setq git-blame-mode (not git-blame-mode)))
+  (make-local-variable 'git-blame-colors)
+  (if git-blame-autoupdate
+      (add-hook 'after-change-functions 'git-blame-after-change nil t)
+    (remove-hook 'after-change-functions 'git-blame-after-change t))
+  (git-blame-cleanup)
+  (if git-blame-mode
+      (progn
+        (let ((bgmode (cdr (assoc 'background-mode (frame-parameters)))))
+          (if (eq bgmode 'dark)
+              (setq git-blame-colors git-blame-dark-colors)
+            (setq git-blame-colors git-blame-light-colors)))
+        (setq git-blame-cache (make-hash-table :test 'equal))
+        (git-blame-run))
+    (cancel-timer git-blame-idle-timer)))
+
+;;;###autoload
+(defun git-reblame ()
+  "Recalculate all blame information in the current buffer"
+  (unless git-blame-mode
+    (error "git-blame is not active"))
+  (interactive)
+  (git-blame-cleanup)
+  (git-blame-run))
+
+(defun git-blame-run (&optional startline endline)
+  (if git-blame-proc
+      ;; Should maybe queue up a new run here
+      (message "Already running git blame")
+    (let ((display-buf (current-buffer))
+          (blame-buf (get-buffer-create
+                      (concat " git blame for " (buffer-name))))
+          (args '("--incremental" "--contents" "-")))
+      (if startline
+          (setq args (append args
+                             (list "-L" (format "%d,%d" startline endline)))))
+      (setq args (append args
+                         (list (file-name-nondirectory buffer-file-name))))
+      (setq git-blame-proc
+            (apply 'start-process
+                   "git-blame" blame-buf
+                   "git" "blame"
+                   args))
+      (with-current-buffer blame-buf
+        (erase-buffer)
+        (make-local-variable 'git-blame-file)
+        (make-local-variable 'git-blame-current)
+        (setq git-blame-file display-buf)
+        (setq git-blame-current nil))
+      (set-process-filter git-blame-proc 'git-blame-filter)
+      (set-process-sentinel git-blame-proc 'git-blame-sentinel)
+      (process-send-region git-blame-proc (point-min) (point-max))
+      (process-send-eof git-blame-proc))))
+
+(defun remove-git-blame-text-properties (start end)
+  (let ((modified (buffer-modified-p))
+        (inhibit-read-only t))
+    (remove-text-properties start end '(point-entered nil))
+    (set-buffer-modified-p modified)))
+
+(defun git-blame-cleanup ()
+  "Remove all blame properties"
+    (mapcar 'delete-overlay git-blame-overlays)
+    (setq git-blame-overlays nil)
+    (remove-git-blame-text-properties (point-min) (point-max)))
+
+(defun git-blame-update-region (start end)
+  "Rerun blame to get updates between START and END"
+  (let ((overlays (overlays-in start end)))
+    (while overlays
+      (let ((overlay (pop overlays)))
+        (if (< (overlay-start overlay) start)
+            (setq start (overlay-start overlay)))
+        (if (> (overlay-end overlay) end)
+            (setq end (overlay-end overlay)))
+        (setq git-blame-overlays (delete overlay git-blame-overlays))
+        (delete-overlay overlay))))
+  (remove-git-blame-text-properties start end)
+  ;; We can be sure that start and end are at line breaks
+  (git-blame-run (1+ (count-lines (point-min) start))
+                 (count-lines (point-min) end)))
+
+(defun git-blame-sentinel (proc status)
+  (with-current-buffer (process-buffer proc)
+    (with-current-buffer git-blame-file
+      (setq git-blame-proc nil)
+      (if git-blame-update-queue
+          (git-blame-delayed-update))))
+  ;;(kill-buffer (process-buffer proc))
+  ;;(message "git blame finished")
+  )
+
+(defvar in-blame-filter nil)
+
+(defun git-blame-filter (proc str)
+  (save-excursion
+    (set-buffer (process-buffer proc))
+    (goto-char (process-mark proc))
+    (insert-before-markers str)
+    (goto-char 0)
+    (unless in-blame-filter
+      (let ((more t)
+            (in-blame-filter t))
+        (while more
+          (setq more (git-blame-parse)))))))
+
+(defun git-blame-parse ()
+  (cond ((looking-at "\\([0-9a-f]\\{40\\}\\) \\([0-9]+\\) \\([0-9]+\\) \\([0-9]+\\)\n")
+         (let ((hash (match-string 1))
+               (src-line (string-to-number (match-string 2)))
+               (res-line (string-to-number (match-string 3)))
+               (num-lines (string-to-number (match-string 4))))
+           (setq git-blame-current
+                 (if (string= hash "0000000000000000000000000000000000000000")
+                     nil
+                   (git-blame-new-commit
+                    hash src-line res-line num-lines))))
+         (delete-region (point) (match-end 0))
+         t)
+        ((looking-at "filename \\(.+\\)\n")
+         (let ((filename (match-string 1)))
+           (git-blame-add-info "filename" filename))
+         (delete-region (point) (match-end 0))
+         t)
+        ((looking-at "\\([a-z-]+\\) \\(.+\\)\n")
+         (let ((key (match-string 1))
+               (value (match-string 2)))
+           (git-blame-add-info key value))
+         (delete-region (point) (match-end 0))
+         t)
+        ((looking-at "boundary\n")
+         (setq git-blame-current nil)
+         (delete-region (point) (match-end 0))
+         t)
+        (t
+         nil)))
+
+
+(defun git-blame-new-commit (hash src-line res-line num-lines)
+  (save-excursion
+    (set-buffer git-blame-file)
+    (let ((info (gethash hash git-blame-cache))
+          (inhibit-point-motion-hooks t)
+          (inhibit-modification-hooks t))
+      (when (not info)
+        (let ((color (pop git-blame-colors)))
+          (unless color
+            (setq color git-blame-ancient-color))
+          (setq info (list hash src-line res-line num-lines
+                           (git-describe-commit hash)
+                           (cons 'color color))))
+        (puthash hash info git-blame-cache))
+      (goto-line res-line)
+      (while (> num-lines 0)
+        (if (get-text-property (point) 'git-blame)
+            (forward-line)
+          (let* ((start (point))
+                 (end (progn (forward-line 1) (point)))
+                 (ovl (make-overlay start end)))
+            (push ovl git-blame-overlays)
+            (overlay-put ovl 'git-blame info)
+            (overlay-put ovl 'help-echo hash)
+            (overlay-put ovl 'face (list :background
+                                         (cdr (assq 'color (nthcdr 5 info)))))
+            ;; the point-entered property doesn't seem to work in overlays
+            ;;(overlay-put ovl 'point-entered
+            ;;             `(lambda (x y) (git-blame-identify ,hash)))
+            (let ((modified (buffer-modified-p)))
+              (put-text-property (if (= start 1) start (1- start)) (1- end)
+                                 'point-entered
+                                 `(lambda (x y) (git-blame-identify ,hash)))
+              (set-buffer-modified-p modified))))
+        (setq num-lines (1- num-lines))))))
+
+(defun git-blame-add-info (key value)
+  (if git-blame-current
+      (nconc git-blame-current (list (cons (intern key) value)))))
+
+(defun git-blame-current-commit ()
+  (let ((info (get-char-property (point) 'git-blame)))
+    (if info
+        (car info)
+      (error "No commit info"))))
+
+(defun git-describe-commit (hash)
+  (with-temp-buffer
+    (call-process "git" nil t nil
+                  "log" "-1" "--pretty=oneline"
+                  hash)
+    (buffer-substring (point-min) (1- (point-max)))))
+
+(defvar git-blame-last-identification nil)
+(make-variable-buffer-local 'git-blame-last-identification)
+(defun git-blame-identify (&optional hash)
+  (interactive)
+  (let ((info (gethash (or hash (git-blame-current-commit)) git-blame-cache)))
+    (when (and info (not (eq info git-blame-last-identification)))
+      (message "%s" (nth 4 info))
+      (setq git-blame-last-identification info))))
+
+;; (defun git-blame-after-save ()
+;;   (when git-blame-mode
+;;     (git-blame-cleanup)
+;;     (git-blame-run)))
+;; (add-hook 'after-save-hook 'git-blame-after-save)
+
+(defun git-blame-after-change (start end length)
+  (when git-blame-mode
+    (git-blame-enq-update start end)))
+
+(defvar git-blame-last-update nil)
+(make-variable-buffer-local 'git-blame-last-update)
+(defun git-blame-enq-update (start end)
+  "Mark the region between START and END as needing blame update"
+  ;; Try to be smart and avoid multiple callouts for sequential
+  ;; editing
+  (cond ((and git-blame-last-update
+              (= start (cdr git-blame-last-update)))
+         (setcdr git-blame-last-update end))
+        ((and git-blame-last-update
+              (= end (car git-blame-last-update)))
+         (setcar git-blame-last-update start))
+        (t
+         (setq git-blame-last-update (cons start end))
+         (setq git-blame-update-queue (nconc git-blame-update-queue
+                                             (list git-blame-last-update)))))
+  (unless (or git-blame-proc git-blame-idle-timer)
+    (setq git-blame-idle-timer
+          (run-with-idle-timer 0.5 nil 'git-blame-delayed-update))))
+
+(defun git-blame-delayed-update ()
+  (setq git-blame-idle-timer nil)
+  (if git-blame-update-queue
+      (let ((first (pop git-blame-update-queue))
+            (inhibit-point-motion-hooks t))
+        (git-blame-update-region (car first) (cdr first)))))
+
+(provide 'git-blame)
+
+;;; git-blame.el ends here
index 972c402ea0e79405ef7123dcdf748eccf9e6f3f7..24629eb3e211505728c77db820618021203241ae 100644 (file)
@@ -49,6 +49,7 @@
 
 (eval-when-compile (require 'cl))
 (require 'ewoc)
+(require 'log-edit)
 
 
 ;;;; Customizations
@@ -147,6 +148,13 @@ if there is already one that displays the same directory."
 
 (defconst git-log-msg-separator "--- log message follows this line ---")
 
+(defvar git-log-edit-font-lock-keywords
+  `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)$"
+     (1 font-lock-keyword-face)
+     (2 font-lock-function-name-face))
+    (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
+     (1 font-lock-comment-face))))
+
 (defun git-get-env-strings (env)
   "Build a list of NAME=VALUE strings from a list of environment strings."
   (mapcar (lambda (entry) (concat (car entry) "=" (cdr entry))) env))
@@ -214,7 +222,7 @@ and returns the process output as a string."
   "Return the name to use as GIT_COMMITTER_NAME."
   ; copied from log-edit
   (or git-committer-name
-      (git-repo-config "user.name")
+      (git-config "user.name")
       (and (boundp 'add-log-full-name) add-log-full-name)
       (and (fboundp 'user-full-name) (user-full-name))
       (and (boundp 'user-full-name) user-full-name)))
@@ -223,7 +231,7 @@ and returns the process output as a string."
   "Return the email address to use as GIT_COMMITTER_EMAIL."
   ; copied from log-edit
   (or git-committer-email
-      (git-repo-config "user.email")
+      (git-config "user.email")
       (and (boundp 'add-log-mailing-address) add-log-mailing-address)
       (and (fboundp 'user-mail-address) (user-mail-address))
       (and (boundp 'user-mail-address) user-mail-address)))
@@ -272,6 +280,15 @@ and returns the process output as a string."
     (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))))
 
+; propertize definition for XEmacs, stolen from erc-compat
+(eval-when-compile
+  (unless (fboundp 'propertize)
+    (defun propertize (string &rest props)
+      (let ((string (copy-sequence string)))
+        (while props
+          (put-text-property 0 (length string) (nth 0 props) (nth 1 props) string)
+          (setq props (cddr props)))
+        string))))
 
 ;;;; Wrappers for basic git commands
 ;;;; ------------------------------------------------------------
@@ -281,9 +298,9 @@ and returns the process output as a string."
   (git-get-string-sha1
    (git-call-process-env-string nil "rev-parse" rev)))
 
-(defun git-repo-config (key)
+(defun git-config (key)
   "Retrieve the value associated to KEY in the git repository config file."
-  (let ((str (git-call-process-env-string nil "repo-config" key)))
+  (let ((str (git-call-process-env-string nil "config" key)))
     (and str (car (split-string str "\n")))))
 
 (defun git-symbolic-ref (ref)
@@ -440,11 +457,10 @@ and returns the process output as a string."
 
 (defun git-fileinfo-prettyprint (info)
   "Pretty-printer for the git-fileinfo structure."
-  (insert (format "   %s %s %s  %s%s"
-                  (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
-                  (git-status-code-as-string (git-fileinfo->state info))
-                  (git-permissions-as-string (git-fileinfo->old-perm info) (git-fileinfo->new-perm info))
-                  (git-escape-file-name (git-fileinfo->name info))
+  (insert (concat "   " (if (git-fileinfo->marked info) (propertize "*" 'face 'git-mark-face) " ")
+                  " " (git-status-code-as-string (git-fileinfo->state info))
+                  " " (git-permissions-as-string (git-fileinfo->old-perm info) (git-fileinfo->new-perm info))
+                  "  " (git-escape-file-name (git-fileinfo->name info))
                   (git-rename-as-string info))))
 
 (defun git-parse-status (status)
@@ -777,7 +793,7 @@ and returns the process output as a string."
   (interactive)
   (let ((files (git-marked-files-state 'unmerged)))
     (when files
-      (apply #'git-run-command nil nil "update-index" "--info-only" "--" (git-get-filenames files))
+      (apply #'git-run-command nil nil "update-index" "--" (git-get-filenames files))
       (git-set-files-state files 'modified)
       (git-refresh-files))))
 
@@ -894,14 +910,9 @@ and returns the process output as a string."
               (sign-off
                (insert (format "\n\nSigned-off-by: %s <%s>\n"
                                (git-get-committer-name) (git-get-committer-email)))))))
-    (let ((log-edit-font-lock-keywords
-           `(("^\\(Author:\\|Date:\\|Parent:\\|Signed-off-by:\\)\\(.*\\)"
-              (1 font-lock-keyword-face)
-              (2 font-lock-function-name-face))
-             (,(concat "^\\(" (regexp-quote git-log-msg-separator) "\\)$")
-              (1 font-lock-comment-face)))))
-      (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
-      (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
+    (log-edit #'git-do-commit nil #'git-log-edit-files buffer)
+    (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
+    (re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t)))
 
 (defun git-find-file ()
   "Visit the current file in its own buffer."
index 8b6361922fd6e6a2fcd9acb20fd54f5b645b36f0..e456ab9712e0574c712e5f79b0dc55cac167d9b1 100644 (file)
@@ -58,8 +58,9 @@
   (with-temp-buffer
     (let* ((dir (file-name-directory file))
            (name (file-relative-name file dir)))
-      (when dir (cd dir))
-      (and (ignore-errors (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
+      (and (ignore-errors
+             (when dir (cd dir))
+             (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)))
            (let ((str (buffer-string)))
              (and (> (length str) (length name))
                   (string= (substring str 0 (1+ (length name))) (concat name "\0"))))))))
     (vc-git--run-command file "commit" "-m" comment "--only" "--")))
 
 (defun vc-git-checkout (file &optional editable rev destfile)
-  (vc-git--run-command file "checkout" (or rev "HEAD")))
+  (if destfile
+      (let ((fullname (substring
+                       (vc-git--run-command-string file "ls-files" "-z" "--full-name" "--")
+                       0 -1))
+            (coding-system-for-read 'no-conversion)
+            (coding-system-for-write 'no-conversion))
+        (with-temp-file destfile
+          (eq 0 (call-process "git" nil t nil "cat-file" "blob"
+                              (concat (or rev "HEAD") ":" fullname)))))
+    (vc-git--run-command file "checkout" (or rev "HEAD"))))
 
 (defun vc-git-annotate-command (file buf &optional rev)
   ; FIXME: rev is ignored
diff --git a/contrib/fast-import/import-tars.perl b/contrib/fast-import/import-tars.perl
new file mode 100755 (executable)
index 0000000..990c9e7
--- /dev/null
@@ -0,0 +1,103 @@
+#!/usr/bin/perl
+
+## tar archive frontend for git-fast-import
+##
+## For example:
+##
+##  mkdir project; cd project; git init
+##  perl import-tars.perl *.tar.bz2
+##  git whatchanged import-tars
+##
+
+use strict;
+die "usage: import-tars *.tar.{gz,bz2,Z}\n" unless @ARGV;
+
+my $branch_name = 'import-tars';
+my $branch_ref = "refs/heads/$branch_name";
+my $committer_name = 'T Ar Creator';
+my $committer_email = 'tar@example.com';
+
+open(FI, '|-', 'git', 'fast-import', '--quiet')
+       or die "Unable to start git fast-import: $!\n";
+foreach my $tar_file (@ARGV)
+{
+       $tar_file =~ m,([^/]+)$,;
+       my $tar_name = $1;
+
+       if ($tar_name =~ s/\.(tar\.gz|tgz)$//) {
+               open(I, '-|', 'gzcat', $tar_file) or die "Unable to gzcat $tar_file: $!\n";
+       } elsif ($tar_name =~ s/\.(tar\.bz2|tbz2)$//) {
+               open(I, '-|', 'bzcat', $tar_file) or die "Unable to bzcat $tar_file: $!\n";
+       } elsif ($tar_name =~ s/\.tar\.Z$//) {
+               open(I, '-|', 'zcat', $tar_file) or die "Unable to zcat $tar_file: $!\n";
+       } elsif ($tar_name =~ s/\.tar$//) {
+               open(I, $tar_file) or die "Unable to open $tar_file: $!\n";
+       } else {
+               die "Unrecognized compression format: $tar_file\n";
+       }
+
+       my $commit_time = 0;
+       my $next_mark = 1;
+       my $have_top_dir = 1;
+       my ($top_dir, %files);
+
+       while (read(I, $_, 512) == 512) {
+               my ($name, $mode, $uid, $gid, $size, $mtime,
+                       $chksum, $typeflag, $linkname, $magic,
+                       $version, $uname, $gname, $devmajor, $devminor,
+                       $prefix) = unpack 'Z100 Z8 Z8 Z8 Z12 Z12
+                       Z8 Z1 Z100 Z6
+                       Z2 Z32 Z32 Z8 Z8 Z*', $_;
+               last unless $name;
+               $mode = oct $mode;
+               $size = oct $size;
+               $mtime = oct $mtime;
+               next if $mode & 0040000;
+
+               print FI "blob\n", "mark :$next_mark\n", "data $size\n";
+               while ($size > 0 && read(I, $_, 512) == 512) {
+                       print FI substr($_, 0, $size);
+                       $size -= 512;
+               }
+               print FI "\n";
+
+               my $path = "$prefix$name";
+               $files{$path} = [$next_mark++, $mode];
+
+               $commit_time = $mtime if $mtime > $commit_time;
+               $path =~ m,^([^/]+)/,;
+               $top_dir = $1 unless $top_dir;
+               $have_top_dir = 0 if $top_dir ne $1;
+       }
+
+       print FI <<EOF;
+commit $branch_ref
+committer $committer_name <$committer_email> $commit_time +0000
+data <<END_OF_COMMIT_MESSAGE
+Imported from $tar_file.
+END_OF_COMMIT_MESSAGE
+
+deleteall
+EOF
+
+       foreach my $path (keys %files)
+       {
+               my ($mark, $mode) = @{$files{$path}};
+               $path =~ s,^([^/]+)/,, if $have_top_dir;
+               printf FI "M %o :%i %s\n", $mode & 0111 ? 0755 : 0644, $mark, $path;
+       }
+       print FI "\n";
+
+       print FI <<EOF;
+tag $tar_name
+from $branch_ref
+tagger $committer_name <$committer_email> $commit_time +0000
+data <<END_OF_TAG_MESSAGE
+Package $tar_name
+END_OF_TAG_MESSAGE
+
+EOF
+
+       close I;
+}
+close FI;
index 3b6bdceeeb20daf3242cf98d12f8f4d94cffc6e8..521b2fcd32da4103d0c916af0ae44fbe53ed282a 100755 (executable)
@@ -497,7 +497,7 @@ class GitView:
                fp.close()
 
        def get_encoding(self):
-               fp = os.popen("git repo-config --get i18n.commitencoding")
+               fp = os.popen("git config --get i18n.commitencoding")
                self.encoding=string.strip(fp.readline())
                fp.close()
                if (self.encoding == ""):
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
new file mode 100755 (executable)
index 0000000..37337ff
--- /dev/null
@@ -0,0 +1,233 @@
+#! /usr/bin/python
+
+""" hg-to-svn.py - A Mercurial to GIT converter
+
+    Copyright (C)2007 Stelian Pop <stelian@popies.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2, or (at your option)
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os, os.path, sys
+import tempfile, popen2, pickle, getopt
+import re
+
+# Maps hg version -> git version
+hgvers = {}
+# List of children for each hg revision
+hgchildren = {}
+# Current branch for each hg revision
+hgbranch = {}
+
+#------------------------------------------------------------------------------
+
+def usage():
+
+        print """\
+%s: [OPTIONS] <hgprj>
+
+options:
+    -s, --gitstate=FILE: name of the state to be saved/read
+                         for incrementals
+
+required:
+    hgprj:  name of the HG project to import (directory)
+""" % sys.argv[0]
+
+#------------------------------------------------------------------------------
+
+def getgitenv(user, date):
+    env = ''
+    elems = re.compile('(.*?)\s+<(.*)>').match(user)
+    if elems:
+        env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
+        env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
+        env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
+        env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
+    else:
+        env += 'export GIT_AUTHOR_NAME="%s" ;' % user
+        env += 'export GIT_COMMITER_NAME="%s" ;' % user
+        env += 'export GIT_AUTHOR_EMAIL= ;'
+        env += 'export GIT_COMMITER_EMAIL= ;'
+
+    env += 'export GIT_AUTHOR_DATE="%s" ;' % date
+    env += 'export GIT_COMMITTER_DATE="%s" ;' % date
+    return env
+
+#------------------------------------------------------------------------------
+
+state = ''
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], 's:t:', ['gitstate=', 'tempdir='])
+    for o, a in opts:
+        if o in ('-s', '--gitstate'):
+            state = a
+            state = os.path.abspath(state)
+
+    if len(args) != 1:
+        raise('params')
+except:
+    usage()
+    sys.exit(1)
+
+hgprj = args[0]
+os.chdir(hgprj)
+
+if state:
+    if os.path.exists(state):
+        print 'State does exist, reading'
+        f = open(state, 'r')
+        hgvers = pickle.load(f)
+    else:
+        print 'State does not exist, first run'
+
+tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip()
+print 'tip is', tip
+
+# Calculate the branches
+print 'analysing the branches...'
+hgchildren["0"] = ()
+hgbranch["0"] = "master"
+for cset in range(1, int(tip) + 1):
+    hgchildren[str(cset)] = ()
+    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
+    if len(prnts) > 0:
+        parent = prnts[0].strip()
+    else:
+        parent = str(cset - 1)
+    hgchildren[parent] += ( str(cset), )
+    if len(prnts) > 1:
+        mparent = prnts[1].strip()
+        hgchildren[mparent] += ( str(cset), )
+    else:
+        mparent = None
+
+    if mparent:
+        # For merge changesets, take either one, preferably the 'master' branch
+        if hgbranch[mparent] == 'master':
+            hgbranch[str(cset)] = 'master'
+        else:
+            hgbranch[str(cset)] = hgbranch[parent]
+    else:
+        # Normal changesets
+        # For first children, take the parent branch, for the others create a new branch
+        if hgchildren[parent][0] == str(cset):
+            hgbranch[str(cset)] = hgbranch[parent]
+        else:
+            hgbranch[str(cset)] = "branch-" + str(cset)
+
+if not hgvers.has_key("0"):
+    print 'creating repository'
+    os.system('git-init-db')
+
+# loop through every hg changeset
+for cset in range(int(tip) + 1):
+
+    # incremental, already seen
+    if hgvers.has_key(str(cset)):
+        continue
+
+    # get info
+    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
+    if len(prnts) > 0:
+        parent = prnts[0].strip()
+    else:
+        parent = str(cset - 1)
+    if len(prnts) > 1:
+        mparent = prnts[1].strip()
+    else:
+        mparent = None
+
+    (fdcomment, filecomment) = tempfile.mkstemp()
+    csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v ^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v ^description: | grep -v ^tag:' % cset).read().strip()
+    os.write(fdcomment, csetcomment)
+    os.close(fdcomment)
+
+    date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip()
+
+    tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip()
+
+    user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip()
+
+    print '-----------------------------------------'
+    print 'cset:', cset
+    print 'branch:', hgbranch[str(cset)]
+    print 'user:', user
+    print 'date:', date
+    print 'comment:', csetcomment
+    print 'parent:', parent
+    if mparent:
+        print 'mparent:', mparent
+    if tag:
+        print 'tag:', tag
+    print '-----------------------------------------'
+
+    # checkout the parent if necessary
+    if cset != 0:
+        if hgbranch[str(cset)] == "branch-" + str(cset):
+            print 'creating new branch', hgbranch[str(cset)]
+            os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
+        else:
+            print 'checking out branch', hgbranch[str(cset)]
+            os.system('git-checkout %s' % hgbranch[str(cset)])
+
+    # merge
+    if mparent:
+        if hgbranch[parent] == hgbranch[str(cset)]:
+            otherbranch = hgbranch[mparent]
+        else:
+            otherbranch = hgbranch[parent]
+        print 'merging', otherbranch, 'into', hgbranch[str(cset)]
+        os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
+
+    # remove everything except .git and .hg directories
+    os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
+
+    # repopulate with checkouted files
+    os.system('hg update -C %d' % cset)
+
+    # add new files
+    os.system('git-ls-files -x .hg --others | git-update-index --add --stdin')
+    # delete removed files
+    os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin')
+
+    # commit
+    os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment)
+    os.unlink(filecomment)
+
+    # tag
+    if tag and tag != 'tip':
+        os.system(getgitenv(user, date) + 'git-tag %s' % tag)
+
+    # delete branch if not used anymore...
+    if mparent and len(hgchildren[str(cset)]):
+        print "Deleting unused branch:", otherbranch
+        os.system('git-branch -d %s' % otherbranch)
+
+    # retrieve and record the version
+    vvv = os.popen('git-show | head -1').read()
+    vvv = vvv[vvv.index(' ') + 1 : ].strip()
+    print 'record', cset, '->', vvv
+    hgvers[str(cset)] = vvv
+
+os.system('git-repack -a -d')
+
+# write the state for incrementals
+if state:
+    print 'Writing state'
+    f = open(state, 'w')
+    pickle.dump(hgvers, f)
+
+# vim: et ts=8 sw=4 sts=4
diff --git a/contrib/hg-to-git/hg-to-git.txt b/contrib/hg-to-git/hg-to-git.txt
new file mode 100644 (file)
index 0000000..91f8fe6
--- /dev/null
@@ -0,0 +1,21 @@
+hg-to-git.py is able to convert a Mercurial repository into a git one,
+and preserves the branches in the process (unlike tailor)
+
+hg-to-git.py can probably be greatly improved (it's a rather crude
+combination of shell and python) but it does already work quite well for
+me. Features:
+       - supports incremental conversion
+         (for keeping a git repo in sync with a hg one)
+        - supports hg branches
+        - converts hg tags
+
+Note that the git repository will be created 'in place' (at the same
+location as the source hg repo). You will have to manually remove the
+'.hg' directory after the conversion.
+
+Also note that the incremental conversion uses 'simple' hg changesets
+identifiers (ordinals, as opposed to SHA-1 ids), and since these ids
+are not stable across different repositories the hg-to-git.py state file
+is forever tied to one hg repository.
+
+Stelian Pop <stelian@popies.net>
index 25901e2b3bc87279d4b2911adc0a5e8d97ce6e38..dc09eae972711496aa390cb99b3017a936ce1afc 100644 (file)
@@ -11,7 +11,7 @@ if [ -d "$GIT_DIR"/remotes ]; then
        {
                cd "$GIT_DIR"/remotes
                ls | while read f; do
-                       name=$(echo -n "$f" | tr -c "A-Za-z0-9" ".")
+                       name=$(printf "$f" | tr -c "A-Za-z0-9" ".")
                        sed -n \
                        -e "s/^URL: \(.*\)$/remote.$name.url \1 ./p" \
                        -e "s/^Pull: \(.*\)$/remote.$name.fetch \1 ^$ /p" \
@@ -26,8 +26,8 @@ if [ -d "$GIT_DIR"/remotes ]; then
                                mv "$GIT_DIR"/remotes "$GIT_DIR"/remotes.old
                        fi ;;
                *)
-                       echo "git-repo-config $key "$value" $regex"
-                       git-repo-config $key "$value" $regex || error=1 ;;
+                       echo "git-config $key "$value" $regex"
+                       git-config $key "$value" $regex || error=1 ;;
                esac
        done
 fi
index a9de09fa2fec161d6a0170553f2b507bca483cef..332121b40e9c54de9f55362cb57760136039e9fd 100644 (file)
@@ -1,5 +1,5 @@
 syn region gitLine start=/^#/ end=/$/
-syn region gitCommit start=/^# Updated but not checked in:$/ end=/^#$/ contains=gitHead,gitCommitFile
+syn region gitCommit start=/^# Changes to be committed:$/ end=/^#$/ contains=gitHead,gitCommitFile
 syn region gitHead contained start=/^#   (.*)/ end=/^#$/
 syn region gitChanged start=/^# Changed but not updated:/ end=/^#$/ contains=gitHead,gitChangedFile
 syn region gitUntracked start=/^# Untracked files:/ end=/^#$/ contains=gitHead,gitUntrackedFile
index 881258311a47f9faa437ec02e99b4a0df1439d53..a63013298566fd3dc21275a90ca976227de8c7f0 100644 (file)
@@ -1,7 +1,3 @@
-#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
-#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
-#define _GNU_SOURCE
-#include <time.h>
 #include "cache.h"
 #include "blob.h"
 #include "commit.h"
index e66bb802da9faed6e01229fffbe56e3d7cca60c6..2a20ca55cbe912a8ddd4bea8b2476ae30215903a 100644 (file)
--- a/daemon.c
+++ b/daemon.c
@@ -1,20 +1,10 @@
-#include <signal.h>
-#include <sys/wait.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/poll.h>
-#include <netdb.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <syslog.h>
-#include <pwd.h>
-#include <grp.h>
-#include <limits.h>
-#include "pkt-line.h"
 #include "cache.h"
+#include "pkt-line.h"
 #include "exec_cmd.h"
 #include "interpolate.h"
 
+#include <syslog.h>
+
 #ifndef HOST_NAME_MAX
 #define HOST_NAME_MAX 256
 #endif
@@ -112,7 +102,7 @@ static void logreport(int priority, const char *err, va_list params)
        buf[buflen++] = '\n';
        buf[buflen] = '\0';
 
-       write(2, buf, buflen);
+       write_in_full(2, buf, buflen);
 }
 
 static void logerror(const char *err, ...)
@@ -382,9 +372,16 @@ static int upload_archive(void)
        return -1;
 }
 
+static int receive_pack(void)
+{
+       execl_git_cmd("receive-pack", ".", NULL);
+       return -1;
+}
+
 static struct daemon_service daemon_service[] = {
        { "upload-archive", "uploadarch", upload_archive, 0, 1 },
        { "upload-pack", "uploadpack", upload_pack, 1, 1 },
+       { "receive-pack", "receivepack", receive_pack, 0, 1 },
 };
 
 static void enable_service(const char *name, int ena) {
@@ -411,7 +408,7 @@ static void make_service_overridable(const char *name, int ena) {
 
 /*
  * Separate the "extra args" information as supplied by the client connection.
- * Any resulting data is squirrelled away in the given interpolation table.
+ * Any resulting data is squirreled away in the given interpolation table.
  */
 static void parse_extra_args(struct interp *table, char *extra_args, int buflen)
 {
diff --git a/date.c b/date.c
index 18259227321f47988386c2bd70888b3c967d8ab6..542c004c2e8d2a9f731a1f303cc3393d88f5a6e8 100644 (file)
--- a/date.c
+++ b/date.c
@@ -4,9 +4,6 @@
  * Copyright (C) Linus Torvalds, 2005
  */
 
-#include <time.h>
-#include <sys/time.h>
-
 #include "cache.h"
 
 static time_t my_mktime(struct tm *tm)
@@ -65,12 +62,11 @@ const char *show_date(unsigned long time, int tz, int relative)
 
        if (relative) {
                unsigned long diff;
-               time_t t = gm_time_t(time, tz);
                struct timeval now;
                gettimeofday(&now, NULL);
-               if (now.tv_sec < t)
+               if (now.tv_sec < time)
                        return "in the future";
-               diff = now.tv_sec - t;
+               diff = now.tv_sec - time;
                if (diff < 90) {
                        snprintf(timebuf, sizeof(timebuf), "%lu seconds ago", diff);
                        return timebuf;
diff --git a/describe.c b/describe.c
deleted file mode 100644 (file)
index f4029ee..0000000
+++ /dev/null
@@ -1,175 +0,0 @@
-#include "cache.h"
-#include "commit.h"
-#include "tag.h"
-#include "refs.h"
-
-#define SEEN (1u << 0)
-
-static const char describe_usage[] =
-"git-describe [--all] [--tags] [--abbrev=<n>] <committish>*";
-
-static int all;        /* Default to annotated tags only */
-static int tags;       /* But allow any tags if --tags is specified */
-
-static int abbrev = DEFAULT_ABBREV;
-
-static int names, allocs;
-static struct commit_name {
-       const struct commit *commit;
-       int prio; /* annotated tag = 2, tag = 1, head = 0 */
-       char path[FLEX_ARRAY]; /* more */
-} **name_array = NULL;
-
-static struct commit_name *match(struct commit *cmit)
-{
-       int i = names;
-       struct commit_name **p = name_array;
-
-       while (i-- > 0) {
-               struct commit_name *n = *p++;
-               if (n->commit == cmit)
-                       return n;
-       }
-       return NULL;
-}
-
-static void add_to_known_names(const char *path,
-                              const struct commit *commit,
-                              int prio)
-{
-       int idx;
-       int len = strlen(path)+1;
-       struct commit_name *name = xmalloc(sizeof(struct commit_name) + len);
-
-       name->commit = commit;
-       name->prio = prio;
-       memcpy(name->path, path, len);
-       idx = names;
-       if (idx >= allocs) {
-               allocs = (idx + 50) * 3 / 2;
-               name_array = xrealloc(name_array, allocs*sizeof(*name_array));
-       }
-       name_array[idx] = name;
-       names = ++idx;
-}
-
-static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
-{
-       struct commit *commit = lookup_commit_reference_gently(sha1, 1);
-       struct object *object;
-       int prio;
-
-       if (!commit)
-               return 0;
-       object = parse_object(sha1);
-       /* If --all, then any refs are used.
-        * If --tags, then any tags are used.
-        * Otherwise only annotated tags are used.
-        */
-       if (!strncmp(path, "refs/tags/", 10)) {
-               if (object->type == OBJ_TAG)
-                       prio = 2;
-               else
-                       prio = 1;
-       }
-       else
-               prio = 0;
-
-       if (!all) {
-               if (!prio)
-                       return 0;
-               if (!tags && prio < 2)
-                       return 0;
-       }
-       add_to_known_names(all ? path + 5 : path + 10, commit, prio);
-       return 0;
-}
-
-static int compare_names(const void *_a, const void *_b)
-{
-       struct commit_name *a = *(struct commit_name **)_a;
-       struct commit_name *b = *(struct commit_name **)_b;
-       unsigned long a_date = a->commit->date;
-       unsigned long b_date = b->commit->date;
-
-       if (a->prio != b->prio)
-               return b->prio - a->prio;
-       return (a_date > b_date) ? -1 : (a_date == b_date) ? 0 : 1;
-}
-
-static void describe(const char *arg, int last_one)
-{
-       unsigned char sha1[20];
-       struct commit *cmit;
-       struct commit_list *list;
-       static int initialized = 0;
-       struct commit_name *n;
-
-       if (get_sha1(arg, sha1))
-               die("Not a valid object name %s", arg);
-       cmit = lookup_commit_reference(sha1);
-       if (!cmit)
-               die("%s is not a valid '%s' object", arg, commit_type);
-
-       if (!initialized) {
-               initialized = 1;
-               for_each_ref(get_name, NULL);
-               qsort(name_array, names, sizeof(*name_array), compare_names);
-       }
-
-       n = match(cmit);
-       if (n) {
-               printf("%s\n", n->path);
-               return;
-       }
-
-       list = NULL;
-       commit_list_insert(cmit, &list);
-       while (list) {
-               struct commit *c = pop_most_recent_commit(&list, SEEN);
-               n = match(c);
-               if (n) {
-                       printf("%s-g%s\n", n->path,
-                              find_unique_abbrev(cmit->object.sha1, abbrev));
-                       if (!last_one)
-                               clear_commit_marks(cmit, SEEN);
-                       return;
-               }
-       }
-       die("cannot describe '%s'", sha1_to_hex(cmit->object.sha1));
-}
-
-int main(int argc, char **argv)
-{
-       int i;
-
-       for (i = 1; i < argc; i++) {
-               const char *arg = argv[i];
-
-               if (*arg != '-')
-                       break;
-               else if (!strcmp(arg, "--all"))
-                       all = 1;
-               else if (!strcmp(arg, "--tags"))
-                       tags = 1;
-               else if (!strncmp(arg, "--abbrev=", 9)) {
-                       abbrev = strtoul(arg + 9, NULL, 10);
-                       if (abbrev < MINIMUM_ABBREV || 40 < abbrev)
-                               abbrev = DEFAULT_ABBREV;
-               }
-               else
-                       usage(describe_usage);
-       }
-
-       setup_git_directory();
-
-       if (argc <= i)
-               describe("HEAD", 1);
-       else
-               while (i < argc) {
-                       describe(argv[i], (i == argc - 1));
-                       i++;
-               }
-
-       return 0;
-}
index fa16d06c8d1e85a458428c673cb2f589857f5424..9f998d0a73e0127d3a68a7caecb3727569149871 100644 (file)
  *  licensing gets turned into GPLv2 within this project.
  */
 
-#include <stdlib.h>
-#include <string.h>
-#include "delta.h"
-
 #include "git-compat-util.h"
+#include "delta.h"
 
 /* maximum hash entry list for the same hash bucket */
 #define HASH_LIMIT 64
index fc69fb92a50c3dff67da76fedf1bf1df561c6065..91cd87742f9efd2c0fcf878c179bc6ed989cb50a 100644 (file)
@@ -7,6 +7,7 @@
 #include "diff.h"
 #include "diffcore.h"
 #include "revision.h"
+#include "cache-tree.h"
 
 /*
  * diff-files
@@ -97,7 +98,7 @@ int run_diff_files(struct rev_info *revs, int silent_on_removed)
                         * Show the diff for the 'ce' if we found the one
                         * from the desired stage.
                         */
-                       diff_unmerge(&revs->diffopt, ce->name);
+                       diff_unmerge(&revs->diffopt, ce->name, 0, null_sha1);
                        if (ce_stage(ce) != diff_unmerged_stage)
                                continue;
                }
@@ -271,7 +272,7 @@ static int diff_cache(struct rev_info *revs,
                                break;
                        }
                        /* Show difference between old and new */
-                       show_modified(revs,ac[1], ce, 1,
+                       show_modified(revs, ac[1], ce, 1,
                                      cached, match_missing);
                        break;
                case 1:
@@ -297,9 +298,12 @@ static int diff_cache(struct rev_info *revs,
                            !show_modified(revs, ce, ac[1], 0,
                                           cached, match_missing))
                                break;
-                       /* fallthru */
+                       diff_unmerge(&revs->diffopt, ce->name,
+                                    ntohl(ce->ce_mode), ce->sha1);
+                       break;
                case 3:
-                       diff_unmerge(&revs->diffopt, ce->name);
+                       diff_unmerge(&revs->diffopt, ce->name,
+                                    0, null_sha1);
                        break;
 
                default:
@@ -369,3 +373,44 @@ int run_diff_index(struct rev_info *revs, int cached)
        diff_flush(&revs->diffopt);
        return ret;
 }
+
+int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
+{
+       struct tree *tree;
+       struct rev_info revs;
+       int i;
+       struct cache_entry **dst;
+       struct cache_entry *last = NULL;
+
+       /*
+        * This is used by git-blame to run diff-cache internally;
+        * it potentially needs to repeatedly run this, so we will
+        * start by removing the higher order entries the last round
+        * left behind.
+        */
+       dst = active_cache;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce_stage(ce)) {
+                       if (last && !strcmp(ce->name, last->name))
+                               continue;
+                       cache_tree_invalidate_path(active_cache_tree,
+                                                  ce->name);
+                       last = ce;
+                       ce->ce_mode = 0;
+                       ce->ce_flags &= ~htons(CE_STAGEMASK);
+               }
+               *dst++ = ce;
+       }
+       active_nr = dst - active_cache;
+
+       init_revisions(&revs, NULL);
+       revs.prune_data = opt->paths;
+       tree = parse_tree_indirect(tree_sha1);
+       if (!tree)
+               die("bad tree object %s", sha1_to_hex(tree_sha1));
+       if (read_tree(tree, 1, opt->paths))
+               return error("unable to read tree %s", sha1_to_hex(tree_sha1));
+       return diff_cache(&revs, active_cache, active_nr, revs.prune_data,
+                         1, 0);
+}
diff --git a/diff.c b/diff.c
index b026c737b682f9249792600032792f3e36230163..165d2520f3aec738e1b8a2b52f1a819e3edb9f33 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1,9 +1,6 @@
 /*
  * Copyright (C) 2005 Junio C Hamano
  */
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
 #include "cache.h"
 #include "quote.h"
 #include "diff.h"
@@ -12,6 +9,12 @@
 #include "xdiff-interface.h"
 #include "color.h"
 
+#ifdef NO_FAST_WORKING_DIRECTORY
+#define FAST_WORKING_DIRECTORY 0
+#else
+#define FAST_WORKING_DIRECTORY 1
+#endif
+
 static int use_size_cache;
 
 static int diff_detect_rename_default;
@@ -60,7 +63,7 @@ int git_diff_ui_config(const char *var, const char *value)
                diff_rename_limit_default = git_config_int(var, value);
                return 0;
        }
-       if (!strcmp(var, "diff.color")) {
+       if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) {
                diff_use_color_default = git_config_colorbool(var, value);
                return 0;
        }
@@ -74,7 +77,7 @@ int git_diff_ui_config(const char *var, const char *value)
                        diff_detect_rename_default = DIFF_DETECT_RENAME;
                return 0;
        }
-       if (!strncmp(var, "diff.color.", 11)) {
+       if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) {
                int slot = parse_diff_color_slot(var, 11);
                color_parse(value, var, diff_colors[slot]);
                return 0;
@@ -556,6 +559,24 @@ static char *pprint_rename(const char *a, const char *b)
        int pfx_length, sfx_length;
        int len_a = strlen(a);
        int len_b = strlen(b);
+       int qlen_a = quote_c_style(a, NULL, NULL, 0);
+       int qlen_b = quote_c_style(b, NULL, NULL, 0);
+
+       if (qlen_a || qlen_b) {
+               if (qlen_a) len_a = qlen_a;
+               if (qlen_b) len_b = qlen_b;
+               name = xmalloc( len_a + len_b + 5 );
+               if (qlen_a)
+                       quote_c_style(a, name, NULL, 0);
+               else
+                       memcpy(name, a, len_a);
+               memcpy(name + len_a, " => ", 4);
+               if (qlen_b)
+                       quote_c_style(b, name + len_a + 4, NULL, 0);
+               else
+                       memcpy(name + len_a + 4, b, len_b + 1);
+               return name;
+       }
 
        /* Find common prefix */
        pfx_length = 0;
@@ -712,12 +733,14 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
                struct diffstat_file *file = data->files[i];
                int change = file->added + file->deleted;
 
-               len = quote_c_style(file->name, NULL, NULL, 0);
-               if (len) {
-                       char *qname = xmalloc(len + 1);
-                       quote_c_style(file->name, qname, NULL, 0);
-                       free(file->name);
-                       file->name = qname;
+               if (!file->is_renamed) {  /* renames are already quoted by pprint_rename */
+                       len = quote_c_style(file->name, NULL, NULL, 0);
+                       if (len) {
+                               char *qname = xmalloc(len + 1);
+                               quote_c_style(file->name, qname, NULL, 0);
+                               free(file->name);
+                               file->name = qname;
+                       }
                }
 
                len = strlen(file->name);
@@ -809,6 +832,35 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
               set, total_files, adds, dels, reset);
 }
 
+static void show_shortstats(struct diffstat_t* data)
+{
+       int i, adds = 0, dels = 0, total_files = data->nr;
+
+       if (data->nr == 0)
+               return;
+
+       for (i = 0; i < data->nr; i++) {
+               if (!data->files[i]->is_binary &&
+                   !data->files[i]->is_unmerged) {
+                       int added = data->files[i]->added;
+                       int deleted= data->files[i]->deleted;
+                       if (!data->files[i]->is_renamed &&
+                           (added + deleted == 0)) {
+                               total_files--;
+                       } else {
+                               adds += added;
+                               dels += deleted;
+                       }
+               }
+               free(data->files[i]->name);
+               free(data->files[i]);
+       }
+       free(data->files);
+
+       printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
+              total_files, adds, dels);
+}
+
 static void show_numstat(struct diffstat_t* data, struct diff_options *options)
 {
        int i;
@@ -816,8 +868,11 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
        for (i = 0; i < data->nr; i++) {
                struct diffstat_file *file = data->files[i];
 
-               printf("%d\t%d\t", file->added, file->deleted);
-               if (options->line_termination &&
+               if (file->is_binary)
+                       printf("-\t-\t");
+               else
+                       printf("%d\t%d\t", file->added, file->deleted);
+               if (options->line_termination && !file->is_renamed &&
                    quote_c_style(file->name, NULL, NULL, 0))
                        quote_c_style(file->name, NULL, stdout, 0);
                else
@@ -839,8 +894,6 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
        if (line[0] == '+') {
                int i, spaces = 0;
 
-               data->lineno++;
-
                /* check space before tab */
                for (i = 1; i < len && (line[i] == ' ' || line[i] == '\t'); i++)
                        if (line[i] == ' ')
@@ -855,6 +908,8 @@ static void checkdiff_consume(void *priv, char *line, unsigned long len)
                if (isspace(line[len - 1]))
                        printf("%s:%d: white space at end: %.*s\n",
                                data->filename, data->lineno, (int)len, line);
+
+               data->lineno++;
        } else if (line[0] == ' ')
                data->lineno++;
        else if (line[0] == '@') {
@@ -1169,7 +1224,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1,
  * the work tree has that object contents, return true, so that
  * prepare_temp_file() does not have to inflate and extract.
  */
-static int work_tree_matches(const char *name, const unsigned char *sha1)
+static int reuse_worktree_file(const char *name, const unsigned char *sha1, int want_file)
 {
        struct cache_entry *ce;
        struct stat st;
@@ -1190,6 +1245,18 @@ static int work_tree_matches(const char *name, const unsigned char *sha1)
        if (!active_cache)
                return 0;
 
+       /* We want to avoid the working directory if our caller
+        * doesn't need the data in a normal file, this system
+        * is rather slow with its stat/open/mmap/close syscalls,
+        * and the object is contained in a pack file.  The pack
+        * is probably already open and will be faster to obtain
+        * the data through than the working directory.  Loose
+        * objects however would tend to be slower as they need
+        * to be individually opened and inflated.
+        */
+       if (!FAST_WORKING_DIRECTORY && !want_file && has_sha1_pack(sha1, NULL))
+               return 0;
+
        len = strlen(name);
        pos = cache_name_pos(name, len);
        if (pos < 0)
@@ -1276,7 +1343,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        if (s->data)
                return err;
        if (!s->sha1_valid ||
-           work_tree_matches(s->path, s->sha1)) {
+           reuse_worktree_file(s->path, s->sha1, 0)) {
                struct stat st;
                int fd;
                if (lstat(s->path, &st) < 0) {
@@ -1308,11 +1375,10 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
                fd = open(s->path, O_RDONLY);
                if (fd < 0)
                        goto err_empty;
-               s->data = mmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
+               s->data = xmmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);
                close(fd);
-               if (s->data == MAP_FAILED)
-                       goto err_empty;
                s->should_munmap = 1;
+               /* FIXME! CRLF -> LF conversion goes here, based on "s->path" */
        }
        else {
                char type[20];
@@ -1358,7 +1424,7 @@ static void prep_temp_blob(struct diff_tempfile *temp,
        fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
        if (fd < 0)
                die("unable to create temp-file");
-       if (write(fd, blob, size) != size)
+       if (write_in_full(fd, blob, size) != size)
                die("unable to write temp-file");
        close(fd);
        temp->name = temp->tmp_path;
@@ -1383,7 +1449,7 @@ static void prepare_temp_file(const char *name,
        }
 
        if (!one->sha1_valid ||
-           work_tree_matches(name, one->sha1)) {
+           reuse_worktree_file(name, one->sha1, 1)) {
                struct stat st;
                if (lstat(name, &st) < 0) {
                        if (errno == ENOENT)
@@ -1764,6 +1830,7 @@ int diff_setup_done(struct diff_options *options)
                options->output_format &= ~(DIFF_FORMAT_RAW |
                                            DIFF_FORMAT_NUMSTAT |
                                            DIFF_FORMAT_DIFFSTAT |
+                                           DIFF_FORMAT_SHORTSTAT |
                                            DIFF_FORMAT_SUMMARY |
                                            DIFF_FORMAT_PATCH);
 
@@ -1774,6 +1841,7 @@ int diff_setup_done(struct diff_options *options)
        if (options->output_format & (DIFF_FORMAT_PATCH |
                                      DIFF_FORMAT_NUMSTAT |
                                      DIFF_FORMAT_DIFFSTAT |
+                                     DIFF_FORMAT_SHORTSTAT |
                                      DIFF_FORMAT_SUMMARY |
                                      DIFF_FORMAT_CHECKDIFF))
                options->recursive = 1;
@@ -1865,6 +1933,9 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
        else if (!strcmp(arg, "--numstat")) {
                options->output_format |= DIFF_FORMAT_NUMSTAT;
        }
+       else if (!strcmp(arg, "--shortstat")) {
+               options->output_format |= DIFF_FORMAT_SHORTSTAT;
+       }
        else if (!strncmp(arg, "--stat", 6)) {
                char *end;
                int width = options->stat_width;
@@ -2155,13 +2226,13 @@ static void diff_flush_raw(struct diff_filepair *p,
                free((void*)path_two);
 }
 
-static void diff_flush_name(struct diff_filepair *p, int line_termination)
+static void diff_flush_name(struct diff_filepair *p, struct diff_options *opt)
 {
        char *path = p->two->path;
 
-       if (line_termination)
+       if (opt->line_termination)
                path = quote_one(p->two->path);
-       printf("%s%c", path, line_termination);
+       printf("%s%c", path, opt->line_termination);
        if (p->two->path != path)
                free(path);
 }
@@ -2368,24 +2439,29 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
        else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
                diff_flush_raw(p, opt);
        else if (fmt & DIFF_FORMAT_NAME)
-               diff_flush_name(p, opt->line_termination);
+               diff_flush_name(p, opt);
 }
 
 static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
 {
+       char *name = quote_one(fs->path);
        if (fs->mode)
-               printf(" %s mode %06o %s\n", newdelete, fs->mode, fs->path);
+               printf(" %s mode %06o %s\n", newdelete, fs->mode, name);
        else
-               printf(" %s %s\n", newdelete, fs->path);
+               printf(" %s %s\n", newdelete, name);
+       free(name);
 }
 
 
 static void show_mode_change(struct diff_filepair *p, int show_name)
 {
        if (p->one->mode && p->two->mode && p->one->mode != p->two->mode) {
-               if (show_name)
+               if (show_name) {
+                       char *name = quote_one(p->two->path);
                        printf(" mode change %06o => %06o %s\n",
-                              p->one->mode, p->two->mode, p->two->path);
+                              p->one->mode, p->two->mode, name);
+                       free(name);
+               }
                else
                        printf(" mode change %06o => %06o\n",
                               p->one->mode, p->two->mode);
@@ -2394,34 +2470,11 @@ static void show_mode_change(struct diff_filepair *p, int show_name)
 
 static void show_rename_copy(const char *renamecopy, struct diff_filepair *p)
 {
-       const char *old, *new;
+       char *names = pprint_rename(p->one->path, p->two->path);
 
-       /* Find common prefix */
-       old = p->one->path;
-       new = p->two->path;
-       while (1) {
-               const char *slash_old, *slash_new;
-               slash_old = strchr(old, '/');
-               slash_new = strchr(new, '/');
-               if (!slash_old ||
-                   !slash_new ||
-                   slash_old - old != slash_new - new ||
-                   memcmp(old, new, slash_new - new))
-                       break;
-               old = slash_old + 1;
-               new = slash_new + 1;
-       }
-       /* p->one->path thru old is the common prefix, and old and new
-        * through the end of names are renames
-        */
-       if (old != p->one->path)
-               printf(" %s %.*s{%s => %s} (%d%%)\n", renamecopy,
-                      (int)(old - p->one->path), p->one->path,
-                      old, new, (int)(0.5 + p->score * 100.0/MAX_SCORE));
-       else
-               printf(" %s %s => %s (%d%%)\n", renamecopy,
-                      p->one->path, p->two->path,
-                      (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       printf(" %s %s (%d%%)\n", renamecopy, names,
+              (int)(0.5 + p->score * 100.0/MAX_SCORE));
+       free(names);
        show_mode_change(p, 0);
 }
 
@@ -2442,8 +2495,10 @@ static void diff_summary(struct diff_filepair *p)
                break;
        default:
                if (p->score) {
-                       printf(" rewrite %s (%d%%)\n", p->two->path,
+                       char *name = quote_one(p->two->path);
+                       printf(" rewrite %s (%d%%)\n", name,
                                (int)(0.5 + p->score * 100.0/MAX_SCORE));
+                       free(name);
                        show_mode_change(p, 0);
                } else  show_mode_change(p, 1);
                break;
@@ -2639,7 +2694,7 @@ void diff_flush(struct diff_options *options)
                separator++;
        }
 
-       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) {
+       if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
                struct diffstat_t diffstat;
 
                memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2653,6 +2708,8 @@ void diff_flush(struct diff_options *options)
                        show_numstat(&diffstat, options);
                if (output_format & DIFF_FORMAT_DIFFSTAT)
                        show_stats(&diffstat, options);
+               else if (output_format & DIFF_FORMAT_SHORTSTAT)
+                       show_shortstats(&diffstat);
                separator++;
        }
 
@@ -2835,10 +2892,12 @@ void diff_change(struct diff_options *options,
 }
 
 void diff_unmerge(struct diff_options *options,
-                 const char *path)
+                 const char *path,
+                 unsigned mode, const unsigned char *sha1)
 {
        struct diff_filespec *one, *two;
        one = alloc_filespec(path);
        two = alloc_filespec(path);
-       diff_queue(&diff_queued_diff, one, two);
+       fill_filespec(one, sha1, mode);
+       diff_queue(&diff_queued_diff, one, two)->is_unmerged = 1;
 }
diff --git a/diff.h b/diff.h
index 101b2b505dcace41754687a427ad0523daaa50a0..eece65ddccdb7bd8bf51af80af92e29b4eb76f37 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -29,6 +29,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
 #define DIFF_FORMAT_NUMSTAT    0x0004
 #define DIFF_FORMAT_SUMMARY    0x0008
 #define DIFF_FORMAT_PATCH      0x0010
+#define DIFF_FORMAT_SHORTSTAT  0x0020
 
 /* These override all above */
 #define DIFF_FORMAT_NAME       0x0100
@@ -143,7 +144,9 @@ extern void diff_change(struct diff_options *,
                        const char *base, const char *path);
 
 extern void diff_unmerge(struct diff_options *,
-                        const char *path);
+                        const char *path,
+                        unsigned mode,
+                        const unsigned char *sha1);
 
 extern int diff_scoreopt_parse(const char *opt);
 
@@ -219,6 +222,7 @@ extern int run_diff_files(struct rev_info *revs, int silent_on_removed);
 
 extern int run_diff_index(struct rev_info *revs, int cached);
 
+extern int do_diff_cache(const unsigned char *, struct diff_options *);
 extern int diff_flush_patch_id(struct diff_options *, unsigned char *);
 
 #endif /* DIFF_H */
index aef6da60447b98a05d91513b4aa74e505b0a0d84..7ad09461858aa6832b828df411fd1b736f035465 100644 (file)
@@ -4,7 +4,6 @@
 #include "cache.h"
 #include "diff.h"
 #include "diffcore.h"
-#include <fnmatch.h>
 
 static char **order;
 static int order_cnt;
index cfcce315babb0edb7ad7be7f2ab2437aaf41eb9d..286919e71428049eda1449ba71ac81667ebc3f7d 100644 (file)
@@ -5,8 +5,6 @@
 #include "diff.h"
 #include "diffcore.h"
 
-#include <regex.h>
-
 static unsigned int contains(struct diff_filespec *one,
                             const char *needle, unsigned long len,
                             regex_t *regexp)
@@ -16,6 +14,8 @@ static unsigned int contains(struct diff_filespec *one,
        const char *data;
        if (diff_populate_filespec(one, 0))
                return 0;
+       if (!len)
+               return 0;
 
        sz = one->size;
        data = one->data;
index 57a74b6bb83e14af5f7336f397f79fbfcdc8b5f7..91fa2bea519a39ee65dcf018b7a72885b9eeec70 100644 (file)
@@ -109,6 +109,8 @@ static int is_exact_match(struct diff_filespec *src,
                return 0;
        if (src->size != dst->size)
                return 0;
+       if (src->sha1_valid && dst->sha1_valid)
+           return !hashcmp(src->sha1, dst->sha1);
        if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
                return 0;
        if (src->size == dst->size &&
index 2249bc2c05744ce5026744547fcb30195a19b3f1..1ea80671e30500f95fc1b648ccc6d5143ac0ac52 100644 (file)
@@ -54,9 +54,9 @@ struct diff_filepair {
        unsigned source_stays : 1; /* all of R/C are copies */
        unsigned broken_pair : 1;
        unsigned renamed_pair : 1;
+       unsigned is_unmerged : 1;
 };
-#define DIFF_PAIR_UNMERGED(p) \
-       (!DIFF_FILE_VALID((p)->one) && !DIFF_FILE_VALID((p)->two))
+#define DIFF_PAIR_UNMERGED(p) ((p)->is_unmerged)
 
 #define DIFF_PAIR_RENAME(p) ((p)->renamed_pair)
 
diff --git a/dir.c b/dir.c
index 96389b32e66af24896220e4738cd93bf2ac75704..32b57f0125d1b9e95345eb23a9a32fbf8444b1b8 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -5,9 +5,6 @@
  * Copyright (C) Linus Torvalds, 2005-2006
  *              Junio Hamano, 2005-2006
  */
-#include <dirent.h>
-#include <fnmatch.h>
-
 #include "cache.h"
 #include "dir.h"
 
@@ -43,6 +40,18 @@ int common_prefix(const char **pathspec)
        return prefix;
 }
 
+/*
+ * Does 'match' matches the given name?
+ * A match is found if
+ *
+ * (1) the 'match' string is leading directory of 'name', or
+ * (2) the 'match' string is a wildcard and matches 'name', or
+ * (3) the 'match' string is exactly the same as 'name'.
+ *
+ * and the return value tells which case it was.
+ *
+ * It returns 0 when there is no match.
+ */
 static int match_one(const char *match, const char *name, int namelen)
 {
        int matchlen;
@@ -50,27 +59,30 @@ static int match_one(const char *match, const char *name, int namelen)
        /* If the match was just the prefix, we matched */
        matchlen = strlen(match);
        if (!matchlen)
-               return 1;
+               return MATCHED_RECURSIVELY;
 
        /*
         * If we don't match the matchstring exactly,
         * we need to match by fnmatch
         */
        if (strncmp(match, name, matchlen))
-               return !fnmatch(match, name, 0);
+               return !fnmatch(match, name, 0) ? MATCHED_FNMATCH : 0;
 
-       /*
-        * If we did match the string exactly, we still
-        * need to make sure that it happened on a path
-        * component boundary (ie either the last character
-        * of the match was '/', or the next character of
-        * the name was '/' or the terminating NUL.
-        */
-       return  match[matchlen-1] == '/' ||
-               name[matchlen] == '/' ||
-               !name[matchlen];
+       if (!name[matchlen])
+               return MATCHED_EXACTLY;
+       if (match[matchlen-1] == '/' || name[matchlen] == '/')
+               return MATCHED_RECURSIVELY;
+       return 0;
 }
 
+/*
+ * Given a name and a list of pathspecs, see if the name matches
+ * any of the pathspecs.  The caller is also interested in seeing
+ * all pathspec matches some names it calls this function with
+ * (otherwise the user could have mistyped the unmatched pathspec),
+ * and a mark is left in seen[] array for pathspec element that
+ * actually matched anything.
+ */
 int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen)
 {
        int retval;
@@ -80,12 +92,16 @@ int match_pathspec(const char **pathspec, const char *name, int namelen, int pre
        namelen -= prefix;
 
        for (retval = 0; (match = *pathspec++) != NULL; seen++) {
-               if (retval & *seen)
+               int how;
+               if (retval && *seen == MATCHED_EXACTLY)
                        continue;
                match += prefix;
-               if (match_one(match, name, namelen)) {
-                       retval = 1;
-                       *seen = 1;
+               how = match_one(match, name, namelen);
+               if (how) {
+                       if (retval < how)
+                               retval = how;
+                       if (*seen < how)
+                               *seen = how;
                }
        }
        return retval;
@@ -126,7 +142,7 @@ static int add_excludes_from_file_1(const char *fname,
                return 0;
        }
        buf = xmalloc(size+1);
-       if (read(fd, buf, size) != size)
+       if (read_in_full(fd, buf, size) != size)
                goto err;
        close(fd);
 
@@ -156,7 +172,7 @@ void add_excludes_from_file(struct dir_struct *dir, const char *fname)
                die("cannot use %s as an exclude file", fname);
 }
 
-static int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
+int push_exclude_per_directory(struct dir_struct *dir, const char *base, int baselen)
 {
        char exclude_file[PATH_MAX];
        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
@@ -170,7 +186,7 @@ static int push_exclude_per_directory(struct dir_struct *dir, const char *base,
        return current_nr;
 }
 
-static void pop_exclude_per_directory(struct dir_struct *dir, int stk)
+void pop_exclude_per_directory(struct dir_struct *dir, int stk)
 {
        struct exclude_list *el = &dir->exclude_list[EXC_DIRS];
 
@@ -244,12 +260,12 @@ int excluded(struct dir_struct *dir, const char *pathname)
        return 0;
 }
 
-static void add_name(struct dir_struct *dir, const char *pathname, int len)
+struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len)
 {
        struct dir_entry *ent;
 
        if (cache_name_pos(pathname, len) >= 0)
-               return;
+               return NULL;
 
        if (dir->nr == dir->alloc) {
                int alloc = alloc_nr(dir->alloc);
@@ -257,10 +273,12 @@ static void add_name(struct dir_struct *dir, const char *pathname, int len)
                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;
 }
 
 static int dir_exists(const char *dirname, int len)
@@ -348,7 +366,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co
                        if (check_only)
                                goto exit_early;
                        else
-                               add_name(dir, fullname, baselen + len);
+                               dir_add_name(dir, fullname, baselen + len);
                }
 exit_early:
                closedir(fdir);
diff --git a/dir.h b/dir.h
index 313f8ab64e7e3a6be1d22335eb54872c929d857e..7233d65bbd393f1d34d75538dd0e39e4a86383f2 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -13,7 +13,9 @@
 
 
 struct dir_entry {
-       int len;
+       unsigned int ignored : 1;
+       unsigned int ignored_dir : 1;
+       unsigned int len : 30;
        char name[FLEX_ARRAY]; /* more */
 };
 
@@ -40,13 +42,21 @@ struct dir_struct {
 };
 
 extern int common_prefix(const char **pathspec);
+
+#define MATCHED_RECURSIVELY 1
+#define MATCHED_FNMATCH 2
+#define MATCHED_EXACTLY 3
 extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
 
 extern int read_directory(struct dir_struct *, const char *path, const char *base, int baselen);
+extern int push_exclude_per_directory(struct dir_struct *, const char *, int);
+extern void pop_exclude_per_directory(struct dir_struct *, int);
+
 extern int excluded(struct dir_struct *, const char *);
 extern void add_excludes_from_file(struct dir_struct *, const char *fname);
 extern void add_exclude(const char *string, const char *base,
                        int baselen, struct exclude_list *which);
 extern int file_exists(const char *);
+extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len);
 
 #endif
diff --git a/entry.c b/entry.c
index b2ea0efa82e1a0511fe5aa798618c23827b59bab..c2641ddefdfde5ccb08af900ac48378231f969c7 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -1,5 +1,3 @@
-#include <sys/types.h>
-#include <dirent.h>
 #include "cache.h"
 #include "blob.h"
 
@@ -91,7 +89,8 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
                        return error("git-checkout-index: unable to create file %s (%s)",
                                path, strerror(errno));
                }
-               wrote = write(fd, new, size);
+               /* FIXME: LF -> CRLF conversion goes here, based on "ce->name" */
+               wrote = write_in_full(fd, new, size);
                close(fd);
                free(new);
                if (wrote != size)
@@ -106,7 +105,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct checkout *stat
                                return error("git-checkout-index: unable to create "
                                                 "file %s (%s)", path, strerror(errno));
                        }
-                       wrote = write(fd, new, size);
+                       wrote = write_in_full(fd, new, size);
                        close(fd);
                        free(new);
                        if (wrote != size)
index 84d870ca4eca6e202bcbec388e9a299b887f9a12..54c22f8248cc04647829c8a7558f2bb24515f30f 100644 (file)
@@ -15,13 +15,17 @@ int use_legacy_headers = 1;
 int trust_executable_bit = 1;
 int assume_unchanged;
 int prefer_symlink_refs;
-int log_all_ref_updates;
+int is_bare_repository_cfg = -1; /* unspecified */
+int log_all_ref_updates = -1; /* unspecified */
 int warn_ambiguous_refs = 1;
 int repository_format_version;
-char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
+char *git_commit_encoding;
+char *git_log_output_encoding;
 int shared_repository = PERM_UMASK;
 const char *apply_default_whitespace;
 int zlib_compression_level = Z_DEFAULT_COMPRESSION;
+size_t packed_git_window_size = DEFAULT_PACKED_GIT_WINDOW_SIZE;
+size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
 int pager_in_use;
 int pager_use_color = 1;
 
@@ -50,6 +54,19 @@ static void setup_git_env(void)
                git_graft_file = xstrdup(git_path("info/grafts"));
 }
 
+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);
+}
+
 const char *get_git_dir(void)
 {
        if (!git_dir)
index 5d6a1247b4a1955dffae2b52da064a6eb489d83b..3996bce33fe11b8f0bae120fabd40a74c66de342 100644 (file)
@@ -21,7 +21,7 @@ const char *git_exec_path(void)
        if (current_exec_path)
                return current_exec_path;
 
-       env = getenv("GIT_EXEC_PATH");
+       env = getenv(EXEC_PATH_ENVIRONMENT);
        if (env && *env) {
                return env;
        }
@@ -35,7 +35,7 @@ int execv_git_cmd(const char **argv)
        char git_command[PATH_MAX + 1];
        int i;
        const char *paths[] = { current_exec_path,
-                               getenv("GIT_EXEC_PATH"),
+                               getenv(EXEC_PATH_ENVIRONMENT),
                                builtin_exec_path };
 
        for (i = 0; i < ARRAY_SIZE(paths); ++i) {
diff --git a/fast-import.c b/fast-import.c
new file mode 100644 (file)
index 0000000..404d911
--- /dev/null
@@ -0,0 +1,2081 @@
+/*
+Format of STDIN stream:
+
+  stream ::= cmd*;
+
+  cmd ::= new_blob
+        | new_commit
+        | new_tag
+        | reset_branch
+        | checkpoint
+        ;
+
+  new_blob ::= 'blob' lf
+       mark?
+    file_content;
+  file_content ::= data;
+
+  new_commit ::= 'commit' sp ref_str lf
+    mark?
+    ('author' sp name '<' email '>' when lf)?
+    'committer' sp name '<' email '>' when lf
+    commit_msg
+    ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
+    ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)*
+    file_change*
+    lf;
+  commit_msg ::= data;
+
+  file_change ::= file_clr | file_del | file_obm | file_inm;
+  file_clr ::= 'deleteall' lf;
+  file_del ::= 'D' 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;
+
+  new_tag ::= 'tag' sp tag_str lf
+    'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf
+       'tagger' sp name '<' email '>' when lf
+    tag_msg;
+  tag_msg ::= data;
+
+  reset_branch ::= 'reset' sp ref_str lf
+    ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
+    lf;
+
+  checkpoint ::= 'checkpoint' lf
+    lf;
+
+     # note: the first idnum in a stream should be 1 and subsequent
+     # idnums should not have gaps between values as this will cause
+     # the stream parser to reserve space for the gapped values.  An
+        # idnum can be updated in the future to a new object by issuing
+     # a new mark directive with the old idnum.
+        #
+  mark ::= 'mark' sp idnum lf;
+  data ::= (delimited_data | exact_data)
+    lf;
+
+    # note: delim may be any string but must not contain lf.
+    # data_line may contain any data but must not be exactly
+    # delim.
+  delimited_data ::= 'data' sp '<<' delim lf
+    (data_line lf)*
+       delim lf;
+
+     # note: declen indicates the length of binary_data in bytes.
+     # declen does not include the lf preceeding the binary data.
+     #
+  exact_data ::= 'data' sp declen lf
+    binary_data;
+
+     # note: quoted strings are C-style quoting supporting \c for
+     # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn
+        # is the signed byte value in octal.  Note that the only
+     # characters which must actually be escaped to protect the
+     # stream formatting is: \, " and LF.  Otherwise these values
+        # are UTF8.
+     #
+  ref_str     ::= ref;
+  sha1exp_str ::= sha1exp;
+  tag_str     ::= tag;
+  path_str    ::= path    | '"' quoted(path)    '"' ;
+  mode        ::= '100644' | '644'
+                | '100755' | '755'
+                | '120000'
+                ;
+
+  declen ::= # unsigned 32 bit value, ascii base10 notation;
+  bigint ::= # unsigned integer value, ascii base10 notation;
+  binary_data ::= # file content, not interpreted;
+
+  when         ::= raw_when | rfc2822_when;
+  raw_when     ::= ts sp tz;
+  rfc2822_when ::= # Valid RFC 2822 date and time;
+
+  sp ::= # ASCII space character;
+  lf ::= # ASCII newline (LF) character;
+
+     # note: a colon (':') must precede the numerical value assigned to
+        # an idnum.  This is to distinguish it from a ref or tag name as
+     # GIT does not permit ':' in ref or tag strings.
+        #
+  idnum   ::= ':' bigint;
+  path    ::= # GIT style file path, e.g. "a/b/c";
+  ref     ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT";
+  tag     ::= # GIT tag name, e.g. "FIREFOX_1_5";
+  sha1exp ::= # Any valid GIT SHA1 expression;
+  hexsha1 ::= # SHA1 in hexadecimal format;
+
+     # note: name and email are UTF8 strings, however name must not
+        # contain '<' or lf and email must not contain any of the
+     # following: '<', '>', lf.
+        #
+  name  ::= # valid GIT author/committer name;
+  email ::= # valid GIT author/committer email;
+  ts    ::= # time since the epoch in seconds, ascii base10 notation;
+  tz    ::= # GIT style timezone;
+*/
+
+#include "builtin.h"
+#include "cache.h"
+#include "object.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "delta.h"
+#include "pack.h"
+#include "refs.h"
+#include "csum-file.h"
+#include "strbuf.h"
+#include "quote.h"
+
+#define PACK_ID_BITS 16
+#define MAX_PACK_ID ((1<<PACK_ID_BITS)-1)
+
+struct object_entry
+{
+       struct object_entry *next;
+       uint32_t offset;
+       unsigned type : TYPE_BITS;
+       unsigned pack_id : PACK_ID_BITS;
+       unsigned char sha1[20];
+};
+
+struct object_entry_pool
+{
+       struct object_entry_pool *next_pool;
+       struct object_entry *next_free;
+       struct object_entry *end;
+       struct object_entry entries[FLEX_ARRAY]; /* more */
+};
+
+struct mark_set
+{
+       union {
+               struct object_entry *marked[1024];
+               struct mark_set *sets[1024];
+       } data;
+       unsigned int shift;
+};
+
+struct last_object
+{
+       void *data;
+       unsigned long len;
+       uint32_t offset;
+       unsigned int depth;
+       unsigned no_free:1;
+};
+
+struct mem_pool
+{
+       struct mem_pool *next_pool;
+       char *next_free;
+       char *end;
+       char space[FLEX_ARRAY]; /* more */
+};
+
+struct atom_str
+{
+       struct atom_str *next_atom;
+       unsigned short str_len;
+       char str_dat[FLEX_ARRAY]; /* more */
+};
+
+struct tree_content;
+struct tree_entry
+{
+       struct tree_content *tree;
+       struct atom_str* name;
+       struct tree_entry_ms
+       {
+               uint16_t mode;
+               unsigned char sha1[20];
+       } versions[2];
+};
+
+struct tree_content
+{
+       unsigned int entry_capacity; /* must match avail_tree_content */
+       unsigned int entry_count;
+       unsigned int delta_depth;
+       struct tree_entry *entries[FLEX_ARRAY]; /* more */
+};
+
+struct avail_tree_content
+{
+       unsigned int entry_capacity; /* must match tree_content */
+       struct avail_tree_content *next_avail;
+};
+
+struct branch
+{
+       struct branch *table_next_branch;
+       struct branch *active_next_branch;
+       const char *name;
+       struct tree_entry branch_tree;
+       uintmax_t last_commit;
+       unsigned int pack_id;
+       unsigned char sha1[20];
+};
+
+struct tag
+{
+       struct tag *next_tag;
+       const char *name;
+       unsigned int pack_id;
+       unsigned char sha1[20];
+};
+
+struct dbuf
+{
+       void *buffer;
+       size_t capacity;
+};
+
+struct hash_list
+{
+       struct hash_list *next;
+       unsigned char sha1[20];
+};
+
+typedef enum {
+       WHENSPEC_RAW = 1,
+       WHENSPEC_RFC2822,
+       WHENSPEC_NOW,
+} whenspec_type;
+
+/* Configured limits on output */
+static unsigned long max_depth = 10;
+static unsigned long max_packsize = (1LL << 32) - 1;
+static int force_update;
+
+/* Stats and misc. counters */
+static uintmax_t alloc_count;
+static uintmax_t marks_set_count;
+static uintmax_t object_count_by_type[1 << TYPE_BITS];
+static uintmax_t duplicate_count_by_type[1 << TYPE_BITS];
+static uintmax_t delta_count_by_type[1 << TYPE_BITS];
+static unsigned long object_count;
+static unsigned long branch_count;
+static unsigned long branch_load_count;
+static int failure;
+static FILE *pack_edges;
+
+/* Memory pools */
+static size_t mem_pool_alloc = 2*1024*1024 - sizeof(struct mem_pool);
+static size_t total_allocd;
+static struct mem_pool *mem_pool;
+
+/* Atom management */
+static unsigned int atom_table_sz = 4451;
+static unsigned int atom_cnt;
+static struct atom_str **atom_table;
+
+/* The .pack file being generated */
+static unsigned int pack_id;
+static struct packed_git *pack_data;
+static struct packed_git **all_packs;
+static unsigned long pack_size;
+
+/* Table of objects we've written. */
+static unsigned int object_entry_alloc = 5000;
+static struct object_entry_pool *blocks;
+static struct object_entry *object_table[1 << 16];
+static struct mark_set *marks;
+static const char* mark_file;
+
+/* Our last blob */
+static struct last_object last_blob;
+
+/* Tree management */
+static unsigned int tree_entry_alloc = 1000;
+static void *avail_tree_entry;
+static unsigned int avail_tree_table_sz = 100;
+static struct avail_tree_content **avail_tree_table;
+static struct dbuf old_tree;
+static struct dbuf new_tree;
+
+/* Branch data */
+static unsigned long max_active_branches = 5;
+static unsigned long cur_active_branches;
+static unsigned long branch_table_sz = 1039;
+static struct branch **branch_table;
+static struct branch *active_branches;
+
+/* Tag data */
+static struct tag *first_tag;
+static struct tag *last_tag;
+
+/* Input stream parsing */
+static whenspec_type whenspec = WHENSPEC_RAW;
+static struct strbuf command_buf;
+static uintmax_t next_mark;
+static struct dbuf new_data;
+
+
+static void alloc_objects(unsigned int cnt)
+{
+       struct object_entry_pool *b;
+
+       b = xmalloc(sizeof(struct object_entry_pool)
+               + cnt * sizeof(struct object_entry));
+       b->next_pool = blocks;
+       b->next_free = b->entries;
+       b->end = b->entries + cnt;
+       blocks = b;
+       alloc_count += cnt;
+}
+
+static struct object_entry *new_object(unsigned char *sha1)
+{
+       struct object_entry *e;
+
+       if (blocks->next_free == blocks->end)
+               alloc_objects(object_entry_alloc);
+
+       e = blocks->next_free++;
+       hashcpy(e->sha1, sha1);
+       return e;
+}
+
+static struct object_entry *find_object(unsigned char *sha1)
+{
+       unsigned int h = sha1[0] << 8 | sha1[1];
+       struct object_entry *e;
+       for (e = object_table[h]; e; e = e->next)
+               if (!hashcmp(sha1, e->sha1))
+                       return e;
+       return NULL;
+}
+
+static struct object_entry *insert_object(unsigned char *sha1)
+{
+       unsigned int h = sha1[0] << 8 | sha1[1];
+       struct object_entry *e = object_table[h];
+       struct object_entry *p = NULL;
+
+       while (e) {
+               if (!hashcmp(sha1, e->sha1))
+                       return e;
+               p = e;
+               e = e->next;
+       }
+
+       e = new_object(sha1);
+       e->next = NULL;
+       e->offset = 0;
+       if (p)
+               p->next = e;
+       else
+               object_table[h] = e;
+       return e;
+}
+
+static unsigned int hc_str(const char *s, size_t len)
+{
+       unsigned int r = 0;
+       while (len-- > 0)
+               r = r * 31 + *s++;
+       return r;
+}
+
+static void *pool_alloc(size_t len)
+{
+       struct mem_pool *p;
+       void *r;
+
+       for (p = mem_pool; p; p = p->next_pool)
+               if ((p->end - p->next_free >= len))
+                       break;
+
+       if (!p) {
+               if (len >= (mem_pool_alloc/2)) {
+                       total_allocd += len;
+                       return xmalloc(len);
+               }
+               total_allocd += sizeof(struct mem_pool) + mem_pool_alloc;
+               p = xmalloc(sizeof(struct mem_pool) + mem_pool_alloc);
+               p->next_pool = mem_pool;
+               p->next_free = p->space;
+               p->end = p->next_free + mem_pool_alloc;
+               mem_pool = p;
+       }
+
+       r = p->next_free;
+       /* round out to a pointer alignment */
+       if (len & (sizeof(void*) - 1))
+               len += sizeof(void*) - (len & (sizeof(void*) - 1));
+       p->next_free += len;
+       return r;
+}
+
+static void *pool_calloc(size_t count, size_t size)
+{
+       size_t len = count * size;
+       void *r = pool_alloc(len);
+       memset(r, 0, len);
+       return r;
+}
+
+static char *pool_strdup(const char *s)
+{
+       char *r = pool_alloc(strlen(s) + 1);
+       strcpy(r, s);
+       return r;
+}
+
+static void size_dbuf(struct dbuf *b, size_t maxlen)
+{
+       if (b->buffer) {
+               if (b->capacity >= maxlen)
+                       return;
+               free(b->buffer);
+       }
+       b->capacity = ((maxlen / 1024) + 1) * 1024;
+       b->buffer = xmalloc(b->capacity);
+}
+
+static void insert_mark(uintmax_t idnum, struct object_entry *oe)
+{
+       struct mark_set *s = marks;
+       while ((idnum >> s->shift) >= 1024) {
+               s = pool_calloc(1, sizeof(struct mark_set));
+               s->shift = marks->shift + 10;
+               s->data.sets[0] = marks;
+               marks = s;
+       }
+       while (s->shift) {
+               uintmax_t i = idnum >> s->shift;
+               idnum -= i << s->shift;
+               if (!s->data.sets[i]) {
+                       s->data.sets[i] = pool_calloc(1, sizeof(struct mark_set));
+                       s->data.sets[i]->shift = s->shift - 10;
+               }
+               s = s->data.sets[i];
+       }
+       if (!s->data.marked[idnum])
+               marks_set_count++;
+       s->data.marked[idnum] = oe;
+}
+
+static struct object_entry *find_mark(uintmax_t idnum)
+{
+       uintmax_t orig_idnum = idnum;
+       struct mark_set *s = marks;
+       struct object_entry *oe = NULL;
+       if ((idnum >> s->shift) < 1024) {
+               while (s && s->shift) {
+                       uintmax_t i = idnum >> s->shift;
+                       idnum -= i << s->shift;
+                       s = s->data.sets[i];
+               }
+               if (s)
+                       oe = s->data.marked[idnum];
+       }
+       if (!oe)
+               die("mark :%ju not declared", orig_idnum);
+       return oe;
+}
+
+static struct atom_str *to_atom(const char *s, unsigned short len)
+{
+       unsigned int hc = hc_str(s, len) % atom_table_sz;
+       struct atom_str *c;
+
+       for (c = atom_table[hc]; c; c = c->next_atom)
+               if (c->str_len == len && !strncmp(s, c->str_dat, len))
+                       return c;
+
+       c = pool_alloc(sizeof(struct atom_str) + len + 1);
+       c->str_len = len;
+       strncpy(c->str_dat, s, len);
+       c->str_dat[len] = 0;
+       c->next_atom = atom_table[hc];
+       atom_table[hc] = c;
+       atom_cnt++;
+       return c;
+}
+
+static struct branch *lookup_branch(const char *name)
+{
+       unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz;
+       struct branch *b;
+
+       for (b = branch_table[hc]; b; b = b->table_next_branch)
+               if (!strcmp(name, b->name))
+                       return b;
+       return NULL;
+}
+
+static struct branch *new_branch(const char *name)
+{
+       unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz;
+       struct branch* b = lookup_branch(name);
+
+       if (b)
+               die("Invalid attempt to create duplicate branch: %s", name);
+       if (check_ref_format(name))
+               die("Branch name doesn't conform to GIT standards: %s", name);
+
+       b = pool_calloc(1, sizeof(struct branch));
+       b->name = pool_strdup(name);
+       b->table_next_branch = branch_table[hc];
+       b->branch_tree.versions[0].mode = S_IFDIR;
+       b->branch_tree.versions[1].mode = S_IFDIR;
+       b->pack_id = MAX_PACK_ID;
+       branch_table[hc] = b;
+       branch_count++;
+       return b;
+}
+
+static unsigned int hc_entries(unsigned int cnt)
+{
+       cnt = cnt & 7 ? (cnt / 8) + 1 : cnt / 8;
+       return cnt < avail_tree_table_sz ? cnt : avail_tree_table_sz - 1;
+}
+
+static struct tree_content *new_tree_content(unsigned int cnt)
+{
+       struct avail_tree_content *f, *l = NULL;
+       struct tree_content *t;
+       unsigned int hc = hc_entries(cnt);
+
+       for (f = avail_tree_table[hc]; f; l = f, f = f->next_avail)
+               if (f->entry_capacity >= cnt)
+                       break;
+
+       if (f) {
+               if (l)
+                       l->next_avail = f->next_avail;
+               else
+                       avail_tree_table[hc] = f->next_avail;
+       } else {
+               cnt = cnt & 7 ? ((cnt / 8) + 1) * 8 : cnt;
+               f = pool_alloc(sizeof(*t) + sizeof(t->entries[0]) * cnt);
+               f->entry_capacity = cnt;
+       }
+
+       t = (struct tree_content*)f;
+       t->entry_count = 0;
+       t->delta_depth = 0;
+       return t;
+}
+
+static void release_tree_entry(struct tree_entry *e);
+static void release_tree_content(struct tree_content *t)
+{
+       struct avail_tree_content *f = (struct avail_tree_content*)t;
+       unsigned int hc = hc_entries(f->entry_capacity);
+       f->next_avail = avail_tree_table[hc];
+       avail_tree_table[hc] = f;
+}
+
+static void release_tree_content_recursive(struct tree_content *t)
+{
+       unsigned int i;
+       for (i = 0; i < t->entry_count; i++)
+               release_tree_entry(t->entries[i]);
+       release_tree_content(t);
+}
+
+static struct tree_content *grow_tree_content(
+       struct tree_content *t,
+       int amt)
+{
+       struct tree_content *r = new_tree_content(t->entry_count + amt);
+       r->entry_count = t->entry_count;
+       r->delta_depth = t->delta_depth;
+       memcpy(r->entries,t->entries,t->entry_count*sizeof(t->entries[0]));
+       release_tree_content(t);
+       return r;
+}
+
+static struct tree_entry *new_tree_entry(void)
+{
+       struct tree_entry *e;
+
+       if (!avail_tree_entry) {
+               unsigned int n = tree_entry_alloc;
+               total_allocd += n * sizeof(struct tree_entry);
+               avail_tree_entry = e = xmalloc(n * sizeof(struct tree_entry));
+               while (n-- > 1) {
+                       *((void**)e) = e + 1;
+                       e++;
+               }
+               *((void**)e) = NULL;
+       }
+
+       e = avail_tree_entry;
+       avail_tree_entry = *((void**)e);
+       return e;
+}
+
+static void release_tree_entry(struct tree_entry *e)
+{
+       if (e->tree)
+               release_tree_content_recursive(e->tree);
+       *((void**)e) = avail_tree_entry;
+       avail_tree_entry = e;
+}
+
+static void start_packfile(void)
+{
+       static char tmpfile[PATH_MAX];
+       struct packed_git *p;
+       struct pack_header hdr;
+       int pack_fd;
+
+       snprintf(tmpfile, sizeof(tmpfile),
+               "%s/pack_XXXXXX", get_object_directory());
+       pack_fd = mkstemp(tmpfile);
+       if (pack_fd < 0)
+               die("Can't create %s: %s", tmpfile, strerror(errno));
+       p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
+       strcpy(p->pack_name, tmpfile);
+       p->pack_fd = pack_fd;
+
+       hdr.hdr_signature = htonl(PACK_SIGNATURE);
+       hdr.hdr_version = htonl(2);
+       hdr.hdr_entries = 0;
+       write_or_die(p->pack_fd, &hdr, sizeof(hdr));
+
+       pack_data = p;
+       pack_size = sizeof(hdr);
+       object_count = 0;
+
+       all_packs = xrealloc(all_packs, sizeof(*all_packs) * (pack_id + 1));
+       all_packs[pack_id] = p;
+}
+
+static void fixup_header_footer(void)
+{
+       static const int buf_sz = 128 * 1024;
+       int pack_fd = pack_data->pack_fd;
+       SHA_CTX c;
+       struct pack_header hdr;
+       char *buf;
+
+       if (lseek(pack_fd, 0, SEEK_SET) != 0)
+               die("Failed seeking to start: %s", strerror(errno));
+       if (read_in_full(pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+               die("Unable to reread header of %s", pack_data->pack_name);
+       if (lseek(pack_fd, 0, SEEK_SET) != 0)
+               die("Failed seeking to start: %s", strerror(errno));
+       hdr.hdr_entries = htonl(object_count);
+       write_or_die(pack_fd, &hdr, sizeof(hdr));
+
+       SHA1_Init(&c);
+       SHA1_Update(&c, &hdr, sizeof(hdr));
+
+       buf = xmalloc(buf_sz);
+       for (;;) {
+               size_t n = xread(pack_fd, buf, buf_sz);
+               if (!n)
+                       break;
+               if (n < 0)
+                       die("Failed to checksum %s", pack_data->pack_name);
+               SHA1_Update(&c, buf, n);
+       }
+       free(buf);
+
+       SHA1_Final(pack_data->sha1, &c);
+       write_or_die(pack_fd, pack_data->sha1, sizeof(pack_data->sha1));
+       close(pack_fd);
+}
+
+static int oecmp (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);
+}
+
+static char *create_index(void)
+{
+       static char tmpfile[PATH_MAX];
+       SHA_CTX ctx;
+       struct sha1file *f;
+       struct object_entry **idx, **c, **last, *e;
+       struct object_entry_pool *o;
+       uint32_t array[256];
+       int i, idx_fd;
+
+       /* Build the sorted table of object IDs. */
+       idx = xmalloc(object_count * sizeof(struct object_entry*));
+       c = idx;
+       for (o = blocks; o; o = o->next_pool)
+               for (e = o->next_free; e-- != o->entries;)
+                       if (pack_id == e->pack_id)
+                               *c++ = e;
+       last = idx + object_count;
+       if (c != last)
+               die("internal consistency error creating the index");
+       qsort(idx, object_count, sizeof(struct object_entry*), oecmp);
+
+       /* Generate the fan-out array. */
+       c = idx;
+       for (i = 0; i < 256; i++) {
+               struct object_entry **next = c;;
+               while (next < last) {
+                       if ((*next)->sha1[0] != i)
+                               break;
+                       next++;
+               }
+               array[i] = htonl(next - idx);
+               c = next;
+       }
+
+       snprintf(tmpfile, sizeof(tmpfile),
+               "%s/index_XXXXXX", get_object_directory());
+       idx_fd = mkstemp(tmpfile);
+       if (idx_fd < 0)
+               die("Can't create %s: %s", tmpfile, strerror(errno));
+       f = sha1fd(idx_fd, tmpfile);
+       sha1write(f, array, 256 * sizeof(int));
+       SHA1_Init(&ctx);
+       for (c = idx; c != last; c++) {
+               uint32_t offset = htonl((*c)->offset);
+               sha1write(f, &offset, 4);
+               sha1write(f, (*c)->sha1, sizeof((*c)->sha1));
+               SHA1_Update(&ctx, (*c)->sha1, 20);
+       }
+       sha1write(f, pack_data->sha1, sizeof(pack_data->sha1));
+       sha1close(f, NULL, 1);
+       free(idx);
+       SHA1_Final(pack_data->sha1, &ctx);
+       return tmpfile;
+}
+
+static char *keep_pack(char *curr_index_name)
+{
+       static char name[PATH_MAX];
+       static char *keep_msg = "fast-import";
+       int keep_fd;
+
+       chmod(pack_data->pack_name, 0444);
+       chmod(curr_index_name, 0444);
+
+       snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
+                get_object_directory(), sha1_to_hex(pack_data->sha1));
+       keep_fd = open(name, O_RDWR|O_CREAT|O_EXCL, 0600);
+       if (keep_fd < 0)
+               die("cannot create keep file");
+       write(keep_fd, keep_msg, strlen(keep_msg));
+       close(keep_fd);
+
+       snprintf(name, sizeof(name), "%s/pack/pack-%s.pack",
+                get_object_directory(), sha1_to_hex(pack_data->sha1));
+       if (move_temp_to_file(pack_data->pack_name, name))
+               die("cannot store pack file");
+
+       snprintf(name, sizeof(name), "%s/pack/pack-%s.idx",
+                get_object_directory(), sha1_to_hex(pack_data->sha1));
+       if (move_temp_to_file(curr_index_name, name))
+               die("cannot store index file");
+       return name;
+}
+
+static void unkeep_all_packs(void)
+{
+       static char name[PATH_MAX];
+       int k;
+
+       for (k = 0; k < pack_id; k++) {
+               struct packed_git *p = all_packs[k];
+               snprintf(name, sizeof(name), "%s/pack/pack-%s.keep",
+                        get_object_directory(), sha1_to_hex(p->sha1));
+               unlink(name);
+       }
+}
+
+static void end_packfile(void)
+{
+       struct packed_git *old_p = pack_data, *new_p;
+
+       if (object_count) {
+               char *idx_name;
+               int i;
+               struct branch *b;
+               struct tag *t;
+
+               fixup_header_footer();
+               idx_name = keep_pack(create_index());
+
+               /* Register the packfile with core git's machinary. */
+               new_p = add_packed_git(idx_name, strlen(idx_name), 1);
+               if (!new_p)
+                       die("core git rejected index %s", idx_name);
+               new_p->windows = old_p->windows;
+               all_packs[pack_id] = new_p;
+               install_packed_git(new_p);
+
+               /* Print the boundary */
+               if (pack_edges) {
+                       fprintf(pack_edges, "%s:", new_p->pack_name);
+                       for (i = 0; i < branch_table_sz; i++) {
+                               for (b = branch_table[i]; b; b = b->table_next_branch) {
+                                       if (b->pack_id == pack_id)
+                                               fprintf(pack_edges, " %s", sha1_to_hex(b->sha1));
+                               }
+                       }
+                       for (t = first_tag; t; t = t->next_tag) {
+                               if (t->pack_id == pack_id)
+                                       fprintf(pack_edges, " %s", sha1_to_hex(t->sha1));
+                       }
+                       fputc('\n', pack_edges);
+                       fflush(pack_edges);
+               }
+
+               pack_id++;
+       }
+       else
+               unlink(old_p->pack_name);
+       free(old_p);
+
+       /* We can't carry a delta across packfiles. */
+       free(last_blob.data);
+       last_blob.data = NULL;
+       last_blob.len = 0;
+       last_blob.offset = 0;
+       last_blob.depth = 0;
+}
+
+static void cycle_packfile(void)
+{
+       end_packfile();
+       start_packfile();
+}
+
+static size_t encode_header(
+       enum object_type type,
+       size_t size,
+       unsigned char *hdr)
+{
+       int n = 1;
+       unsigned char c;
+
+       if (type < OBJ_COMMIT || type > OBJ_REF_DELTA)
+               die("bad type %d", type);
+
+       c = (type << 4) | (size & 15);
+       size >>= 4;
+       while (size) {
+               *hdr++ = c | 0x80;
+               c = size & 0x7f;
+               size >>= 7;
+               n++;
+       }
+       *hdr = c;
+       return n;
+}
+
+static int store_object(
+       enum object_type type,
+       void *dat,
+       size_t datlen,
+       struct last_object *last,
+       unsigned char *sha1out,
+       uintmax_t mark)
+{
+       void *out, *delta;
+       struct object_entry *e;
+       unsigned char hdr[96];
+       unsigned char sha1[20];
+       unsigned long hdrlen, deltalen;
+       SHA_CTX c;
+       z_stream s;
+
+       hdrlen = sprintf((char*)hdr,"%s %lu", type_names[type],
+               (unsigned long)datlen) + 1;
+       SHA1_Init(&c);
+       SHA1_Update(&c, hdr, hdrlen);
+       SHA1_Update(&c, dat, datlen);
+       SHA1_Final(sha1, &c);
+       if (sha1out)
+               hashcpy(sha1out, sha1);
+
+       e = insert_object(sha1);
+       if (mark)
+               insert_mark(mark, e);
+       if (e->offset) {
+               duplicate_count_by_type[type]++;
+               return 1;
+       }
+
+       if (last && last->data && last->depth < max_depth) {
+               delta = diff_delta(last->data, last->len,
+                       dat, datlen,
+                       &deltalen, 0);
+               if (delta && deltalen >= datlen) {
+                       free(delta);
+                       delta = NULL;
+               }
+       } else
+               delta = NULL;
+
+       memset(&s, 0, sizeof(s));
+       deflateInit(&s, zlib_compression_level);
+       if (delta) {
+               s.next_in = delta;
+               s.avail_in = deltalen;
+       } else {
+               s.next_in = dat;
+               s.avail_in = datlen;
+       }
+       s.avail_out = deflateBound(&s, s.avail_in);
+       s.next_out = out = xmalloc(s.avail_out);
+       while (deflate(&s, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&s);
+
+       /* Determine if we should auto-checkpoint. */
+       if ((pack_size + 60 + s.total_out) > max_packsize
+               || (pack_size + 60 + s.total_out) < pack_size) {
+
+               /* This new object needs to *not* have the current pack_id. */
+               e->pack_id = pack_id + 1;
+               cycle_packfile();
+
+               /* We cannot carry a delta into the new pack. */
+               if (delta) {
+                       free(delta);
+                       delta = NULL;
+
+                       memset(&s, 0, sizeof(s));
+                       deflateInit(&s, zlib_compression_level);
+                       s.next_in = dat;
+                       s.avail_in = datlen;
+                       s.avail_out = deflateBound(&s, s.avail_in);
+                       s.next_out = out = xrealloc(out, s.avail_out);
+                       while (deflate(&s, Z_FINISH) == Z_OK)
+                               /* nothing */;
+                       deflateEnd(&s);
+               }
+       }
+
+       e->type = type;
+       e->pack_id = pack_id;
+       e->offset = pack_size;
+       object_count++;
+       object_count_by_type[type]++;
+
+       if (delta) {
+               unsigned long ofs = e->offset - last->offset;
+               unsigned pos = sizeof(hdr) - 1;
+
+               delta_count_by_type[type]++;
+               last->depth++;
+
+               hdrlen = encode_header(OBJ_OFS_DELTA, deltalen, hdr);
+               write_or_die(pack_data->pack_fd, hdr, hdrlen);
+               pack_size += hdrlen;
+
+               hdr[pos] = ofs & 127;
+               while (ofs >>= 7)
+                       hdr[--pos] = 128 | (--ofs & 127);
+               write_or_die(pack_data->pack_fd, hdr + pos, sizeof(hdr) - pos);
+               pack_size += sizeof(hdr) - pos;
+       } else {
+               if (last)
+                       last->depth = 0;
+               hdrlen = encode_header(type, datlen, hdr);
+               write_or_die(pack_data->pack_fd, hdr, hdrlen);
+               pack_size += hdrlen;
+       }
+
+       write_or_die(pack_data->pack_fd, out, s.total_out);
+       pack_size += s.total_out;
+
+       free(out);
+       free(delta);
+       if (last) {
+               if (!last->no_free)
+                       free(last->data);
+               last->data = dat;
+               last->offset = e->offset;
+               last->len = datlen;
+       }
+       return 0;
+}
+
+static void *gfi_unpack_entry(
+       struct object_entry *oe,
+       unsigned long *sizep)
+{
+       static char type[20];
+       struct packed_git *p = all_packs[oe->pack_id];
+       if (p == pack_data)
+               p->pack_size = pack_size + 20;
+       return unpack_entry(p, oe->offset, type, sizep);
+}
+
+static const char *get_mode(const char *str, uint16_t *modep)
+{
+       unsigned char c;
+       uint16_t mode = 0;
+
+       while ((c = *str++) != ' ') {
+               if (c < '0' || c > '7')
+                       return NULL;
+               mode = (mode << 3) + (c - '0');
+       }
+       *modep = mode;
+       return str;
+}
+
+static void load_tree(struct tree_entry *root)
+{
+       unsigned char* sha1 = root->versions[1].sha1;
+       struct object_entry *myoe;
+       struct tree_content *t;
+       unsigned long size;
+       char *buf;
+       const char *c;
+
+       root->tree = t = new_tree_content(8);
+       if (is_null_sha1(sha1))
+               return;
+
+       myoe = find_object(sha1);
+       if (myoe) {
+               if (myoe->type != OBJ_TREE)
+                       die("Not a tree: %s", sha1_to_hex(sha1));
+               t->delta_depth = 0;
+               buf = gfi_unpack_entry(myoe, &size);
+       } else {
+               char type[20];
+               buf = read_sha1_file(sha1, type, &size);
+               if (!buf || strcmp(type, tree_type))
+                       die("Can't load tree %s", sha1_to_hex(sha1));
+       }
+
+       c = buf;
+       while (c != (buf + size)) {
+               struct tree_entry *e = new_tree_entry();
+
+               if (t->entry_count == t->entry_capacity)
+                       root->tree = t = grow_tree_content(t, 8);
+               t->entries[t->entry_count++] = e;
+
+               e->tree = NULL;
+               c = get_mode(c, &e->versions[1].mode);
+               if (!c)
+                       die("Corrupt mode in %s", sha1_to_hex(sha1));
+               e->versions[0].mode = e->versions[1].mode;
+               e->name = to_atom(c, (unsigned short)strlen(c));
+               c += e->name->str_len + 1;
+               hashcpy(e->versions[0].sha1, (unsigned char*)c);
+               hashcpy(e->versions[1].sha1, (unsigned char*)c);
+               c += 20;
+       }
+       free(buf);
+}
+
+static int tecmp0 (const void *_a, const void *_b)
+{
+       struct tree_entry *a = *((struct tree_entry**)_a);
+       struct tree_entry *b = *((struct tree_entry**)_b);
+       return base_name_compare(
+               a->name->str_dat, a->name->str_len, a->versions[0].mode,
+               b->name->str_dat, b->name->str_len, b->versions[0].mode);
+}
+
+static int tecmp1 (const void *_a, const void *_b)
+{
+       struct tree_entry *a = *((struct tree_entry**)_a);
+       struct tree_entry *b = *((struct tree_entry**)_b);
+       return base_name_compare(
+               a->name->str_dat, a->name->str_len, a->versions[1].mode,
+               b->name->str_dat, b->name->str_len, b->versions[1].mode);
+}
+
+static void mktree(struct tree_content *t,
+       int v,
+       unsigned long *szp,
+       struct dbuf *b)
+{
+       size_t maxlen = 0;
+       unsigned int i;
+       char *c;
+
+       if (!v)
+               qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp0);
+       else
+               qsort(t->entries,t->entry_count,sizeof(t->entries[0]),tecmp1);
+
+       for (i = 0; i < t->entry_count; i++) {
+               if (t->entries[i]->versions[v].mode)
+                       maxlen += t->entries[i]->name->str_len + 34;
+       }
+
+       size_dbuf(b, maxlen);
+       c = b->buffer;
+       for (i = 0; i < t->entry_count; i++) {
+               struct tree_entry *e = t->entries[i];
+               if (!e->versions[v].mode)
+                       continue;
+               c += sprintf(c, "%o", (unsigned int)e->versions[v].mode);
+               *c++ = ' ';
+               strcpy(c, e->name->str_dat);
+               c += e->name->str_len + 1;
+               hashcpy((unsigned char*)c, e->versions[v].sha1);
+               c += 20;
+       }
+       *szp = c - (char*)b->buffer;
+}
+
+static void store_tree(struct tree_entry *root)
+{
+       struct tree_content *t = root->tree;
+       unsigned int i, j, del;
+       unsigned long new_len;
+       struct last_object lo;
+       struct object_entry *le;
+
+       if (!is_null_sha1(root->versions[1].sha1))
+               return;
+
+       for (i = 0; i < t->entry_count; i++) {
+               if (t->entries[i]->tree)
+                       store_tree(t->entries[i]);
+       }
+
+       le = find_object(root->versions[0].sha1);
+       if (!S_ISDIR(root->versions[0].mode)
+               || !le
+               || le->pack_id != pack_id) {
+               lo.data = NULL;
+               lo.depth = 0;
+       } else {
+               mktree(t, 0, &lo.len, &old_tree);
+               lo.data = old_tree.buffer;
+               lo.offset = le->offset;
+               lo.depth = t->delta_depth;
+               lo.no_free = 1;
+       }
+
+       mktree(t, 1, &new_len, &new_tree);
+       store_object(OBJ_TREE, new_tree.buffer, new_len,
+               &lo, root->versions[1].sha1, 0);
+
+       t->delta_depth = lo.depth;
+       for (i = 0, j = 0, del = 0; i < t->entry_count; i++) {
+               struct tree_entry *e = t->entries[i];
+               if (e->versions[1].mode) {
+                       e->versions[0].mode = e->versions[1].mode;
+                       hashcpy(e->versions[0].sha1, e->versions[1].sha1);
+                       t->entries[j++] = e;
+               } else {
+                       release_tree_entry(e);
+                       del++;
+               }
+       }
+       t->entry_count -= del;
+}
+
+static int tree_content_set(
+       struct tree_entry *root,
+       const char *p,
+       const unsigned char *sha1,
+       const uint16_t mode)
+{
+       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) {
+                               if (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) {
+                                       release_tree_content_recursive(e->tree);
+                                       e->tree = NULL;
+                               }
+                               hashclr(root->versions[1].sha1);
+                               return 1;
+                       }
+                       if (!S_ISDIR(e->versions[1].mode)) {
+                               e->tree = new_tree_content(8);
+                               e->versions[1].mode = S_IFDIR;
+                       }
+                       if (!e->tree)
+                               load_tree(e);
+                       if (tree_content_set(e, slash1 + 1, sha1, mode)) {
+                               hashclr(root->versions[1].sha1);
+                               return 1;
+                       }
+                       return 0;
+               }
+       }
+
+       if (t->entry_count == t->entry_capacity)
+               root->tree = t = grow_tree_content(t, 8);
+       e = new_tree_entry();
+       e->name = to_atom(p, (unsigned short)n);
+       e->versions[0].mode = 0;
+       hashclr(e->versions[0].sha1);
+       t->entries[t->entry_count++] = e;
+       if (slash1) {
+               e->tree = new_tree_content(8);
+               e->versions[1].mode = S_IFDIR;
+               tree_content_set(e, slash1 + 1, sha1, mode);
+       } else {
+               e->tree = NULL;
+               e->versions[1].mode = mode;
+               hashcpy(e->versions[1].sha1, sha1);
+       }
+       hashclr(root->versions[1].sha1);
+       return 1;
+}
+
+static int tree_content_remove(struct tree_entry *root, const char *p)
+{
+       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 || !S_ISDIR(e->versions[1].mode))
+                               goto del_entry;
+                       if (!e->tree)
+                               load_tree(e);
+                       if (tree_content_remove(e, slash1 + 1)) {
+                               for (n = 0; n < e->tree->entry_count; n++) {
+                                       if (e->tree->entries[n]->versions[1].mode) {
+                                               hashclr(root->versions[1].sha1);
+                                               return 1;
+                                       }
+                               }
+                               goto del_entry;
+                       }
+                       return 0;
+               }
+       }
+       return 0;
+
+del_entry:
+       if (e->tree) {
+               release_tree_content_recursive(e->tree);
+               e->tree = NULL;
+       }
+       e->versions[1].mode = 0;
+       hashclr(e->versions[1].sha1);
+       hashclr(root->versions[1].sha1);
+       return 1;
+}
+
+static int update_branch(struct branch *b)
+{
+       static const char *msg = "fast-import";
+       struct ref_lock *lock;
+       unsigned char old_sha1[20];
+
+       if (read_ref(b->name, old_sha1))
+               hashclr(old_sha1);
+       lock = lock_any_ref_for_update(b->name, old_sha1);
+       if (!lock)
+               return error("Unable to lock %s", b->name);
+       if (!force_update && !is_null_sha1(old_sha1)) {
+               struct commit *old_cmit, *new_cmit;
+
+               old_cmit = lookup_commit_reference_gently(old_sha1, 0);
+               new_cmit = lookup_commit_reference_gently(b->sha1, 0);
+               if (!old_cmit || !new_cmit) {
+                       unlock_ref(lock);
+                       return error("Branch %s is missing commits.", b->name);
+               }
+
+               if (!in_merge_bases(old_cmit, &new_cmit, 1)) {
+                       unlock_ref(lock);
+                       warn("Not updating %s"
+                               " (new tip %s does not contain %s)",
+                               b->name, sha1_to_hex(b->sha1), sha1_to_hex(old_sha1));
+                       return -1;
+               }
+       }
+       if (write_ref_sha1(lock, b->sha1, msg) < 0)
+               return error("Unable to update %s", b->name);
+       return 0;
+}
+
+static void dump_branches(void)
+{
+       unsigned int i;
+       struct branch *b;
+
+       for (i = 0; i < branch_table_sz; i++) {
+               for (b = branch_table[i]; b; b = b->table_next_branch)
+                       failure |= update_branch(b);
+       }
+}
+
+static void dump_tags(void)
+{
+       static const char *msg = "fast-import";
+       struct tag *t;
+       struct ref_lock *lock;
+       char ref_name[PATH_MAX];
+
+       for (t = first_tag; t; t = t->next_tag) {
+               sprintf(ref_name, "tags/%s", t->name);
+               lock = lock_ref_sha1(ref_name, NULL);
+               if (!lock || write_ref_sha1(lock, t->sha1, msg) < 0)
+                       failure |= error("Unable to update %s", ref_name);
+       }
+}
+
+static void dump_marks_helper(FILE *f,
+       uintmax_t base,
+       struct mark_set *m)
+{
+       uintmax_t k;
+       if (m->shift) {
+               for (k = 0; k < 1024; k++) {
+                       if (m->data.sets[k])
+                               dump_marks_helper(f, (base + k) << m->shift,
+                                       m->data.sets[k]);
+               }
+       } else {
+               for (k = 0; k < 1024; k++) {
+                       if (m->data.marked[k])
+                               fprintf(f, ":%ju %s\n", base + k,
+                                       sha1_to_hex(m->data.marked[k]->sha1));
+               }
+       }
+}
+
+static void dump_marks(void)
+{
+       if (mark_file)
+       {
+               FILE *f = fopen(mark_file, "w");
+               if (f) {
+                       dump_marks_helper(f, 0, marks);
+                       fclose(f);
+               } else
+                       failure |= error("Unable to write marks file %s: %s",
+                               mark_file, strerror(errno));
+       }
+}
+
+static void read_next_command(void)
+{
+       read_line(&command_buf, stdin, '\n');
+}
+
+static void cmd_mark(void)
+{
+       if (!strncmp("mark :", command_buf.buf, 6)) {
+               next_mark = strtoumax(command_buf.buf + 6, NULL, 10);
+               read_next_command();
+       }
+       else
+               next_mark = 0;
+}
+
+static void *cmd_data (size_t *size)
+{
+       size_t length;
+       char *buffer;
+
+       if (strncmp("data ", command_buf.buf, 5))
+               die("Expected 'data n' command, found: %s", command_buf.buf);
+
+       if (!strncmp("<<", command_buf.buf + 5, 2)) {
+               char *term = xstrdup(command_buf.buf + 5 + 2);
+               size_t sz = 8192, term_len = command_buf.len - 5 - 2;
+               length = 0;
+               buffer = xmalloc(sz);
+               for (;;) {
+                       read_next_command();
+                       if (command_buf.eof)
+                               die("EOF in data (terminator '%s' not found)", term);
+                       if (term_len == command_buf.len
+                               && !strcmp(term, command_buf.buf))
+                               break;
+                       if (sz < (length + command_buf.len)) {
+                               sz = sz * 3 / 2 + 16;
+                               if (sz < (length + command_buf.len))
+                                       sz = length + command_buf.len;
+                               buffer = xrealloc(buffer, sz);
+                       }
+                       memcpy(buffer + length,
+                               command_buf.buf,
+                               command_buf.len - 1);
+                       length += command_buf.len - 1;
+                       buffer[length++] = '\n';
+               }
+               free(term);
+       }
+       else {
+               size_t n = 0;
+               length = strtoul(command_buf.buf + 5, NULL, 10);
+               buffer = xmalloc(length);
+               while (n < length) {
+                       size_t s = fread(buffer + n, 1, length - n, stdin);
+                       if (!s && feof(stdin))
+                               die("EOF in data (%lu bytes remaining)",
+                                       (unsigned long)(length - n));
+                       n += s;
+               }
+       }
+
+       if (fgetc(stdin) != '\n')
+               die("An lf did not trail the binary data as expected.");
+
+       *size = length;
+       return buffer;
+}
+
+static int validate_raw_date(const char *src, char *result, int maxlen)
+{
+       const char *orig_src = src;
+       char *endp, sign;
+
+       strtoul(src, &endp, 10);
+       if (endp == src || *endp != ' ')
+               return -1;
+
+       src = endp + 1;
+       if (*src != '-' && *src != '+')
+               return -1;
+       sign = *src;
+
+       strtoul(src + 1, &endp, 10);
+       if (endp == src || *endp || (endp - orig_src) >= maxlen)
+               return -1;
+
+       strcpy(result, orig_src);
+       return 0;
+}
+
+static char *parse_ident(const char *buf)
+{
+       const char *gt;
+       size_t name_len;
+       char *ident;
+
+       gt = strrchr(buf, '>');
+       if (!gt)
+               die("Missing > in ident string: %s", buf);
+       gt++;
+       if (*gt != ' ')
+               die("Missing space after > in ident string: %s", buf);
+       gt++;
+       name_len = gt - buf;
+       ident = xmalloc(name_len + 24);
+       strncpy(ident, buf, name_len);
+
+       switch (whenspec) {
+       case WHENSPEC_RAW:
+               if (validate_raw_date(gt, ident + name_len, 24) < 0)
+                       die("Invalid raw date \"%s\" in ident: %s", gt, buf);
+               break;
+       case WHENSPEC_RFC2822:
+               if (parse_date(gt, ident + name_len, 24) < 0)
+                       die("Invalid rfc2822 date \"%s\" in ident: %s", gt, buf);
+               break;
+       case WHENSPEC_NOW:
+               if (strcmp("now", gt))
+                       die("Date in ident must be 'now': %s", buf);
+               datestamp(ident + name_len, 24);
+               break;
+       }
+
+       return ident;
+}
+
+static void cmd_new_blob(void)
+{
+       size_t l;
+       void *d;
+
+       read_next_command();
+       cmd_mark();
+       d = cmd_data(&l);
+
+       if (store_object(OBJ_BLOB, d, l, &last_blob, NULL, next_mark))
+               free(d);
+}
+
+static void unload_one_branch(void)
+{
+       while (cur_active_branches
+               && cur_active_branches >= max_active_branches) {
+               unsigned long min_commit = ULONG_MAX;
+               struct branch *e, *l = NULL, *p = NULL;
+
+               for (e = active_branches; e; e = e->active_next_branch) {
+                       if (e->last_commit < min_commit) {
+                               p = l;
+                               min_commit = e->last_commit;
+                       }
+                       l = e;
+               }
+
+               if (p) {
+                       e = p->active_next_branch;
+                       p->active_next_branch = e->active_next_branch;
+               } else {
+                       e = active_branches;
+                       active_branches = e->active_next_branch;
+               }
+               e->active_next_branch = NULL;
+               if (e->branch_tree.tree) {
+                       release_tree_content_recursive(e->branch_tree.tree);
+                       e->branch_tree.tree = NULL;
+               }
+               cur_active_branches--;
+       }
+}
+
+static void load_branch(struct branch *b)
+{
+       load_tree(&b->branch_tree);
+       b->active_next_branch = active_branches;
+       active_branches = b;
+       cur_active_branches++;
+       branch_load_count++;
+}
+
+static void file_change_m(struct branch *b)
+{
+       const char *p = command_buf.buf + 2;
+       char *p_uq;
+       const char *endp;
+       struct object_entry *oe = oe;
+       unsigned char sha1[20];
+       uint16_t mode, inline_data = 0;
+       char type[20];
+
+       p = get_mode(p, &mode);
+       if (!p)
+               die("Corrupt mode: %s", command_buf.buf);
+       switch (mode) {
+       case S_IFREG | 0644:
+       case S_IFREG | 0755:
+       case S_IFLNK:
+       case 0644:
+       case 0755:
+               /* ok */
+               break;
+       default:
+               die("Corrupt mode: %s", command_buf.buf);
+       }
+
+       if (*p == ':') {
+               char *x;
+               oe = find_mark(strtoumax(p + 1, &x, 10));
+               hashcpy(sha1, oe->sha1);
+               p = x;
+       } else if (!strncmp("inline", p, 6)) {
+               inline_data = 1;
+               p += 6;
+       } else {
+               if (get_sha1_hex(p, sha1))
+                       die("Invalid SHA1: %s", command_buf.buf);
+               oe = find_object(sha1);
+               p += 40;
+       }
+       if (*p++ != ' ')
+               die("Missing space after SHA1: %s", command_buf.buf);
+
+       p_uq = unquote_c_style(p, &endp);
+       if (p_uq) {
+               if (*endp)
+                       die("Garbage after path in: %s", command_buf.buf);
+               p = p_uq;
+       }
+
+       if (inline_data) {
+               size_t l;
+               void *d;
+               if (!p_uq)
+                       p = p_uq = xstrdup(p);
+               read_next_command();
+               d = cmd_data(&l);
+               if (store_object(OBJ_BLOB, d, l, &last_blob, sha1, 0))
+                       free(d);
+       } else if (oe) {
+               if (oe->type != OBJ_BLOB)
+                       die("Not a blob (actually a %s): %s",
+                               command_buf.buf, type_names[oe->type]);
+       } else {
+               if (sha1_object_info(sha1, type, NULL))
+                       die("Blob not found: %s", command_buf.buf);
+               if (strcmp(blob_type, type))
+                       die("Not a blob (actually a %s): %s",
+                               command_buf.buf, type);
+       }
+
+       tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode);
+       free(p_uq);
+}
+
+static void file_change_d(struct branch *b)
+{
+       const char *p = command_buf.buf + 2;
+       char *p_uq;
+       const char *endp;
+
+       p_uq = unquote_c_style(p, &endp);
+       if (p_uq) {
+               if (*endp)
+                       die("Garbage after path in: %s", command_buf.buf);
+               p = p_uq;
+       }
+       tree_content_remove(&b->branch_tree, p);
+       free(p_uq);
+}
+
+static void file_change_deleteall(struct branch *b)
+{
+       release_tree_content_recursive(b->branch_tree.tree);
+       hashclr(b->branch_tree.versions[0].sha1);
+       hashclr(b->branch_tree.versions[1].sha1);
+       load_tree(&b->branch_tree);
+}
+
+static void cmd_from(struct branch *b)
+{
+       const char *from;
+       struct branch *s;
+
+       if (strncmp("from ", command_buf.buf, 5))
+               return;
+
+       if (b->branch_tree.tree) {
+               release_tree_content_recursive(b->branch_tree.tree);
+               b->branch_tree.tree = NULL;
+       }
+
+       from = strchr(command_buf.buf, ' ') + 1;
+       s = lookup_branch(from);
+       if (b == s)
+               die("Can't create a branch from itself: %s", b->name);
+       else if (s) {
+               unsigned char *t = s->branch_tree.versions[1].sha1;
+               hashcpy(b->sha1, s->sha1);
+               hashcpy(b->branch_tree.versions[0].sha1, t);
+               hashcpy(b->branch_tree.versions[1].sha1, t);
+       } else if (*from == ':') {
+               uintmax_t idnum = strtoumax(from + 1, NULL, 10);
+               struct object_entry *oe = find_mark(idnum);
+               unsigned long size;
+               char *buf;
+               if (oe->type != OBJ_COMMIT)
+                       die("Mark :%ju not a commit", idnum);
+               hashcpy(b->sha1, oe->sha1);
+               buf = gfi_unpack_entry(oe, &size);
+               if (!buf || size < 46)
+                       die("Not a valid commit: %s", from);
+               if (memcmp("tree ", buf, 5)
+                       || get_sha1_hex(buf + 5, b->branch_tree.versions[1].sha1))
+                       die("The commit %s is corrupt", sha1_to_hex(b->sha1));
+               free(buf);
+               hashcpy(b->branch_tree.versions[0].sha1,
+                       b->branch_tree.versions[1].sha1);
+       } else if (!get_sha1(from, b->sha1)) {
+               if (is_null_sha1(b->sha1)) {
+                       hashclr(b->branch_tree.versions[0].sha1);
+                       hashclr(b->branch_tree.versions[1].sha1);
+               } else {
+                       unsigned long size;
+                       char *buf;
+
+                       buf = read_object_with_reference(b->sha1,
+                               type_names[OBJ_COMMIT], &size, b->sha1);
+                       if (!buf || size < 46)
+                               die("Not a valid commit: %s", from);
+                       if (memcmp("tree ", buf, 5)
+                               || get_sha1_hex(buf + 5, b->branch_tree.versions[1].sha1))
+                               die("The commit %s is corrupt", sha1_to_hex(b->sha1));
+                       free(buf);
+                       hashcpy(b->branch_tree.versions[0].sha1,
+                               b->branch_tree.versions[1].sha1);
+               }
+       } else
+               die("Invalid ref name or SHA1 expression: %s", from);
+
+       read_next_command();
+}
+
+static struct hash_list *cmd_merge(unsigned int *count)
+{
+       struct hash_list *list = NULL, *n, *e = e;
+       const char *from;
+       struct branch *s;
+
+       *count = 0;
+       while (!strncmp("merge ", command_buf.buf, 6)) {
+               from = strchr(command_buf.buf, ' ') + 1;
+               n = xmalloc(sizeof(*n));
+               s = lookup_branch(from);
+               if (s)
+                       hashcpy(n->sha1, s->sha1);
+               else if (*from == ':') {
+                       uintmax_t idnum = strtoumax(from + 1, NULL, 10);
+                       struct object_entry *oe = find_mark(idnum);
+                       if (oe->type != OBJ_COMMIT)
+                               die("Mark :%ju not a commit", idnum);
+                       hashcpy(n->sha1, oe->sha1);
+               } else if (get_sha1(from, n->sha1))
+                       die("Invalid ref name or SHA1 expression: %s", from);
+
+               n->next = NULL;
+               if (list)
+                       e->next = n;
+               else
+                       list = n;
+               e = n;
+               (*count)++;
+               read_next_command();
+       }
+       return list;
+}
+
+static void cmd_new_commit(void)
+{
+       struct branch *b;
+       void *msg;
+       size_t msglen;
+       char *sp;
+       char *author = NULL;
+       char *committer = NULL;
+       struct hash_list *merge_list = NULL;
+       unsigned int merge_count;
+
+       /* Obtain the branch name from the rest of our command */
+       sp = strchr(command_buf.buf, ' ') + 1;
+       b = lookup_branch(sp);
+       if (!b)
+               b = new_branch(sp);
+
+       read_next_command();
+       cmd_mark();
+       if (!strncmp("author ", command_buf.buf, 7)) {
+               author = parse_ident(command_buf.buf + 7);
+               read_next_command();
+       }
+       if (!strncmp("committer ", command_buf.buf, 10)) {
+               committer = parse_ident(command_buf.buf + 10);
+               read_next_command();
+       }
+       if (!committer)
+               die("Expected committer but didn't get one");
+       msg = cmd_data(&msglen);
+       read_next_command();
+       cmd_from(b);
+       merge_list = cmd_merge(&merge_count);
+
+       /* ensure the branch is active/loaded */
+       if (!b->branch_tree.tree || !max_active_branches) {
+               unload_one_branch();
+               load_branch(b);
+       }
+
+       /* file_change* */
+       for (;;) {
+               if (1 == command_buf.len)
+                       break;
+               else if (!strncmp("M ", command_buf.buf, 2))
+                       file_change_m(b);
+               else if (!strncmp("D ", command_buf.buf, 2))
+                       file_change_d(b);
+               else if (!strcmp("deleteall", command_buf.buf))
+                       file_change_deleteall(b);
+               else
+                       die("Unsupported file_change: %s", command_buf.buf);
+               read_next_command();
+       }
+
+       /* build the tree and the commit */
+       store_tree(&b->branch_tree);
+       hashcpy(b->branch_tree.versions[0].sha1,
+               b->branch_tree.versions[1].sha1);
+       size_dbuf(&new_data, 114 + msglen
+               + merge_count * 49
+               + (author
+                       ? strlen(author) + strlen(committer)
+                       : 2 * strlen(committer)));
+       sp = new_data.buffer;
+       sp += sprintf(sp, "tree %s\n",
+               sha1_to_hex(b->branch_tree.versions[1].sha1));
+       if (!is_null_sha1(b->sha1))
+               sp += sprintf(sp, "parent %s\n", sha1_to_hex(b->sha1));
+       while (merge_list) {
+               struct hash_list *next = merge_list->next;
+               sp += sprintf(sp, "parent %s\n", sha1_to_hex(merge_list->sha1));
+               free(merge_list);
+               merge_list = next;
+       }
+       sp += sprintf(sp, "author %s\n", author ? author : committer);
+       sp += sprintf(sp, "committer %s\n", committer);
+       *sp++ = '\n';
+       memcpy(sp, msg, msglen);
+       sp += msglen;
+       free(author);
+       free(committer);
+       free(msg);
+
+       if (!store_object(OBJ_COMMIT,
+               new_data.buffer, sp - (char*)new_data.buffer,
+               NULL, b->sha1, next_mark))
+               b->pack_id = pack_id;
+       b->last_commit = object_count_by_type[OBJ_COMMIT];
+}
+
+static void cmd_new_tag(void)
+{
+       char *sp;
+       const char *from;
+       char *tagger;
+       struct branch *s;
+       void *msg;
+       size_t msglen;
+       struct tag *t;
+       uintmax_t from_mark = 0;
+       unsigned char sha1[20];
+
+       /* Obtain the new tag name from the rest of our command */
+       sp = strchr(command_buf.buf, ' ') + 1;
+       t = pool_alloc(sizeof(struct tag));
+       t->next_tag = NULL;
+       t->name = pool_strdup(sp);
+       if (last_tag)
+               last_tag->next_tag = t;
+       else
+               first_tag = t;
+       last_tag = t;
+       read_next_command();
+
+       /* from ... */
+       if (strncmp("from ", command_buf.buf, 5))
+               die("Expected from command, got %s", command_buf.buf);
+       from = strchr(command_buf.buf, ' ') + 1;
+       s = lookup_branch(from);
+       if (s) {
+               hashcpy(sha1, s->sha1);
+       } else if (*from == ':') {
+               struct object_entry *oe;
+               from_mark = strtoumax(from + 1, NULL, 10);
+               oe = find_mark(from_mark);
+               if (oe->type != OBJ_COMMIT)
+                       die("Mark :%ju not a commit", from_mark);
+               hashcpy(sha1, oe->sha1);
+       } else if (!get_sha1(from, sha1)) {
+               unsigned long size;
+               char *buf;
+
+               buf = read_object_with_reference(sha1,
+                       type_names[OBJ_COMMIT], &size, sha1);
+               if (!buf || size < 46)
+                       die("Not a valid commit: %s", from);
+               free(buf);
+       } else
+               die("Invalid ref name or SHA1 expression: %s", from);
+       read_next_command();
+
+       /* tagger ... */
+       if (strncmp("tagger ", command_buf.buf, 7))
+               die("Expected tagger command, got %s", command_buf.buf);
+       tagger = parse_ident(command_buf.buf + 7);
+
+       /* tag payload/message */
+       read_next_command();
+       msg = cmd_data(&msglen);
+
+       /* build the tag object */
+       size_dbuf(&new_data, 67+strlen(t->name)+strlen(tagger)+msglen);
+       sp = new_data.buffer;
+       sp += sprintf(sp, "object %s\n", sha1_to_hex(sha1));
+       sp += sprintf(sp, "type %s\n", type_names[OBJ_COMMIT]);
+       sp += sprintf(sp, "tag %s\n", t->name);
+       sp += sprintf(sp, "tagger %s\n", tagger);
+       *sp++ = '\n';
+       memcpy(sp, msg, msglen);
+       sp += msglen;
+       free(tagger);
+       free(msg);
+
+       if (store_object(OBJ_TAG, new_data.buffer,
+               sp - (char*)new_data.buffer,
+               NULL, t->sha1, 0))
+               t->pack_id = MAX_PACK_ID;
+       else
+               t->pack_id = pack_id;
+}
+
+static void cmd_reset_branch(void)
+{
+       struct branch *b;
+       char *sp;
+
+       /* Obtain the branch name from the rest of our command */
+       sp = strchr(command_buf.buf, ' ') + 1;
+       b = lookup_branch(sp);
+       if (b) {
+               hashclr(b->sha1);
+               hashclr(b->branch_tree.versions[0].sha1);
+               hashclr(b->branch_tree.versions[1].sha1);
+               if (b->branch_tree.tree) {
+                       release_tree_content_recursive(b->branch_tree.tree);
+                       b->branch_tree.tree = NULL;
+               }
+       }
+       else
+               b = new_branch(sp);
+       read_next_command();
+       cmd_from(b);
+}
+
+static void cmd_checkpoint(void)
+{
+       if (object_count) {
+               cycle_packfile();
+               dump_branches();
+               dump_tags();
+               dump_marks();
+       }
+       read_next_command();
+}
+
+static const char fast_import_usage[] =
+"git-fast-import [--date-format=f] [--max-pack-size=n] [--depth=n] [--active-branches=n] [--export-marks=marks.file]";
+
+int main(int argc, const char **argv)
+{
+       int i, show_stats = 1;
+
+       git_config(git_default_config);
+
+       for (i = 1; i < argc; i++) {
+               const char *a = argv[i];
+
+               if (*a != '-' || !strcmp(a, "--"))
+                       break;
+               else if (!strncmp(a, "--date-format=", 14)) {
+                       const char *fmt = a + 14;
+                       if (!strcmp(fmt, "raw"))
+                               whenspec = WHENSPEC_RAW;
+                       else if (!strcmp(fmt, "rfc2822"))
+                               whenspec = WHENSPEC_RFC2822;
+                       else if (!strcmp(fmt, "now"))
+                               whenspec = WHENSPEC_NOW;
+                       else
+                               die("unknown --date-format argument %s", fmt);
+               }
+               else if (!strncmp(a, "--max-pack-size=", 16))
+                       max_packsize = strtoumax(a + 16, NULL, 0) * 1024 * 1024;
+               else if (!strncmp(a, "--depth=", 8))
+                       max_depth = strtoul(a + 8, NULL, 0);
+               else if (!strncmp(a, "--active-branches=", 18))
+                       max_active_branches = strtoul(a + 18, NULL, 0);
+               else if (!strncmp(a, "--export-marks=", 15))
+                       mark_file = a + 15;
+               else if (!strncmp(a, "--export-pack-edges=", 20)) {
+                       if (pack_edges)
+                               fclose(pack_edges);
+                       pack_edges = fopen(a + 20, "a");
+                       if (!pack_edges)
+                               die("Cannot open %s: %s", a + 20, strerror(errno));
+               } else if (!strcmp(a, "--force"))
+                       force_update = 1;
+               else if (!strcmp(a, "--quiet"))
+                       show_stats = 0;
+               else if (!strcmp(a, "--stats"))
+                       show_stats = 1;
+               else
+                       die("unknown option %s", a);
+       }
+       if (i != argc)
+               usage(fast_import_usage);
+
+       alloc_objects(object_entry_alloc);
+       strbuf_init(&command_buf);
+
+       atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*));
+       branch_table = xcalloc(branch_table_sz, sizeof(struct branch*));
+       avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*));
+       marks = pool_calloc(1, sizeof(struct mark_set));
+
+       start_packfile();
+       for (;;) {
+               read_next_command();
+               if (command_buf.eof)
+                       break;
+               else if (!strcmp("blob", command_buf.buf))
+                       cmd_new_blob();
+               else if (!strncmp("commit ", command_buf.buf, 7))
+                       cmd_new_commit();
+               else if (!strncmp("tag ", command_buf.buf, 4))
+                       cmd_new_tag();
+               else if (!strncmp("reset ", command_buf.buf, 6))
+                       cmd_reset_branch();
+               else if (!strcmp("checkpoint", command_buf.buf))
+                       cmd_checkpoint();
+               else
+                       die("Unsupported command: %s", command_buf.buf);
+       }
+       end_packfile();
+
+       dump_branches();
+       dump_tags();
+       unkeep_all_packs();
+       dump_marks();
+
+       if (pack_edges)
+               fclose(pack_edges);
+
+       if (show_stats) {
+               uintmax_t total_count = 0, duplicate_count = 0;
+               for (i = 0; i < ARRAY_SIZE(object_count_by_type); i++)
+                       total_count += object_count_by_type[i];
+               for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++)
+                       duplicate_count += duplicate_count_by_type[i];
+
+               fprintf(stderr, "%s statistics:\n", argv[0]);
+               fprintf(stderr, "---------------------------------------------------------------------\n");
+               fprintf(stderr, "Alloc'd objects: %10ju\n", alloc_count);
+               fprintf(stderr, "Total objects:   %10ju (%10ju duplicates                  )\n", total_count, duplicate_count);
+               fprintf(stderr, "      blobs  :   %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB]);
+               fprintf(stderr, "      trees  :   %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE]);
+               fprintf(stderr, "      commits:   %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT]);
+               fprintf(stderr, "      tags   :   %10ju (%10ju duplicates %10ju deltas)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG]);
+               fprintf(stderr, "Total branches:  %10lu (%10lu loads     )\n", branch_count, branch_load_count);
+               fprintf(stderr, "      marks:     %10ju (%10ju unique    )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count);
+               fprintf(stderr, "      atoms:     %10u\n", atom_cnt);
+               fprintf(stderr, "Memory total:    %10ju KiB\n", (total_allocd + alloc_count*sizeof(struct object_entry))/1024);
+               fprintf(stderr, "       pools:    %10lu KiB\n", (unsigned long)(total_allocd/1024));
+               fprintf(stderr, "     objects:    %10ju KiB\n", (alloc_count*sizeof(struct object_entry))/1024);
+               fprintf(stderr, "---------------------------------------------------------------------\n");
+               pack_report();
+               fprintf(stderr, "---------------------------------------------------------------------\n");
+               fprintf(stderr, "\n");
+       }
+
+       return failure ? 1 : 0;
+}
index 0a169dce8574102a03c978bb24f8d0664f1fc9b6..c7871067640f65d416500191792747cb451ffd32 100644 (file)
@@ -4,16 +4,20 @@
 #include "commit.h"
 #include "tag.h"
 #include "exec_cmd.h"
+#include "pack.h"
 #include "sideband.h"
-#include <sys/wait.h>
 
 static int keep_pack;
+static int transfer_unpack_limit = -1;
+static int fetch_unpack_limit = -1;
+static int unpack_limit = 100;
 static int quiet;
 static int verbose;
 static int fetch_all;
+static int depth;
 static const char fetch_pack_usage[] =
-"git-fetch-pack [--all] [-q] [-v] [-k] [--thin] [--exec=upload-pack] [host:]directory <refs>...";
-static const char *exec = "git-upload-pack";
+"git-fetch-pack [--all] [--quiet|-q] [--keep|-k] [--thin] [--upload-pack=<git-upload-pack>] [--depth=<n>] [-v] [<host>:]<directory> [<refs>...]";
+static const char *uploadpack = "git-upload-pack";
 
 #define COMPLETE       (1U << 0)
 #define COMMON         (1U << 1)
@@ -180,10 +184,41 @@ static int find_common(int fd[2], unsigned char *result_sha1,
                        packet_write(fd[1], "want %s\n", sha1_to_hex(remote));
                fetching++;
        }
+       if (is_repository_shallow())
+               write_shallow_commits(fd[1], 1);
+       if (depth > 0)
+               packet_write(fd[1], "deepen %d", depth);
        packet_flush(fd[1]);
        if (!fetching)
                return 1;
 
+       if (depth > 0) {
+               char line[1024];
+               unsigned char sha1[20];
+               int len;
+
+               while ((len = packet_read_line(fd[0], line, sizeof(line)))) {
+                       if (!strncmp("shallow ", line, 8)) {
+                               if (get_sha1_hex(line + 8, sha1))
+                                       die("invalid shallow line: %s", line);
+                               register_shallow(sha1);
+                               continue;
+                       }
+                       if (!strncmp("unshallow ", line, 10)) {
+                               if (get_sha1_hex(line + 10, sha1))
+                                       die("invalid unshallow line: %s", line);
+                               if (!lookup_object(sha1))
+                                       die("object not found: %s", line);
+                               /* make sure that it is parsed as shallow */
+                               parse_object(sha1);
+                               if (unregister_shallow(sha1))
+                                       die("no shallow found: %s", line);
+                               continue;
+                       }
+                       die("expected shallow/unshallow, got %s", line);
+               }
+       }
+
        flushes = 0;
        retval = -1;
        while ((sha1 = get_rev())) {
@@ -310,7 +345,8 @@ static void filter_refs(struct ref **refs, int nr_match, char **match)
                if (!memcmp(ref->name, "refs/", 5) &&
                    check_ref_format(ref->name + 5))
                        ; /* trash */
-               else if (fetch_all) {
+               else if (fetch_all &&
+                        (!depth || strncmp(ref->name, "refs/tags/", 10) )) {
                        *newtail = ref;
                        ref->next = NULL;
                        newtail = &ref->next;
@@ -369,9 +405,11 @@ static int everything_local(struct ref **refs, int nr_match, char **match)
                }
        }
 
-       for_each_ref(mark_complete, NULL);
-       if (cutoff)
-               mark_recent_complete_commits(cutoff);
+       if (!depth) {
+               for_each_ref(mark_complete, NULL);
+               if (cutoff)
+                       mark_recent_complete_commits(cutoff);
+       }
 
        /*
         * Mark all complete remote refs as common refs.
@@ -452,13 +490,58 @@ static pid_t setup_sideband(int fd[2], int xd[2])
        return side_pid;
 }
 
-static int get_pack(int xd[2], const char **argv)
+static int get_pack(int xd[2])
 {
        int status;
        pid_t pid, side_pid;
        int fd[2];
+       const char *argv[20];
+       char keep_arg[256];
+       char hdr_arg[256];
+       const char **av;
+       int do_keep = keep_pack;
 
        side_pid = setup_sideband(fd, xd);
+
+       av = argv;
+       *hdr_arg = 0;
+       if (unpack_limit) {
+               struct pack_header header;
+
+               if (read_pack_header(fd[0], &header))
+                       die("protocol error: bad pack header");
+               snprintf(hdr_arg, sizeof(hdr_arg), "--pack_header=%u,%u",
+                        ntohl(header.hdr_version), ntohl(header.hdr_entries));
+               if (ntohl(header.hdr_entries) < unpack_limit)
+                       do_keep = 0;
+               else
+                       do_keep = 1;
+       }
+
+       if (do_keep) {
+               *av++ = "index-pack";
+               *av++ = "--stdin";
+               if (!quiet)
+                       *av++ = "-v";
+               if (use_thin_pack)
+                       *av++ = "--fix-thin";
+               if (keep_pack > 1 || unpack_limit) {
+                       int s = sprintf(keep_arg,
+                                       "--keep=fetch-pack %d on ", getpid());
+                       if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
+                               strcpy(keep_arg + s, "localhost");
+                       *av++ = keep_arg;
+               }
+       }
+       else {
+               *av++ = "unpack-objects";
+               if (quiet)
+                       *av++ = "-q";
+       }
+       if (*hdr_arg)
+               *av++ = hdr_arg;
+       *av++ = NULL;
+
        pid = fork();
        if (pid < 0)
                die("fetch-pack: unable to fork off %s", argv[0]);
@@ -488,41 +571,14 @@ static int get_pack(int xd[2], const char **argv)
        die("%s died of unnatural causes %d", argv[0], status);
 }
 
-static int explode_rx_pack(int xd[2])
-{
-       const char *argv[3] = { "unpack-objects", quiet ? "-q" : NULL, NULL };
-       return get_pack(xd, argv);
-}
-
-static int keep_rx_pack(int xd[2])
-{
-       const char *argv[6];
-       char keep_arg[256];
-       int n = 0;
-
-       argv[n++] = "index-pack";
-       argv[n++] = "--stdin";
-       if (!quiet)
-               argv[n++] = "-v";
-       if (use_thin_pack)
-               argv[n++] = "--fix-thin";
-       if (keep_pack > 1) {
-               int s = sprintf(keep_arg, "--keep=fetch-pack %i on ", getpid());
-               if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
-                       strcpy(keep_arg + s, "localhost");
-               argv[n++] = keep_arg;
-       }
-       argv[n] = NULL;
-       return get_pack(xd, argv);
-}
-
 static int fetch_pack(int fd[2], int nr_match, char **match)
 {
        struct ref *ref;
        unsigned char sha1[20];
-       int status;
 
        get_remote_heads(fd[0], &ref, 0, NULL, 0);
+       if (is_repository_shallow() && !server_supports("shallow"))
+               die("Server does not support shallow clients");
        if (server_supports("multi_ack")) {
                if (verbose)
                        fprintf(stderr, "Server supports multi_ack\n");
@@ -553,8 +609,7 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
                         */
                        fprintf(stderr, "warning: no common commits\n");
 
-       status = (keep_pack) ? keep_rx_pack(fd) : explode_rx_pack(fd);
-       if (status)
+       if (get_pack(fd))
                die("git-fetch-pack: fetch failed.");
 
  all_done:
@@ -566,14 +621,61 @@ static int fetch_pack(int fd[2], int nr_match, char **match)
        return 0;
 }
 
+static int remove_duplicates(int nr_heads, char **heads)
+{
+       int src, dst;
+
+       for (src = dst = 0; src < nr_heads; src++) {
+               /* If heads[src] is different from any of
+                * heads[0..dst], push it in.
+                */
+               int i;
+               for (i = 0; i < dst; i++) {
+                       if (!strcmp(heads[i], heads[src]))
+                               break;
+               }
+               if (i < dst)
+                       continue;
+               if (src != dst)
+                       heads[dst] = heads[src];
+               dst++;
+       }
+       heads[dst] = 0;
+       return dst;
+}
+
+static int fetch_pack_config(const char *var, const char *value)
+{
+       if (strcmp(var, "fetch.unpacklimit") == 0) {
+               fetch_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       if (strcmp(var, "transfer.unpacklimit") == 0) {
+               transfer_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value);
+}
+
+static struct lock_file lock;
+
 int main(int argc, char **argv)
 {
        int i, ret, nr_heads;
        char *dest = NULL, **heads;
        int fd[2];
        pid_t pid;
+       struct stat st;
 
        setup_git_directory();
+       git_config(fetch_pack_config);
+
+       if (0 <= transfer_unpack_limit)
+               unpack_limit = transfer_unpack_limit;
+       else if (0 <= fetch_unpack_limit)
+               unpack_limit = fetch_unpack_limit;
 
        nr_heads = 0;
        heads = NULL;
@@ -581,8 +683,12 @@ int main(int argc, char **argv)
                char *arg = argv[i];
 
                if (*arg == '-') {
+                       if (!strncmp("--upload-pack=", arg, 14)) {
+                               uploadpack = arg + 14;
+                               continue;
+                       }
                        if (!strncmp("--exec=", arg, 7)) {
-                               exec = arg + 7;
+                               uploadpack = arg + 7;
                                continue;
                        }
                        if (!strcmp("--quiet", arg) || !strcmp("-q", arg)) {
@@ -591,6 +697,7 @@ int main(int argc, char **argv)
                        }
                        if (!strcmp("--keep", arg) || !strcmp("-k", arg)) {
                                keep_pack++;
+                               unpack_limit = 0;
                                continue;
                        }
                        if (!strcmp("--thin", arg)) {
@@ -605,6 +712,12 @@ int main(int argc, char **argv)
                                verbose = 1;
                                continue;
                        }
+                       if (!strncmp("--depth=", arg, 8)) {
+                               depth = strtol(arg + 8, NULL, 0);
+                               if (stat(git_path("shallow"), &st))
+                                       st.st_mtime = 0;
+                               continue;
+                       }
                        usage(fetch_pack_usage);
                }
                dest = arg;
@@ -614,9 +727,11 @@ int main(int argc, char **argv)
        }
        if (!dest)
                usage(fetch_pack_usage);
-       pid = git_connect(fd, dest, exec);
+       pid = git_connect(fd, dest, uploadpack);
        if (pid < 0)
                return 1;
+       if (heads && nr_heads)
+               nr_heads = remove_duplicates(nr_heads, heads);
        ret = fetch_pack(fd, nr_heads, heads);
        close(fd[0]);
        close(fd[1]);
@@ -635,5 +750,34 @@ int main(int argc, char **argv)
                        }
        }
 
+       if (!ret && depth > 0) {
+               struct cache_time mtime;
+               char *shallow = git_path("shallow");
+               int fd;
+
+               mtime.sec = st.st_mtime;
+#ifdef USE_NSEC
+               mtime.usec = st.st_mtim.usec;
+#endif
+               if (stat(shallow, &st)) {
+                       if (mtime.sec)
+                               die("shallow file was removed during fetch");
+               } else if (st.st_mtime != mtime.sec
+#ifdef USE_NSEC
+                               || st.st_mtim.usec != mtime.usec
+#endif
+                         )
+                       die("shallow file was changed during fetch");
+
+               fd = hold_lock_file_for_update(&lock, shallow, 1);
+               if (!write_shallow_commits(fd, 0)) {
+                       unlink(shallow);
+                       rollback_lock_file(&lock);
+               } else {
+                       close(fd);
+                       commit_lock_file(&lock);
+               }
+       }
+
        return !!ret;
 }
diff --git a/fetch.c b/fetch.c
index c426c049974fa1bf1aceea9d43d2e2c5ce194e06..f69be82f10d287d71f6184c4b9203bdab3ce81fc 100644 (file)
--- a/fetch.c
+++ b/fetch.c
@@ -1,6 +1,5 @@
-#include "fetch.h"
-
 #include "cache.h"
+#include "fetch.h"
 #include "commit.h"
 #include "tree.h"
 #include "tree-walk.h"
@@ -22,14 +21,15 @@ void pull_say(const char *fmt, const char *hex)
                fprintf(stderr, fmt, hex);
 }
 
-static void report_missing(const char *what, const unsigned char *missing)
+static void report_missing(const struct object *obj)
 {
        char missing_hex[41];
-
-       strcpy(missing_hex, sha1_to_hex(missing));;
-       fprintf(stderr,
-               "Cannot obtain needed %s %s\nwhile processing commit %s.\n",
-               what, missing_hex, sha1_to_hex(current_commit_sha1));
+       strcpy(missing_hex, sha1_to_hex(obj->sha1));;
+       fprintf(stderr, "Cannot obtain needed %s %s\n",
+               obj->type ? typename(obj->type): "object", missing_hex);
+       if (!is_null_sha1(current_commit_sha1))
+               fprintf(stderr, "while processing commit %s.\n",
+                       sha1_to_hex(current_commit_sha1));
 }
 
 static int process(struct object *obj);
@@ -177,7 +177,7 @@ static int loop(void)
                 */
                if (! (obj->flags & TO_SCAN)) {
                        if (fetch(obj->sha1)) {
-                               report_missing(typename(obj->type), obj->sha1);
+                               report_missing(obj);
                                return -1;
                        }
                }
index 5450918be339bf101a6562d518c0f4cc7b37c0f2..975777f05ed69ef78f1fd91ac512f5b92a384141 100755 (executable)
@@ -4,7 +4,7 @@ echo "/* Automatically generated by $0 */
 struct cmdname_help
 {
     char name[16];
-    char help[64];
+    char help[80];
 };
 
 struct cmdname_help common_cmds[] = {"
@@ -22,7 +22,7 @@ commit
 diff
 fetch
 grep
-init-db
+init
 log
 merge
 mv
@@ -37,7 +37,6 @@ show
 show-branch
 status
 tag
-verify-tag
 EOF
 while read cmd
 do
diff --git a/git-add--interactive.perl b/git-add--interactive.perl
new file mode 100755 (executable)
index 0000000..dc30380
--- /dev/null
@@ -0,0 +1,803 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+sub run_cmd_pipe {
+       my $fh = undef;
+       open($fh, '-|', @_) or die;
+       return <$fh>;
+}
+
+my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
+
+if (!defined $GIT_DIR) {
+       exit(1); # rev-parse would have already said "not a git repo"
+}
+chomp($GIT_DIR);
+
+sub refresh {
+       my $fh;
+       open $fh, '-|', qw(git update-index --refresh)
+           or die;
+       while (<$fh>) {
+               ;# ignore 'needs update'
+       }
+       close $fh;
+}
+
+sub list_untracked {
+       map {
+               chomp $_;
+               $_;
+       }
+       run_cmd_pipe(qw(git ls-files --others
+                       --exclude-per-directory=.gitignore),
+                    "--exclude-from=$GIT_DIR/info/exclude",
+                    '--', @_);
+}
+
+my $status_fmt = '%12s %12s %s';
+my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
+
+# Returns list of hashes, contents of each of which are:
+# PRINT:       print message
+# VALUE:       pathname
+# BINARY:      is a binary path
+# INDEX:       is index different from HEAD?
+# FILE:                is file different from index?
+# INDEX_ADDDEL:        is it add/delete between HEAD and index?
+# FILE_ADDDEL: is it add/delete between index and file?
+
+sub list_modified {
+       my ($only) = @_;
+       my (%data, @return);
+       my ($add, $del, $adddel, $file);
+
+       for (run_cmd_pipe(qw(git diff-index --cached
+                            --numstat --summary HEAD))) {
+               if (($add, $del, $file) =
+                   /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       my ($change, $bin);
+                       if ($add eq '-' && $del eq '-') {
+                               $change = 'binary';
+                               $bin = 1;
+                       }
+                       else {
+                               $change = "+$add/-$del";
+                       }
+                       $data{$file} = {
+                               INDEX => $change,
+                               BINARY => $bin,
+                               FILE => 'nothing',
+                       }
+               }
+               elsif (($adddel, $file) =
+                      /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $data{$file}{INDEX_ADDDEL} = $adddel;
+               }
+       }
+
+       for (run_cmd_pipe(qw(git diff-files --numstat --summary))) {
+               if (($add, $del, $file) =
+                   /^([-\d]+)  ([-\d]+)        (.*)/) {
+                       if (!exists $data{$file}) {
+                               $data{$file} = +{
+                                       INDEX => 'unchanged',
+                                       BINARY => 0,
+                               };
+                       }
+                       my ($change, $bin);
+                       if ($add eq '-' && $del eq '-') {
+                               $change = 'binary';
+                               $bin = 1;
+                       }
+                       else {
+                               $change = "+$add/-$del";
+                       }
+                       $data{$file}{FILE} = $change;
+                       if ($bin) {
+                               $data{$file}{BINARY} = 1;
+                       }
+               }
+               elsif (($adddel, $file) =
+                      /^ (create|delete) mode [0-7]+ (.*)$/) {
+                       $data{$file}{FILE_ADDDEL} = $adddel;
+               }
+       }
+
+       for (sort keys %data) {
+               my $it = $data{$_};
+
+               if ($only) {
+                       if ($only eq 'index-only') {
+                               next if ($it->{INDEX} eq 'unchanged');
+                       }
+                       if ($only eq 'file-only') {
+                               next if ($it->{FILE} eq 'nothing');
+                       }
+               }
+               push @return, +{
+                       VALUE => $_,
+                       PRINT => (sprintf $status_fmt,
+                                 $it->{INDEX}, $it->{FILE}, $_),
+                       %$it,
+               };
+       }
+       return @return;
+}
+
+sub find_unique {
+       my ($string, @stuff) = @_;
+       my $found = undef;
+       for (my $i = 0; $i < @stuff; $i++) {
+               my $it = $stuff[$i];
+               my $hit = undef;
+               if (ref $it) {
+                       if ((ref $it) eq 'ARRAY') {
+                               $it = $it->[0];
+                       }
+                       else {
+                               $it = $it->{VALUE};
+                       }
+               }
+               eval {
+                       if ($it =~ /^$string/) {
+                               $hit = 1;
+                       };
+               };
+               if (defined $hit && defined $found) {
+                       return undef;
+               }
+               if ($hit) {
+                       $found = $i + 1;
+               }
+       }
+       return $found;
+}
+
+sub list_and_choose {
+       my ($opts, @stuff) = @_;
+       my (@chosen, @return);
+       my $i;
+
+      TOPLOOP:
+       while (1) {
+               my $last_lf = 0;
+
+               if ($opts->{HEADER}) {
+                       if (!$opts->{LIST_FLAT}) {
+                               print "     ";
+                       }
+                       print "$opts->{HEADER}\n";
+               }
+               for ($i = 0; $i < @stuff; $i++) {
+                       my $chosen = $chosen[$i] ? '*' : ' ';
+                       my $print = $stuff[$i];
+                       if (ref $print) {
+                               if ((ref $print) eq 'ARRAY') {
+                                       $print = $print->[0];
+                               }
+                               else {
+                                       $print = $print->{PRINT};
+                               }
+                       }
+                       printf("%s%2d: %s", $chosen, $i+1, $print);
+                       if (($opts->{LIST_FLAT}) &&
+                           (($i + 1) % ($opts->{LIST_FLAT}))) {
+                               print "\t";
+                               $last_lf = 0;
+                       }
+                       else {
+                               print "\n";
+                               $last_lf = 1;
+                       }
+               }
+               if (!$last_lf) {
+                       print "\n";
+               }
+
+               return if ($opts->{LIST_ONLY});
+
+               print $opts->{PROMPT};
+               if ($opts->{SINGLETON}) {
+                       print "> ";
+               }
+               else {
+                       print ">> ";
+               }
+               my $line = <STDIN>;
+               last if (!$line);
+               chomp $line;
+               my $donesomething = 0;
+               for my $choice (split(/[\s,]+/, $line)) {
+                       my $choose = 1;
+                       my ($bottom, $top);
+
+                       # Input that begins with '-'; unchoose
+                       if ($choice =~ s/^-//) {
+                               $choose = 0;
+                       }
+                       # A range can be specified like 5-7
+                       if ($choice =~ /^(\d+)-(\d+)$/) {
+                               ($bottom, $top) = ($1, $2);
+                       }
+                       elsif ($choice =~ /^\d+$/) {
+                               $bottom = $top = $choice;
+                       }
+                       elsif ($choice eq '*') {
+                               $bottom = 1;
+                               $top = 1 + @stuff;
+                       }
+                       else {
+                               $bottom = $top = find_unique($choice, @stuff);
+                               if (!defined $bottom) {
+                                       print "Huh ($choice)?\n";
+                                       next TOPLOOP;
+                               }
+                       }
+                       if ($opts->{SINGLETON} && $bottom != $top) {
+                               print "Huh ($choice)?\n";
+                               next TOPLOOP;
+                       }
+                       for ($i = $bottom-1; $i <= $top-1; $i++) {
+                               next if (@stuff <= $i);
+                               $chosen[$i] = $choose;
+                               $donesomething++;
+                       }
+               }
+               last if (!$donesomething || $opts->{IMMEDIATE});
+       }
+       for ($i = 0; $i < @stuff; $i++) {
+               if ($chosen[$i]) {
+                       push @return, $stuff[$i];
+               }
+       }
+       return @return;
+}
+
+sub status_cmd {
+       list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
+                       list_modified());
+       print "\n";
+}
+
+sub say_n_paths {
+       my $did = shift @_;
+       my $cnt = scalar @_;
+       print "$did ";
+       if (1 < $cnt) {
+               print "$cnt paths\n";
+       }
+       else {
+               print "one path\n";
+       }
+}
+
+sub update_cmd {
+       my @mods = list_modified('file-only');
+       return if (!@mods);
+
+       my @update = list_and_choose({ PROMPT => 'Update',
+                                      HEADER => $status_head, },
+                                    @mods);
+       if (@update) {
+               system(qw(git update-index --add --remove --),
+                      map { $_->{VALUE} } @update);
+               say_n_paths('updated', @update);
+       }
+       print "\n";
+}
+
+sub revert_cmd {
+       my @update = list_and_choose({ PROMPT => 'Revert',
+                                      HEADER => $status_head, },
+                                    list_modified());
+       if (@update) {
+               my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
+                                        map { $_->{VALUE} } @update);
+               my $fh;
+               open $fh, '|-', qw(git update-index --index-info)
+                   or die;
+               for (@lines) {
+                       print $fh $_;
+               }
+               close($fh);
+               for (@update) {
+                       if ($_->{INDEX_ADDDEL} &&
+                           $_->{INDEX_ADDDEL} eq 'create') {
+                               system(qw(git update-index --force-remove --),
+                                      $_->{VALUE});
+                               print "note: $_->{VALUE} is untracked now.\n";
+                       }
+               }
+               refresh();
+               say_n_paths('reverted', @update);
+       }
+       print "\n";
+}
+
+sub add_untracked_cmd {
+       my @add = list_and_choose({ PROMPT => 'Add untracked' },
+                                 list_untracked());
+       if (@add) {
+               system(qw(git update-index --add --), @add);
+               say_n_paths('added', @add);
+       }
+       print "\n";
+}
+
+sub parse_diff {
+       my ($path) = @_;
+       my @diff = run_cmd_pipe(qw(git diff-files -p --), $path);
+       my (@hunk) = { TEXT => [] };
+
+       for (@diff) {
+               if (/^@@ /) {
+                       push @hunk, { TEXT => [] };
+               }
+               push @{$hunk[-1]{TEXT}}, $_;
+       }
+       return @hunk;
+}
+
+sub hunk_splittable {
+       my ($text) = @_;
+
+       my @s = split_hunk($text);
+       return (1 < @s);
+}
+
+sub parse_hunk_header {
+       my ($line) = @_;
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+           $line =~ /^@@ -(\d+)(?:,(\d+)) \+(\d+)(?:,(\d+)) @@/;
+       return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
+}
+
+sub split_hunk {
+       my ($text) = @_;
+       my @split = ();
+
+       # If there are context lines in the middle of a hunk,
+       # it can be split, but we would need to take care of
+       # overlaps later.
+
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]);
+       my $hunk_start = 1;
+       my $next_hunk_start;
+
+      OUTER:
+       while (1) {
+               my $next_hunk_start = undef;
+               my $i = $hunk_start - 1;
+               my $this = +{
+                       TEXT => [],
+                       OLD => $o_ofs,
+                       NEW => $n_ofs,
+                       OCNT => 0,
+                       NCNT => 0,
+                       ADDDEL => 0,
+                       POSTCTX => 0,
+               };
+
+               while (++$i < @$text) {
+                       my $line = $text->[$i];
+                       if ($line =~ /^ /) {
+                               if ($this->{ADDDEL} &&
+                                   !defined $next_hunk_start) {
+                                       # We have seen leading context and
+                                       # adds/dels and then here is another
+                                       # context, which is trailing for this
+                                       # split hunk and leading for the next
+                                       # one.
+                                       $next_hunk_start = $i;
+                               }
+                               push @{$this->{TEXT}}, $line;
+                               $this->{OCNT}++;
+                               $this->{NCNT}++;
+                               if (defined $next_hunk_start) {
+                                       $this->{POSTCTX}++;
+                               }
+                               next;
+                       }
+
+                       # add/del
+                       if (defined $next_hunk_start) {
+                               # We are done with the current hunk and
+                               # this is the first real change for the
+                               # next split one.
+                               $hunk_start = $next_hunk_start;
+                               $o_ofs = $this->{OLD} + $this->{OCNT};
+                               $n_ofs = $this->{NEW} + $this->{NCNT};
+                               $o_ofs -= $this->{POSTCTX};
+                               $n_ofs -= $this->{POSTCTX};
+                               push @split, $this;
+                               redo OUTER;
+                       }
+                       push @{$this->{TEXT}}, $line;
+                       $this->{ADDDEL}++;
+                       if ($line =~ /^-/) {
+                               $this->{OCNT}++;
+                       }
+                       else {
+                               $this->{NCNT}++;
+                       }
+               }
+
+               push @split, $this;
+               last;
+       }
+
+       for my $hunk (@split) {
+               $o_ofs = $hunk->{OLD};
+               $n_ofs = $hunk->{NEW};
+               $o_cnt = $hunk->{OCNT};
+               $n_cnt = $hunk->{NCNT};
+
+               my $head = ("@@ -$o_ofs" .
+                           (($o_cnt != 1) ? ",$o_cnt" : '') .
+                           " +$n_ofs" .
+                           (($n_cnt != 1) ? ",$n_cnt" : '') .
+                           " @@\n");
+               unshift @{$hunk->{TEXT}}, $head;
+       }
+       return map { $_->{TEXT} } @split;
+}
+
+sub find_last_o_ctx {
+       my ($it) = @_;
+       my $text = $it->{TEXT};
+       my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = parse_hunk_header($text->[0]);
+       my $i = @{$text};
+       my $last_o_ctx = $o_ofs + $o_cnt;
+       while (0 < --$i) {
+               my $line = $text->[$i];
+               if ($line =~ /^ /) {
+                       $last_o_ctx--;
+                       next;
+               }
+               last;
+       }
+       return $last_o_ctx;
+}
+
+sub merge_hunk {
+       my ($prev, $this) = @_;
+       my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
+           parse_hunk_header($prev->{TEXT}[0]);
+       my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
+           parse_hunk_header($this->{TEXT}[0]);
+
+       my (@line, $i, $ofs, $o_cnt, $n_cnt);
+       $ofs = $o0_ofs;
+       $o_cnt = $n_cnt = 0;
+       for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
+               my $line = $prev->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+
+               last if ($o1_ofs <= $ofs);
+
+               $o_cnt++;
+               $ofs++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+
+       for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
+               my $line = $this->{TEXT}[$i];
+               if ($line =~ /^\+/) {
+                       $n_cnt++;
+                       push @line, $line;
+                       next;
+               }
+               $ofs++;
+               $o_cnt++;
+               if ($line =~ /^ /) {
+                       $n_cnt++;
+               }
+               push @line, $line;
+       }
+       my $head = ("@@ -$o0_ofs" .
+                   (($o_cnt != 1) ? ",$o_cnt" : '') .
+                   " +$n0_ofs" .
+                   (($n_cnt != 1) ? ",$n_cnt" : '') .
+                   " @@\n");
+       @{$prev->{TEXT}} = ($head, @line);
+}
+
+sub coalesce_overlapping_hunks {
+       my (@in) = @_;
+       my @out = ();
+
+       my ($last_o_ctx);
+
+       for (grep { $_->{USE} } @in) {
+               my $text = $_->{TEXT};
+               my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+                   parse_hunk_header($text->[0]);
+               if (defined $last_o_ctx &&
+                   $o_ofs <= $last_o_ctx) {
+                       merge_hunk($out[-1], $_);
+               }
+               else {
+                       push @out, $_;
+               }
+               $last_o_ctx = find_last_o_ctx($out[-1]);
+       }
+       return @out;
+}
+
+sub help_patch_cmd {
+       print <<\EOF ;
+y - stage this hunk
+n - do not stage this hunk
+a - stage this and all the remaining hunks
+d - do not stage this hunk nor any of the remaining hunks
+j - leave this hunk undecided, see next undecided hunk
+J - leave this hunk undecided, see next hunk
+k - leave this hunk undecided, see previous undecided hunk
+K - leave this hunk undecided, see previous hunk
+s - split the current hunk into smaller hunks
+EOF
+}
+
+sub patch_update_cmd {
+       my @mods = list_modified('file-only');
+       @mods = grep { !($_->{BINARY}) } @mods;
+       return if (!@mods);
+
+       my ($it) = list_and_choose({ PROMPT => 'Patch update',
+                                    SINGLETON => 1,
+                                    IMMEDIATE => 1,
+                                    HEADER => $status_head, },
+                                  @mods);
+       return if (!$it);
+
+       my ($ix, $num);
+       my $path = $it->{VALUE};
+       my ($head, @hunk) = parse_diff($path);
+       for (@{$head->{TEXT}}) {
+               print;
+       }
+       $num = scalar @hunk;
+       $ix = 0;
+
+       while (1) {
+               my ($prev, $next, $other, $undecided, $i);
+               $other = '';
+
+               if ($num <= $ix) {
+                       $ix = 0;
+               }
+               for ($i = 0; $i < $ix; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $prev = 1;
+                               $other .= '/k';
+                               last;
+                       }
+               }
+               if ($ix) {
+                       $other .= '/K';
+               }
+               for ($i = $ix + 1; $i < $num; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $next = 1;
+                               $other .= '/j';
+                               last;
+                       }
+               }
+               if ($ix < $num - 1) {
+                       $other .= '/J';
+               }
+               for ($i = 0; $i < $num; $i++) {
+                       if (!defined $hunk[$i]{USE}) {
+                               $undecided = 1;
+                               last;
+                       }
+               }
+               last if (!$undecided);
+
+               if (hunk_splittable($hunk[$ix]{TEXT})) {
+                       $other .= '/s';
+               }
+               for (@{$hunk[$ix]{TEXT}}) {
+                       print;
+               }
+               print "Stage this hunk [y/n/a/d$other/?]? ";
+               my $line = <STDIN>;
+               if ($line) {
+                       if ($line =~ /^y/i) {
+                               $hunk[$ix]{USE} = 1;
+                       }
+                       elsif ($line =~ /^n/i) {
+                               $hunk[$ix]{USE} = 0;
+                       }
+                       elsif ($line =~ /^a/i) {
+                               while ($ix < $num) {
+                                       if (!defined $hunk[$ix]{USE}) {
+                                               $hunk[$ix]{USE} = 1;
+                                       }
+                                       $ix++;
+                               }
+                               next;
+                       }
+                       elsif ($line =~ /^d/i) {
+                               while ($ix < $num) {
+                                       if (!defined $hunk[$ix]{USE}) {
+                                               $hunk[$ix]{USE} = 0;
+                                       }
+                                       $ix++;
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /K/ && $line =~ /^K/) {
+                               $ix--;
+                               next;
+                       }
+                       elsif ($other =~ /J/ && $line =~ /^J/) {
+                               $ix++;
+                               next;
+                       }
+                       elsif ($other =~ /k/ && $line =~ /^k/) {
+                               while (1) {
+                                       $ix--;
+                                       last if (!$ix ||
+                                                !defined $hunk[$ix]{USE});
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /j/ && $line =~ /^j/) {
+                               while (1) {
+                                       $ix++;
+                                       last if ($ix >= $num ||
+                                                !defined $hunk[$ix]{USE});
+                               }
+                               next;
+                       }
+                       elsif ($other =~ /s/ && $line =~ /^s/) {
+                               my @split = split_hunk($hunk[$ix]{TEXT});
+                               if (1 < @split) {
+                                       print "Split into ",
+                                       scalar(@split), " hunks.\n";
+                               }
+                               splice(@hunk, $ix, 1,
+                                      map { +{ TEXT => $_, USE => undef } }
+                                      @split);
+                               $num = scalar @hunk;
+                               next;
+                       }
+                       else {
+                               help_patch_cmd($other);
+                               next;
+                       }
+                       # soft increment
+                       while (1) {
+                               $ix++;
+                               last if ($ix >= $num ||
+                                        !defined $hunk[$ix]{USE});
+                       }
+               }
+       }
+
+       @hunk = coalesce_overlapping_hunks(@hunk);
+
+       my ($o_lofs, $n_lofs) = (0, 0);
+       my @result = ();
+       for (@hunk) {
+               my $text = $_->{TEXT};
+               my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
+                   parse_hunk_header($text->[0]);
+
+               if (!$_->{USE}) {
+                       if (!defined $o_cnt) { $o_cnt = 1; }
+                       if (!defined $n_cnt) { $n_cnt = 1; }
+
+                       # We would have added ($n_cnt - $o_cnt) lines
+                       # to the postimage if we were to use this hunk,
+                       # but we didn't.  So the line number that the next
+                       # hunk starts at would be shifted by that much.
+                       $n_lofs -= ($n_cnt - $o_cnt);
+                       next;
+               }
+               else {
+                       if ($n_lofs) {
+                               $n_ofs += $n_lofs;
+                               $text->[0] = ("@@ -$o_ofs" .
+                                             ((defined $o_cnt)
+                                              ? ",$o_cnt" : '') .
+                                             " +$n_ofs" .
+                                             ((defined $n_cnt)
+                                              ? ",$n_cnt" : '') .
+                                             " @@\n");
+                       }
+                       for (@$text) {
+                               push @result, $_;
+                       }
+               }
+       }
+
+       if (@result) {
+               my $fh;
+
+               open $fh, '|-', qw(git apply --cached);
+               for (@{$head->{TEXT}}, @result) {
+                       print $fh $_;
+               }
+               if (!close $fh) {
+                       for (@{$head->{TEXT}}, @result) {
+                               print STDERR $_;
+                       }
+               }
+               refresh();
+       }
+
+       print "\n";
+}
+
+sub diff_cmd {
+       my @mods = list_modified('index-only');
+       @mods = grep { !($_->{BINARY}) } @mods;
+       return if (!@mods);
+       my (@them) = list_and_choose({ PROMPT => 'Review diff',
+                                    IMMEDIATE => 1,
+                                    HEADER => $status_head, },
+                                  @mods);
+       return if (!@them);
+       system(qw(git diff-index -p --cached HEAD --),
+              map { $_->{VALUE} } @them);
+}
+
+sub quit_cmd {
+       print "Bye.\n";
+       exit(0);
+}
+
+sub help_cmd {
+       print <<\EOF ;
+status        - show paths with changes
+update        - add working tree state to the staged set of changes
+revert        - revert staged set of changes back to the HEAD version
+patch         - pick hunks and update selectively
+diff         - view diff between HEAD and index
+add untracked - add contents of untracked files to the staged set of changes
+EOF
+}
+
+sub main_loop {
+       my @cmd = ([ 'status', \&status_cmd, ],
+                  [ 'update', \&update_cmd, ],
+                  [ 'revert', \&revert_cmd, ],
+                  [ 'add untracked', \&add_untracked_cmd, ],
+                  [ 'patch', \&patch_update_cmd, ],
+                  [ 'diff', \&diff_cmd, ],
+                  [ 'quit', \&quit_cmd, ],
+                  [ 'help', \&help_cmd, ],
+       );
+       while (1) {
+               my ($it) = list_and_choose({ PROMPT => 'What now',
+                                            SINGLETON => 1,
+                                            LIST_FLAT => 4,
+                                            HEADER => '*** Commands ***',
+                                            IMMEDIATE => 1 }, @cmd);
+               if ($it) {
+                       eval {
+                               $it->[1]->();
+                       };
+                       if ($@) {
+                               print "$@";
+                       }
+               }
+       }
+}
+
+my @z;
+
+refresh();
+status_cmd();
+main_loop();
index afe322b20fb0b40dfeb1cb17dc3cad09096e943a..6db9cb503a2717d0c45ee40e262747cbeba3ad17 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -2,10 +2,12 @@
 #
 # Copyright (c) 2005, 2006 Junio C Hamano
 
-USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
-  [--interactive] [--whitespace=<option>] <mbox>...
+USAGE='[--signoff] [--dotest=<dir>] [--utf8 | --no-utf8] [--binary] [--3way]
+  [--interactive] [--whitespace=<option>] [-C<n>] [-p<n>] <mbox>...
   or, when resuming [--skip | --resolved]'
 . git-sh-setup
+set_reflog_action am
+require_work_tree
 
 git var GIT_COMMITTER_IDENT >/dev/null || exit
 
@@ -87,10 +89,12 @@ It does not apply to blobs recorded in its index."
     # 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,
+    # patch did not touch, so recursive ends up canceling them,
     # saying that we reverted all those changes.
 
-    git-merge-resolve $orig_tree -- HEAD $his_tree || {
+    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
@@ -98,11 +102,12 @@ It does not apply to blobs recorded in its index."
            echo Failed to merge in the changes.
            exit 1
     }
+    unset GITHEAD_$his_tree
 }
 
 prec=4
-rloga=am
-dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg=
+dotest=.dotest sign= utf8=t keep= skip= interactive= resolved= binary= resolvemsg=
+git_apply_opt=
 
 while case "$#" in 0) break;; esac
 do
@@ -125,7 +130,9 @@ do
        -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
        sign=t; shift ;;
        -u|--u|--ut|--utf|--utf8)
-       utf8=t; shift ;;
+       utf8=t; shift ;; # this is now default
+       --no-u|--no-ut|--no-utf|--no-utf8)
+       utf8=; shift ;;
        -k|--k|--ke|--kee|--keep)
        keep=t; shift ;;
 
@@ -135,15 +142,12 @@ do
        --sk|--ski|--skip)
        skip=t; shift ;;
 
-       --whitespace=*)
-       ws=$1; shift ;;
+       --whitespace=*|-C*|-p*)
+       git_apply_opt="$git_apply_opt $1"; shift ;;
 
        --resolvemsg=*)
        resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;;
 
-       --reflog-action=*)
-       rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;;
-
        --)
        shift; break ;;
        -*)
@@ -226,6 +230,8 @@ fi
 if test "$(cat "$dotest/utf8")" = t
 then
        utf8=-u
+else
+       utf8=-n
 fi
 if test "$(cat "$dotest/keep")" = t
 then
@@ -246,6 +252,10 @@ 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
        this=`expr "$this" + 1`
        resume=
 fi
@@ -385,7 +395,7 @@ do
 
        case "$resolved" in
        '')
-               git-apply $binary --index $ws "$dotest/patch"
+               git-apply $git_apply_opt $binary --index "$dotest/patch"
                apply_status=$?
                ;;
        t)
@@ -397,17 +407,21 @@ do
                changed="$(git-diff-index --cached --name-only HEAD)"
                if test '' = "$changed"
                then
-                       echo "No changes - did you forget update-index?"
+                       echo "No changes - did you forget to use 'git add'?"
                        stop_here_user_resolve $this
                fi
                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'?"
                        stop_here_user_resolve $this
                fi
                apply_status=0
+               if test -d "$GIT_DIR/rr-cache"
+               then
+                       git rerere
+               fi
                ;;
        esac
 
@@ -444,7 +458,7 @@ do
        parent=$(git-rev-parse --verify HEAD) &&
        commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") &&
        echo Committed: $commit &&
-       git-update-ref -m "$rloga: $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
index 5569fdcc3463b214411d5a168a00783b0d390044..1f68599ae51a671f8c7a3a01c6aa9c6b205dd217 100755 (executable)
@@ -23,11 +23,12 @@ USAGE='[-u] [-k] [-q] [-m] (-c .dotest/<num> | mbox) [signoff]'
 
 git var GIT_COMMITTER_IDENT >/dev/null || exit
 
-keep_subject= query_apply= continue= utf8= resume=t
+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 ;;
index ada60ec240a8b3eb4ef6efea6b6223fc8523705f..66aaeae1021eb3c791815f927e95a87e7afa557c 100755 (executable)
@@ -95,6 +95,15 @@ $ENV{'TMPDIR'} = $opt_t if $opt_t; # $ENV{TMPDIR} will affect tempdir() calls:
 my $tmp = tempdir('git-archimport-XXXXXX', TMPDIR => 1, CLEANUP => 1);
 $opt_v && print "+ Using $tmp as temporary directory\n";
 
+unless (-d $git_dir) { # initial import needs empty directory
+    opendir DIR, '.' or die "Unable to open current directory: $!\n";
+    while (my $entry = readdir DIR) {
+        $entry =~ /^\.\.?$/ or
+            die "Initial import needs an empty current working directory.\n"
+    }
+    closedir DIR
+}
+
 my %reachable = ();             # Arch repositories we can access
 my %unreachable = ();           # Arch repositories we can't access :<
 my @psets  = ();                # the collection
@@ -226,7 +235,7 @@ my $import = 0;
 unless (-d $git_dir) { # initial import
     if ($psets[0]{type} eq 'i' || $psets[0]{type} eq 't') {
         print "Starting import from $psets[0]{id}\n";
-       `git-init-db`;
+       `git-init`;
        die $! if $?;
        $import = 1;
     } else {
index 6da31e87a01af9b883958b3b0bae4304bfa94520..b1c3a6b1c1af9815db473595f4eb3e9593425f10 100755 (executable)
@@ -1,6 +1,6 @@
 #!/bin/sh
 
-USAGE='[start|bad|good|next|reset|visualize]'
+USAGE='[start|bad|good|next|reset|visualize|replay|log]'
 LONG_USAGE='git bisect start [<pathspec>]      reset bisect state and start bisection.
 git bisect bad [<rev>]         mark <rev> a known-bad revision.
 git bisect good [<rev>...]     mark <rev>... known-good revisions.
@@ -11,6 +11,7 @@ git bisect replay <logfile>   replay bisection log
 git bisect log                 show bisect log.'
 
 . git-sh-setup
+require_work_tree
 
 sq() {
        @@PERL@@ -e '
@@ -152,7 +153,7 @@ bisect_next() {
        nr=$(eval "git-rev-list $rev $good -- $(cat $GIT_DIR/BISECT_NAMES)" | wc -l) || exit
        echo "Bisecting: $nr revisions left to test after this"
        echo "$rev" > "$GIT_DIR/refs/heads/new-bisect"
-       git checkout new-bisect || exit
+       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 "$rev"
index 737abd0c094ae6cfb201fb46c28f4962a9826a02..14835a4aa982af88d758ea014afacf7b2fb1bb1e 100755 (executable)
@@ -1,11 +1,13 @@
 #!/bin/sh
 
-USAGE='[-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
+USAGE='[-q] [-f] [-b <new_branch>] [-m] [<branch>] [<paths>...]'
 SUBDIRECTORY_OK=Sometimes
 . git-sh-setup
+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)
 new=
 new_name=
 force=
@@ -13,6 +15,9 @@ branch=
 newbranch=
 newbranch_log=
 merge=
+quiet=
+LF='
+'
 while [ "$#" != "0" ]; do
     arg="$1"
     shift
@@ -36,6 +41,9 @@ while [ "$#" != "0" ]; do
        -m)
                merge=1
                ;;
+       "-q")
+               quiet=1
+               ;;
        --)
                break
                ;;
@@ -50,7 +58,7 @@ while [ "$#" != "0" ]; do
                                exit 1
                        fi
                        new="$rev"
-                       new_name="$arg^0"
+                       new_name="$arg"
                        if git-show-ref --verify --quiet -- "refs/heads/$arg"
                        then
                                branch="$arg"
@@ -131,28 +139,43 @@ fi
 
 # We are switching branches and checking out trees, so
 # we *NEED* to be at the toplevel.
-cdup=$(git-rev-parse --show-cdup)
-if test ! -z "$cdup"
-then
-       cd "$cdup"
-fi
+cd_to_toplevel
 
 [ -z "$new" ] && new=$old && new_name="$old_name"
 
-# If we don't have an old branch that we're switching to,
+# If we don't have an existing branch that we're switching to,
 # and we don't have a new branch name for the target we
-# are switching to, then we'd better just be checking out
-# what we already had
+# are switching to, then we are detaching our HEAD from any
+# branch.  However, if "git checkout HEAD" detaches the HEAD
+# from the current branch, even though that may be logically
+# correct, it feels somewhat funny.  More importantly, we do not
+# want "git checkout" nor "git checkout -f" to detach HEAD.
 
-[ -z "$branch$newbranch" ] &&
-       [ "$new" != "$old" ] &&
-       die "git checkout: to checkout the requested commit you need to specify 
-              a name for a new branch which is created and switched to"
+detached=
+detach_warn=
+
+if test -z "$branch$newbranch" && test "$new" != "$old"
+then
+       detached="$new"
+       if test -n "$oldbranch" && test -z "$quiet"
+       then
+               detach_warn="Note: moving to \"$new_name\" which isn't a local branch
+If you want to create a new branch from this checkout, you may do so
+(now or later) by using -b with the checkout command again. Example:
+  git checkout -b <new_branch_name>"
+       fi
+elif test -z "$oldbranch" && test -z "$quiet"
+then
+       echo >&2 "Previous HEAD position was $old"
+fi
 
 if [ "X$old" = X ]
 then
-       echo "warning: You do not appear to currently be on a branch." >&2
-       echo "warning: Forcing checkout of $new_name." >&2
+       if test -z "$quiet"
+       then
+               echo >&2 "warning: You appear to be on a branch yet to be born."
+               echo >&2 "warning: Forcing checkout of $new_name."
+       fi
        force=1
 fi
 
@@ -161,7 +184,7 @@ then
     git-read-tree --reset -u $new
 else
     git-update-index --refresh >/dev/null
-    merge_error=$(git-read-tree -m -u $old $new 2>&1) || (
+    merge_error=$(git-read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
        case "$merge" in
        '')
                echo >&2 "$merge_error"
@@ -171,15 +194,12 @@ else
        # Match the index to the working tree, and do a three-way.
        git diff-files --name-only | git update-index --remove --stdin &&
        work=`git write-tree` &&
-       git read-tree --reset -u $new &&
-       git read-tree -m -u --aggressive $old $new $work || exit
+       git read-tree --reset -u $new || exit
 
-       if result=`git write-tree 2>/dev/null`
-       then
-           echo >&2 "Trivially automerged."
-       else
-           git merge-index -o git-merge-one-file -a
-       fi
+       eval GITHEAD_$new=${new_name:-${branch:-$new}} &&
+       eval GITHEAD_$work=local &&
+       export GITHEAD_$new GITHEAD_$work &&
+       git merge-recursive $old -- $new $work
 
        # Do not register the cleanly merged paths in the index yet.
        # this is not a real merge before committing, but just carrying
@@ -200,9 +220,9 @@ else
        exit 0
     )
     saved_err=$?
-    if test "$saved_err" = 0
+    if test "$saved_err" = 0 && test -z "$quiet"
     then
-       test "$new" = "$old" || git diff-index --name-status "$new"
+       git diff-index --name-status "$new"
     fi
     (exit $saved_err)
 fi
@@ -222,8 +242,30 @@ if [ "$?" -eq 0 ]; then
                git-update-ref -m "checkout: Created from $new_name" "refs/heads/$newbranch" $new || exit
                branch="$newbranch"
        fi
-       [ "$branch" ] &&
-       GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD "refs/heads/$branch"
+       if test -n "$branch"
+       then
+               GIT_DIR="$GIT_DIR" git-symbolic-ref -m "checkout: moving to $branch" HEAD "refs/heads/$branch"
+               if test -z "$quiet"
+               then
+                       echo >&2 "Switched to${newbranch:+ a new} branch \"$branch\""
+               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" ||
+                       die "Cannot detach HEAD"
+               if test -n "$detach_warn"
+               then
+                       echo >&2 "$detach_warn"
+               fi
+       fi
        rm -f "$GIT_DIR/MERGE_HEAD"
 else
        exit 1
index 3834323bcf479b7e01af4e1f247fb13aac423f90..db177a7886b6407b4c4ad7b778a1ae99471355ac 100755 (executable)
@@ -14,11 +14,11 @@ When optional <paths>... arguments are given, the paths
 affected are further limited to those that match them.'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
+require_work_tree
 
 ignored=
 ignoredonly=
 cleandir=
-quiet=
 rmf="rm -f --"
 rmrf="rm -rf --"
 rm_refuse="echo Not removing"
@@ -31,14 +31,13 @@ do
                cleandir=1
                ;;
        -n)
-               quiet=1
                rmf="echo Would remove"
                rmrf="echo Would remove"
                rm_refuse="echo Would not remove"
                echo1=":"
                ;;
        -q)
-               quiet=1
+               echo1=":"
                ;;
        -x)
                ignored=1
index 3f006d1a778636e5906ad9e8671adb6561fa0d98..1bd54ded3c424db30ca46b00b3ed42b4d774d21d 100755 (executable)
@@ -14,7 +14,7 @@ die() {
 }
 
 usage() {
-       die "Usage: $0 [--template=<template_directory>] [--use-separate-remote] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]"
+       die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] <repo> [<dir>]"
 }
 
 get_repo_base() {
@@ -36,7 +36,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-repo-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" ||
@@ -48,6 +48,10 @@ Perhaps git-update-server-info needs to be run there?"
                case "$name" in
                *^*)    continue;;
                esac
+               case "$bare,$name" in
+               yes,* | ,heads/* | ,tags/*) ;;
+               *)      continue ;;
+               esac
                if test -n "$use_separate_remote" &&
                   branch_name=`expr "z$name" : 'zheads/\(.*\)'`
                then
@@ -62,48 +66,6 @@ Perhaps git-update-server-info needs to be run there?"
        rm -f "$GIT_DIR/REMOTE_HEAD"
 }
 
-# Read git-fetch-pack -k output and store the remote branches.
-copy_refs='
-use File::Path qw(mkpath);
-use File::Basename qw(dirname);
-my $git_dir = $ARGV[0];
-my $use_separate_remote = $ARGV[1];
-my $origin = $ARGV[2];
-
-my $branch_top = ($use_separate_remote ? "remotes/$origin" : "heads");
-my $tag_top = "tags";
-
-sub store {
-       my ($sha1, $name, $top) = @_;
-       $name = "$git_dir/refs/$top/$name";
-       mkpath(dirname($name));
-       open O, ">", "$name";
-       print O "$sha1\n";
-       close O;
-}
-
-open FH, "<", "$git_dir/CLONE_HEAD";
-while (<FH>) {
-       my ($sha1, $name) = /^([0-9a-f]{40})\s(.*)$/;
-       next if ($name =~ /\^\173/);
-       if ($name eq "HEAD") {
-               open O, ">", "$git_dir/REMOTE_HEAD";
-               print O "$sha1\n";
-               close O;
-               next;
-       }
-       if ($name =~ s/^refs\/heads\///) {
-               store($sha1, $name, $branch_top);
-               next;
-       }
-       if ($name =~ s/^refs\/tags\///) {
-               store($sha1, $name, $tag_top);
-               next;
-       }
-}
-close FH;
-'
-
 quiet=
 local=no
 use_local=no
@@ -115,7 +77,8 @@ bare=
 reference=
 origin=
 origin_override=
-use_separate_remote=
+use_separate_remote=t
+depth=
 while
        case "$#,$1" in
        0,*) break ;;
@@ -133,8 +96,9 @@ while
        *,--template=*)
          template="$1" ;;
        *,-q|*,--quiet) quiet=-q ;;
-       *,--use-separate-remote)
-               use_separate_remote=t ;;
+       *,--use-separate-remote) ;;
+       *,--no-separate-remote)
+               die "clones are always made with separate-remote layout" ;;
        1,--reference) usage ;;
        *,--reference)
                shift; reference="$1" ;;
@@ -157,7 +121,13 @@ while
        1,-u|1,--upload-pack) usage ;;
        *,-u|*,--upload-pack)
                shift
-               upload_pack="--exec=$1" ;;
+               upload_pack="--upload-pack=$1" ;;
+       *,--upload-pack=*)
+               upload_pack=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)') ;;
+       1,--depth) usage;;
+       *,--depth)
+               shift
+               depth="--depth=$1";;
        *,-*) usage ;;
        *) break ;;
        esac
@@ -169,18 +139,15 @@ repo="$1"
 test -n "$repo" ||
     die 'you must specify a repository to clone.'
 
-# --bare implies --no-checkout
+# --bare implies --no-checkout and --no-separate-remote
 if test yes = "$bare"
 then
        if test yes = "$origin_override"
        then
                die '--bare and --origin $origin options are incompatible.'
        fi
-       if test t = "$use_separate_remote"
-       then
-               die '--bare and --use-separate-remote options are incompatible.'
-       fi
        no_checkout=yes
+       use_separate_remote=
 fi
 
 if test -z "$origin"
@@ -207,23 +174,36 @@ yes)
        GIT_DIR="$D" ;;
 *)
        GIT_DIR="$D/.git" ;;
-esac && export GIT_DIR && git-init-db ${template+"$template"} || usage
+esac && export GIT_DIR && git-init ${template+"$template"} || usage
 
 if test -n "$reference"
 then
+       ref_git=
        if test -d "$reference"
        then
                if test -d "$reference/.git/objects"
                then
-                       reference="$reference/.git"
+                       ref_git="$reference/.git"
+               elif test -d "$reference/objects"
+               then
+                       ref_git="$reference"
                fi
-               reference=$(cd "$reference" && pwd)
-               echo "$reference/objects" >"$GIT_DIR/objects/info/alternates"
-               (cd "$reference" && tar cf - refs) |
-               (cd "$GIT_DIR/refs" &&
-                mkdir reference-tmp &&
-                cd reference-tmp &&
-                tar xf -)
+       fi
+       if test -n "$ref_git"
+       then
+               ref_git=$(cd "$ref_git" && pwd)
+               echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
+               (
+                       GIT_DIR="$ref_git" git for-each-ref \
+                               --format='%(objectname) %(*objectname)'
+               ) |
+               while read a b
+               do
+                       test -z "$a" ||
+                       git update-ref "refs/reference-tmp/$a" "$a"
+                       test -z "$b" ||
+                       git update-ref "refs/reference-tmp/$b" "$b"
+               done
        else
                die "reference repository '$reference' is not a local directory."
        fi
@@ -265,6 +245,10 @@ yes,yes)
 *)
        case "$repo" in
        rsync://*)
+               case "$depth" in
+               "") ;;
+               *) die "shallow over rsync not supported" ;;
+               esac
                rsync $quiet -av --ignore-existing  \
                        --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
                exit
@@ -293,6 +277,10 @@ yes,yes)
                git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
                ;;
        https://*|http://*|ftp://*)
+               case "$depth" in
+               "") ;;
+               *) die "shallow over http or ftp not supported" ;;
+               esac
                if test -z "@@NO_CURL@@"
                then
                        clone_dumb_http "$repo" "$D"
@@ -302,8 +290,8 @@ yes,yes)
                ;;
        *)
                case "$upload_pack" in
-               '') git-fetch-pack --all -k $quiet "$repo" ;;
-               *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;;
+               '') git-fetch-pack --all -k $quiet $depth "$repo" ;;
+               *) git-fetch-pack --all -k $quiet "$upload_pack" $depth "$repo" ;;
                esac >"$GIT_DIR/CLONE_HEAD" ||
                        die "fetch-pack from '$repo' failed."
                ;;
@@ -315,20 +303,37 @@ test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
 if test -f "$GIT_DIR/CLONE_HEAD"
 then
        # Read git-fetch-pack -k output and store the remote branches.
-       @@PERL@@ -e "$copy_refs" "$GIT_DIR" "$use_separate_remote" "$origin" ||
-       exit
+       if [ -n "$use_separate_remote" ]
+       then
+               branch_top="remotes/$origin"
+       else
+               branch_top="heads"
+       fi
+       tag_top="tags"
+       while read sha1 name
+       do
+               case "$name" in
+               *'^{}')
+                       continue ;;
+               HEAD)
+                       destname="REMOTE_HEAD" ;;
+               refs/heads/*)
+                       destname="refs/$branch_top/${name#refs/heads/}" ;;
+               refs/tags/*)
+                       destname="refs/$tag_top/${name#refs/tags/}" ;;
+               *)
+                       continue ;;
+               esac
+               git-update-ref -m "clone: from $repo" "$destname" "$sha1" ""
+       done < "$GIT_DIR/CLONE_HEAD"
 fi
 
 cd "$D" || exit
 
 if test -z "$bare" && test -f "$GIT_DIR/REMOTE_HEAD"
 then
-       # Figure out which remote branch HEAD points at.
-       case "$use_separate_remote" in
-       '')     remote_top=refs/heads ;;
-       *)      remote_top="refs/remotes/$origin" ;;
-       esac
-
+       # a non-bare repository is always in separate-remote layout
+       remote_top="refs/remotes/$origin"
        head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
        case "$head_sha1" in
        'ref: refs/'*)
@@ -344,7 +349,7 @@ then
        # The name under $remote_top the remote HEAD seems to point at.
        head_points_at=$(
                (
-                       echo "master"
+                       test -f "$GIT_DIR/$remote_top/master" && echo "master"
                        cd "$GIT_DIR/$remote_top" &&
                        find . -type f -print | sed -e 's/^\.\///'
                ) | (
@@ -362,41 +367,28 @@ then
                )
        )
 
-       # Write out remotes/$origin file, and update our "$head_points_at".
+       # Write out remote.$origin config, and update our "$head_points_at".
        case "$head_points_at" in
        ?*)
-               mkdir -p "$GIT_DIR/remotes" &&
+               # Local default branch
                git-symbolic-ref HEAD "refs/heads/$head_points_at" &&
-               case "$use_separate_remote" in
-               t)      origin_track="$remote_top/$head_points_at"
-                       git-update-ref HEAD "$head_sha1" ;;
-               *)      origin_track="$remote_top/$origin"
-                       git-update-ref "refs/heads/$origin" "$head_sha1" ;;
-               esac &&
-               echo >"$GIT_DIR/remotes/$origin" \
-               "URL: $repo
-Pull: refs/heads/$head_points_at:$origin_track" &&
-               (cd "$GIT_DIR/$remote_top" && find . -type f -print) |
-               while read dotslref
-               do
-                       name=`expr "$dotslref" : './\(.*\)'`
-                       if test "z$head_points_at" = "z$name"
-                       then
-                               continue
-                       fi
-                       if test "$use_separate_remote" = '' &&
-                          test "z$origin" = "z$name"
-                       then
-                               continue
-                       fi
-                       echo "Pull: refs/heads/${name}:$remote_top/${name}"
-               done >>"$GIT_DIR/remotes/$origin" &&
-               case "$use_separate_remote" in
-               t)
-                       rm -f "refs/remotes/$origin/HEAD"
-                       git-symbolic-ref "refs/remotes/$origin/HEAD" \
-                               "refs/remotes/$origin/$head_points_at"
-               esac
+
+               # Tracking branch for the primary branch at the remote.
+               origin_track="$remote_top/$head_points_at" &&
+               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" \
+                       "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"
        esac
 
        case "$no_checkout" in
index 81c3a0cb6152b02ccdcaed607efcd500813ce607..ec506d956fa2858897c53b35d28db8083b7fdcec 100755 (executable)
@@ -3,12 +3,12 @@
 # Copyright (c) 2005 Linus Torvalds
 # Copyright (c) 2006 Junio C Hamano
 
-USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-u] [--amend] [-e] [--author <author>] [[-i | -o] <path>...]'
+USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit> | --amend] [-u] [-e] [--author <author>] [[-i | -o] <path>...]'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
+require_work_tree
 
 git-rev-parse --verify HEAD >/dev/null 2>&1 || initial_commit=t
-branch=$(GIT_DIR="$GIT_DIR" git-symbolic-ref HEAD)
 
 case "$0" in
 *status)
@@ -80,6 +80,7 @@ no_edit=
 log_given=
 log_message=
 verify=t
+quiet=
 verbose=
 signoff=
 force_author=
@@ -241,6 +242,10 @@ $1"
                signoff=t
                shift
                ;;
+       -q|--q|--qu|--qui|--quie|--quiet)
+               quiet=t
+               shift
+               ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t
                shift
@@ -279,9 +284,9 @@ esac
 
 case "$log_given" in
 tt*)
-       die "Only one of -c/-C/-F can be used." ;;
+       die "Only one of -c/-C/-F/--amend can be used." ;;
 *tm*|*mt*)
-       die "Option -m cannot be combined with -c/-C/-F." ;;
+       die "Option -m cannot be combined with -c/-C/-F/--amend." ;;
 esac
 
 case "$#,$also,$only,$amend" in
@@ -311,22 +316,16 @@ esac
 ################################################################
 # Prepare index to have a tree to be committed
 
-TOP=`git-rev-parse --show-cdup`
-if test -z "$TOP"
-then
-       TOP=./
-fi
-
 case "$all,$also" in
 t,)
        save_index &&
        (
-               cd "$TOP"
-               GIT_INDEX_FILE="$NEXT_INDEX"
-               export GIT_INDEX_FILE
+               cd_to_toplevel &&
+               GIT_INDEX_FILE="$NEXT_INDEX" &&
+               export GIT_INDEX_FILE &&
                git-diff-files --name-only -z |
                git-update-index --remove -z --stdin
-       )
+       ) || exit
        ;;
 ,t)
        save_index &&
@@ -334,11 +333,11 @@ t,)
 
        git-diff-files --name-only -z -- "$@"  |
        (
-               cd "$TOP"
-               GIT_INDEX_FILE="$NEXT_INDEX"
-               export GIT_INDEX_FILE
+               cd_to_toplevel &&
+               GIT_INDEX_FILE="$NEXT_INDEX" &&
+               export GIT_INDEX_FILE &&
                git-update-index --remove -z --stdin
-       )
+       ) || exit
        ;;
 ,)
        case "$#" in
@@ -350,19 +349,9 @@ t,)
                        refuse_partial "Cannot do a partial commit during a merge."
                fi
                TMP_INDEX="$GIT_DIR/tmp-index$$"
-               if test -z "$initial_commit"
-               then
-                       # make sure index is clean at the specified paths, or
-                       # they are additions.
-                       dirty_in_index=`git-diff-index --cached --name-status \
-                               --diff-filter=DMTU HEAD -- "$@"`
-                       test -z "$dirty_in_index" ||
-                       refuse_partial "Different in index and the last commit:
-$dirty_in_index"
-               fi
                commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
 
-               # Build the temporary index and update the real index
+               # Build a temporary index and update the real index
                # the same way.
                if test -z "$initial_commit"
                then
@@ -440,7 +429,9 @@ then
        fi
 elif test "$use_commit" != ""
 then
-       git-cat-file commit "$use_commit" | sed -e '1,/^$/d'
+       encoding=$(git config i18n.commitencoding || echo UTF-8)
+       git show -s --pretty=raw --encoding="$encoding" "$use_commit" |
+       sed -e '1,/^$/d' -e 's/^    //'
 elif test -f "$GIT_DIR/MERGE_MSG"
 then
        cat "$GIT_DIR/MERGE_MSG"
@@ -451,8 +442,11 @@ fi | git-stripspace >"$GIT_DIR"/COMMIT_EDITMSG
 
 case "$signoff" in
 t)
+       need_blank_before_signoff=
+       tail -n 1 "$GIT_DIR"/COMMIT_EDITMSG |
+       grep 'Signed-off-by:' >/dev/null || need_blank_before_signoff=yes
        {
-               echo
+               test -z "$need_blank_before_signoff" || echo
                git-var GIT_COMMITTER_IDENT | sed -e '
                        s/>.*/>/
                        s/^/Signed-off-by: /
@@ -471,15 +465,7 @@ if test -f "$GIT_DIR/MERGE_HEAD" && test -z "$no_edit"; then
 fi >>"$GIT_DIR"/COMMIT_EDITMSG
 
 # Author
-if test '' != "$force_author"
-then
-       GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
-       GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
-       test '' != "$GIT_AUTHOR_NAME" &&
-       test '' != "$GIT_AUTHOR_EMAIL" ||
-       die "malformed --author parameter"
-       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
-elif test '' != "$use_commit"
+if test '' != "$use_commit"
 then
        pick_author_script='
        /^author /{
@@ -502,13 +488,23 @@ then
                q
        }
        '
-       set_author_env=`git-cat-file commit "$use_commit" |
+       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
 fi
+if test '' != "$force_author"
+then
+       GIT_AUTHOR_NAME=`expr "z$force_author" : 'z\(.*[^ ]\) *<.*'` &&
+       GIT_AUTHOR_EMAIL=`expr "z$force_author" : '.*\(<.*\)'` &&
+       test '' != "$GIT_AUTHOR_NAME" &&
+       test '' != "$GIT_AUTHOR_EMAIL" ||
+       die "malformed --author parameter"
+       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+fi
 
 PARENTS="-p HEAD"
 if test -z "$initial_commit"
@@ -525,13 +521,14 @@ then
        current="$(git-rev-parse --verify HEAD)"
 else
        if [ -z "$(git-ls-files)" ]; then
-               echo >&2 Nothing to commit
+               echo >&2 'nothing to commit (use "git add file1 file2" to include for commit)'
                exit 1
        fi
        PARENTS=""
        rloga='commit (initial)'
        current=''
 fi
+set_reflog_action "$rloga"
 
 if test -z "$no_edit"
 then
@@ -606,7 +603,7 @@ then
        fi &&
        commit=$(cat "$GIT_DIR"/COMMIT_MSG | git-commit-tree $tree $PARENTS) &&
        rlogm=$(sed -e 1q "$GIT_DIR"/COMMIT_MSG) &&
-       git-update-ref -m "$rloga: $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
@@ -625,8 +622,17 @@ then
        git-rerere
 fi
 
-if test -x "$GIT_DIR"/hooks/post-commit && test "$ret" = 0
+if test "$ret" = 0
 then
-       "$GIT_DIR"/hooks/post-commit
+       if test -x "$GIT_DIR"/hooks/post-commit
+       then
+               "$GIT_DIR"/hooks/post-commit
+       fi
+       if test -z "$quiet"
+       then
+               echo "Created${initial_commit:+ initial} commit $commit"
+               git-diff-tree --shortstat --summary --root --no-commit-id HEAD --
+       fi
 fi
+
 exit "$ret"
index 0272d043d0b82dc6ad0ff4836fe8db8ed1e1d960..c1bcb001a51324ebcb66bfb0a695e20ff366df02 100644 (file)
 
 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
 
+#if !defined(__APPLE__) && !defined(__FreeBSD__)
+#define _XOPEN_SOURCE 600 /* glibc2 and AIX 5.3L need 500, OpenBSD needs 600 for S_ISLNK() */
+#define _XOPEN_SOURCE_EXTENDED 1 /* AIX 5.3L needs this */
+#endif
+#define _ALL_SOURCE 1
+#define _GNU_SOURCE 1
+#define _BSD_SOURCE 1
+
 #include <unistd.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <errno.h>
 #include <limits.h>
 #include <sys/param.h>
-#include <netinet/in.h>
 #include <sys/types.h>
 #include <dirent.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <fnmatch.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <assert.h>
+#include <regex.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <pwd.h>
+#include <inttypes.h>
+#undef _ALL_SOURCE /* AIX 5.3L defines a struct list with _ALL_SOURCE. */
+#include <grp.h>
+#define _ALL_SOURCE 1
+
+#ifndef NO_ICONV
+#include <iconv.h>
+#endif
 
 /* On most systems <limits.h> would have given us this, but
  * not on some systems (e.g. GNU/Hurd).
 extern void usage(const char *err) NORETURN;
 extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
 extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern void warn(const char *err, ...) __attribute__((format (printf, 1, 2)));
 
 extern void set_usage_routine(void (*routine)(const char *err) NORETURN);
 extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN);
 extern void set_error_routine(void (*routine)(const char *err, va_list params));
+extern void set_warn_routine(void (*routine)(const char *warn, va_list params));
 
 #ifdef NO_MMAP
 
@@ -60,17 +91,31 @@ extern void set_error_routine(void (*routine)(const char *err, va_list params));
 #define MAP_FAILED ((void*)-1)
 #endif
 
-#define mmap gitfakemmap
-#define munmap gitfakemunmap
-extern void *gitfakemmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);
-extern int gitfakemunmap(void *start, size_t length);
+#define mmap git_mmap
+#define munmap git_munmap
+extern void *git_mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern int git_munmap(void *start, size_t length);
+
+#define DEFAULT_PACKED_GIT_WINDOW_SIZE (1 * 1024 * 1024)
 
 #else /* NO_MMAP */
 
 #include <sys/mman.h>
+#define DEFAULT_PACKED_GIT_WINDOW_SIZE \
+       (sizeof(void*) >= 8 \
+               ?  1 * 1024 * 1024 * 1024 \
+               : 32 * 1024 * 1024)
 
 #endif /* NO_MMAP */
 
+#define DEFAULT_PACKED_GIT_LIMIT \
+       ((1024L * 1024L) * (sizeof(void*) >= 8 ? 8192 : 256))
+
+#ifdef NO_PREAD
+#define pread git_pread
+extern ssize_t git_pread(int fd, void *buf, size_t count, off_t offset);
+#endif
+
 #ifdef NO_SETENV
 #define setenv gitsetenv
 extern int gitsetenv(const char *, const char *, int);
@@ -91,11 +136,17 @@ extern char *gitstrcasestr(const char *haystack, const char *needle);
 extern size_t gitstrlcpy(char *, const char *, size_t);
 #endif
 
+extern void release_pack_memory(size_t);
+
 static inline char* xstrdup(const char *str)
 {
        char *ret = strdup(str);
-       if (!ret)
-               die("Out of memory, strdup failed");
+       if (!ret) {
+               release_pack_memory(strlen(str) + 1);
+               ret = strdup(str);
+               if (!ret)
+                       die("Out of memory, strdup failed");
+       }
        return ret;
 }
 
@@ -104,8 +155,14 @@ static inline void *xmalloc(size_t size)
        void *ret = malloc(size);
        if (!ret && !size)
                ret = malloc(1);
-       if (!ret)
-               die("Out of memory, malloc failed");
+       if (!ret) {
+               release_pack_memory(size);
+               ret = malloc(size);
+               if (!ret && !size)
+                       ret = malloc(1);
+               if (!ret)
+                       die("Out of memory, malloc failed");
+       }
 #ifdef XMALLOC_POISON
        memset(ret, 0xA5, size);
 #endif
@@ -117,8 +174,14 @@ static inline void *xrealloc(void *ptr, size_t size)
        void *ret = realloc(ptr, size);
        if (!ret && !size)
                ret = realloc(ptr, 1);
-       if (!ret)
-               die("Out of memory, realloc failed");
+       if (!ret) {
+               release_pack_memory(size);
+               ret = realloc(ptr, size);
+               if (!ret && !size)
+                       ret = realloc(ptr, 1);
+               if (!ret)
+                       die("Out of memory, realloc failed");
+       }
        return ret;
 }
 
@@ -127,8 +190,29 @@ static inline void *xcalloc(size_t nmemb, size_t size)
        void *ret = calloc(nmemb, size);
        if (!ret && (!nmemb || !size))
                ret = calloc(1, 1);
-       if (!ret)
-               die("Out of memory, calloc failed");
+       if (!ret) {
+               release_pack_memory(nmemb * size);
+               ret = calloc(nmemb, size);
+               if (!ret && (!nmemb || !size))
+                       ret = calloc(1, 1);
+               if (!ret)
+                       die("Out of memory, calloc failed");
+       }
+       return ret;
+}
+
+static inline void *xmmap(void *start, size_t length,
+       int prot, int flags, int fd, off_t offset)
+{
+       void *ret = mmap(start, length, prot, flags, fd, offset);
+       if (ret == MAP_FAILED) {
+               if (!length)
+                       return NULL;
+               release_pack_memory(length);
+               ret = mmap(start, length, prot, flags, fd, offset);
+               if (ret == MAP_FAILED)
+                       die("Out of memory? mmap failed: %s", strerror(errno));
+       }
        return ret;
 }
 
index 7bac16e9463a0d0119218458b853570be6188ff4..870554eade66d1744c561d4cba33c41dd7701e55 100755 (executable)
@@ -2,9 +2,8 @@
 
 # Known limitations:
 # - does not propagate permissions
-# - tells "ready for commit" even when things could not be completed
-#   (not sure this is true anymore, more testing is needed)
-# - does not handle whitespace in pathnames at all.
+# - error handling has not been extensively tested
+#
 
 use strict;
 use Getopt::Std;
@@ -16,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_v, $opt_c, $opt_f, $opt_a, $opt_m );
+our ($opt_h, $opt_P, $opt_p, $opt_v, $opt_c, $opt_f, $opt_a, $opt_m );
 
-getopts('hpvcfam:');
+getopts('hPpvcfam:');
 
 $opt_h && usage();
 
@@ -90,7 +89,7 @@ if ($parent) {
            last;
        }; # found it
     }
-    die "Did not find $parent in the parents for this commit!" if !$found;
+    die "Did not find $parent in the parents for this commit!" if !$found and !$opt_P;
 } else { # we don't have a parent from the cmdline...
     if (@parents == 1) { # it's safe to get it from the commit
        $parent = $parents[0];
@@ -115,47 +114,40 @@ if ($opt_a) {
 }
 close MSG;
 
-my (@afiles, @dfiles, @mfiles, @dirs);
-my @files = safe_pipe_capture('git-diff-tree', '-r', $parent, $commit);
-#print @files;
-$? && die "Error in git-diff-tree";
-foreach my $f (@files) {
-    chomp $f;
-    my @fields = split(m!\s+!, $f);
-    if ($fields[4] eq 'A') {
-        my $path = $fields[5];
-       push @afiles, $path;
-        # add any needed parent directories
-       $path = dirname $path;
-       while (!-d $path and ! grep { $_ eq $path } @dirs) {
-           unshift @dirs, $path;
-           $path = dirname $path;
-       }
-    }
-    if ($fields[4] eq 'M') {
-       push @mfiles, $fields[5];
-    }
-    if ($fields[4] eq 'D') {
-       push @dfiles, $fields[5];
-    }
+`git-diff-tree --binary -p $parent $commit >.cvsexportcommit.diff`;# || die "Cannot diff";
+
+## apply non-binary changes
+my $fuzz = $opt_p ? 0 : 2;
+
+print "Checking if patch will apply\n";
+
+my @stat;
+open APPLY, "GIT_DIR= git-apply -C$fuzz --binary --summary --numstat<.cvsexportcommit.diff|" || die "cannot patch";
+@stat=<APPLY>;
+close APPLY || die "Cannot patch";
+my (@bfiles,@files,@afiles,@dfiles);
+chomp @stat;
+foreach (@stat) {
+       push (@bfiles,$1) if m/^-\t-\t(.*)$/;
+       push (@files, $1) if m/^-\t-\t(.*)$/;
+       push (@files, $1) if m/^\d+\t\d+\t(.*)$/;
+       push (@afiles,$1) if m/^ create mode [0-7]+ (.*)$/;
+       push (@dfiles,$1) if m/^ delete mode [0-7]+ (.*)$/;
 }
-my (@binfiles, @abfiles, @dbfiles, @bfiles, @mbfiles);
-@binfiles = grep m/^Binary files/, safe_pipe_capture('git-diff-tree', '-p', $parent, $commit);
-map { chomp } @binfiles;
-@abfiles = grep s/^Binary files \/dev\/null and b\/(.*) differ$/$1/, @binfiles;
-@dbfiles = grep s/^Binary files a\/(.*) and \/dev\/null differ$/$1/, @binfiles;
-@mbfiles = grep s/^Binary files a\/(.*) and b\/(.*) differ$/$1/, @binfiles;
-push @bfiles, @abfiles;
-push @bfiles, @dbfiles;
-push @bfiles, @mbfiles;
-push @mfiles, @mbfiles;
-
-$opt_v && print "The commit affects:\n ";
-$opt_v && print join ("\n ", @afiles,@mfiles,@dfiles) . "\n\n";
-undef @files; # don't need it anymore
+map { s/^"(.*)"$/$1/g } @bfiles,@files;
+map { s/\\([0-7]{3})/sprintf('%c',oct $1)/eg } @bfiles,@files;
 
 # check that the files are clean and up to date according to cvs
 my $dirty;
+my @dirs;
+foreach my $p (@afiles) {
+    my $path = dirname $p;
+    while (!-d $path and ! grep { $_ eq $path } @dirs) {
+       unshift @dirs, $path;
+       $path = dirname $path;
+    }
+}
+
 foreach my $d (@dirs) {
     if (-e $d) {
        $dirty = 1;
@@ -178,7 +170,8 @@ foreach my $f (@afiles) {
     }
 }
 
-foreach my $f (@mfiles, @dfiles) {
+foreach my $f (@files) {
+    next if grep { $_ eq $f } @afiles;
     # TODO:we need to handle removed in cvs
     my @status = grep(m/^File/,  safe_pipe_capture('cvs', '-q', 'status' ,$f));
     if (@status > 1) { warn 'Strange! cvs status returned more than one line?'};
@@ -195,78 +188,18 @@ if ($dirty) {
     }
 }
 
-###
-### NOTE: if you are planning to die() past this point
-###       you MUST call cleanupcvs(@files) before die()
-###
-
+print "Applying\n";
+`GIT_DIR= git-apply -C$fuzz --binary --summary --numstat --apply <.cvsexportcommit.diff` || die "cannot patch";
 
-print "Creating new directories\n";
+print "Patch applied successfully. Adding new files and directories to CVS\n";
+my $dirtypatch = 0;
 foreach my $d (@dirs) {
-    unless (mkdir $d) {
-        warn "Could not mkdir $d: $!";
-       $dirty = 1;
-    }
-    `cvs add $d`;
-    if ($?) {
-       $dirty = 1;
+    if (system('cvs','add',$d)) {
+       $dirtypatch = 1;
        warn "Failed to cvs add directory $d -- you may need to do it manually";
     }
 }
 
-print "'Patching' binary files\n";
-
-foreach my $f (@bfiles) {
-    # check that the file in cvs matches the "old" file
-    # extract the file to $tmpdir and compare with cmp
-    if (not(grep { $_ eq $f } @afiles)) {
-        my $tree = safe_pipe_capture('git-rev-parse', "$parent^{tree}");
-       chomp $tree;
-       my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
-       chomp $blob;
-        `git-cat-file blob $blob > $tmpdir/blob`;
-        if (system('cmp', '-s', $f, "$tmpdir/blob")) {
-           warn "Binary file $f in CVS does not match parent.\n";
-           if (not $opt_f) {
-               $dirty = 1;
-               next;
-           }
-        }
-    }
-    if (not(grep { $_ eq $f } @dfiles)) {
-       my $tree = safe_pipe_capture('git-rev-parse', "$commit^{tree}");
-       chomp $tree;
-       my $blob = `git-ls-tree $tree "$f" | cut -f 1 | cut -d ' ' -f 3`;
-       chomp $blob;
-       # replace with the new file
-       `git-cat-file blob $blob > $f`;
-    }
-
-    # TODO: something smart with file modes
-
-}
-if ($dirty) {
-    cleanupcvs(@files);
-    die "Exiting: Binary files in CVS do not match parent";
-}
-
-## apply non-binary changes
-my $fuzz = $opt_p ? 0 : 2;
-
-print "Patching non-binary files\n";
-
-if (scalar(@afiles)+scalar(@dfiles)+scalar(@mfiles) != scalar(@bfiles)) {
-    print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;
-}
-
-my $dirtypatch = 0;
-if (($? >> 8) == 2) {
-    cleanupcvs(@files);
-    die "Exiting: Patch reported serious trouble -- you will have to apply this patch manually";
-} elsif (($? >> 8) == 1) { # some hunks failed to apply
-    $dirtypatch = 1;
-}
-
 foreach my $f (@afiles) {
     if (grep { $_ eq $f } @bfiles) {
       system('cvs', 'add','-kb',$f);
@@ -274,7 +207,7 @@ foreach my $f (@afiles) {
       system('cvs', 'add', $f);
     }
     if ($?) {
-       $dirty = 1;
+       $dirtypatch = 1;
        warn "Failed to cvs add $f -- you may need to do it manually";
     }
 }
@@ -282,35 +215,40 @@ foreach my $f (@afiles) {
 foreach my $f (@dfiles) {
     system('cvs', 'rm', '-f', $f);
     if ($?) {
-       $dirty = 1;
+       $dirtypatch = 1;
        warn "Failed to cvs rm -f $f -- you may need to do it manually";
     }
 }
 
 print "Commit to CVS\n";
-print "Patch: $title\n";
-my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);
-my $cmd = "cvs commit -F .msg $commitfiles";
+print "Patch title (first comment line): $title\n";
+my @commitfiles = map { unless (m/\s/) { '\''.$_.'\''; } else { $_; }; } (@files);
+my $cmd = "cvs commit -F .msg @commitfiles";
 
 if ($dirtypatch) {
     print "NOTE: One or more hunks failed to apply cleanly.\n";
-    print "Resolve the conflicts and then commit using:\n";
+    print "You'll need to apply the patch in .cvsexportcommit.diff manually\n";
+    print "using a patch program. After applying the patch and resolving the\n";
+    print "problems you may commit using:";
     print "\n    $cmd\n\n";
     exit(1);
 }
 
-
 if ($opt_c) {
     print "Autocommit\n  $cmd\n";
-    print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @afiles, @mfiles, @dfiles);
+    print safe_pipe_capture('cvs', 'commit', '-F', '.msg', @files);
     if ($?) {
-       cleanupcvs(@files);
        die "Exiting: The commit did not succeed";
     }
     print "Committed successfully to CVS\n";
 } else {
     print "Ready for you to commit, just run:\n\n   $cmd\n";
 }
+
+# clean up
+unlink(".cvsexportcommit.diff");
+unlink(".msg");
+
 sub usage {
        print STDERR <<END;
 Usage: GIT_DIR=/path/to/.git ${\basename $0} [-h] [-p] [-v] [-c] [-f] [-m msgprefix] [ parent ] commit
@@ -318,17 +256,6 @@ END
        exit(1);
 }
 
-# ensure cvs is clean before we die
-sub cleanupcvs {
-    my @files = @_;
-    foreach my $f (@files) {
-       system('cvs', '-q', 'update', '-C', $f);
-       if ($?) {
-           warn "Warning! Failed to cleanup state of $f\n";
-       }
-    }
-}
-
 # An alternative to `command` that allows input to be passed as an array
 # to work around shell problems with weird characters in arguments
 # if the exec returns non-zero we die
@@ -342,3 +269,16 @@ sub safe_pipe_capture {
     }
     return wantarray ? @output : join('',@output);
 }
+
+sub safe_pipe_capture_blob {
+    my $output;
+    if (my $pid = open my $child, '-|') {
+        local $/;
+       undef $/;
+       $output = (<$child>);
+       close $child or die join(' ',@_).": $! $?";
+    } else {
+       exec(@_) or die "$! $?"; # exec() can fail the executable can't be found
+    }
+    return $output;
+}
index b54a9486d2703120ca98a176e4b52b3bf8f91aa6..1a1ba7b1a6f779773702ae3c1efb56425c31beb4 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);
+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);
 my (%conv_author_name, %conv_author_email);
 
 sub usage() {
@@ -37,7 +37,7 @@ sub usage() {
 Usage: ${\basename $0}     # fetch/update GIT from CVS
        [-o branch-for-HEAD] [-h] [-v] [-d CVSROOT] [-A author-conv-file]
        [-p opts-for-cvsps] [-C GIT_repository] [-z fuzz] [-i] [-k] [-u]
-       [-s subst] [-m] [-M regex] [-S regex] [CVS_module]
+       [-s subst] [-a] [-m] [-M regex] [-S regex] [CVS_module]
 END
        exit(1);
 }
@@ -85,26 +85,56 @@ sub write_author_info($) {
        close ($f);
 }
 
-getopts("hivmkuo:d:p:C:z:s:M:P:A:S:L:") or usage();
+# convert getopts specs for use by git-repo-config
+sub read_repo_config {
+    # Split the string between characters, unless there is a ':'
+    # So "abc:de" becomes ["a", "b", "c:", "d", "e"]
+       my @opts = split(/ *(?!:)/, shift);
+       foreach my $o (@opts) {
+               my $key = $o;
+               $key =~ s/://g;
+               my $arg = 'git-repo-config';
+               $arg .= ' --bool' if ($o !~ /:$/);
+
+        chomp(my $tmp = `$arg --get cvsimport.$key`);
+               if ($tmp && !($arg =~ /--bool/ && $tmp eq 'false')) {
+            no strict 'refs';
+            my $opt_name = "opt_" . $key;
+            if (!$$opt_name) {
+                $$opt_name = $tmp;
+            }
+               }
+       }
+    if (@ARGV == 0) {
+        chomp(my $module = `git-repo-config --get cvsimport.module`);
+        push(@ARGV, $module);
+    }
+}
+
+my $opts = "haivmkuo:d:p:C:z:s:M:P:A:S:L:";
+read_repo_config($opts);
+getopts($opts) or usage();
 usage if $opt_h;
 
 @ARGV <= 1 or usage();
 
-if($opt_d) {
+if ($opt_d) {
        $ENV{"CVSROOT"} = $opt_d;
-} elsif(-f 'CVS/Root') {
+} elsif (-f 'CVS/Root') {
        open my $f, '<', 'CVS/Root' or die 'Failed to open CVS/Root';
        $opt_d = <$f>;
        chomp $opt_d;
        close $f;
        $ENV{"CVSROOT"} = $opt_d;
-} elsif($ENV{"CVSROOT"}) {
+} elsif ($ENV{"CVSROOT"}) {
        $opt_d = $ENV{"CVSROOT"};
 } else {
        die "CVSROOT needs to be set";
 }
 $opt_o ||= "origin";
 $opt_s ||= "-";
+$opt_a ||= 0;
+
 my $git_tree = $opt_C;
 $git_tree ||= ".";
 
@@ -129,6 +159,11 @@ if ($opt_M) {
        push (@mergerx, qr/$opt_M/);
 }
 
+# Remember UTC of our starting time
+# we'll want to avoid importing commits
+# that are too recent
+our $starttime = time();
+
 select(STDERR); $|=1; select(STDOUT);
 
 
@@ -141,7 +176,7 @@ use File::Temp qw(tempfile);
 use POSIX qw(strftime dup2);
 
 sub new {
-       my($what,$repo,$subdir) = @_;
+       my ($what,$repo,$subdir) = @_;
        $what=ref($what) if ref($what);
 
        my $self = {};
@@ -161,24 +196,38 @@ sub new {
 sub conn {
        my $self = shift;
        my $repo = $self->{'fullrep'};
-       if($repo =~ s/^:pserver:(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
-               my($user,$pass,$serv,$port) = ($1,$2,$3,$4);
+       if ($repo =~ s/^:pserver(?:([^:]*)):(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?//) {
+               my ($param,$user,$pass,$serv,$port) = ($1,$2,$3,$4,$5);
+
+               my ($proxyhost,$proxyport);
+               if ($param && ($param =~ m/proxy=([^;]+)/)) {
+                       $proxyhost = $1;
+                       # Default proxyport, if not specified, is 8080.
+                       $proxyport = 8080;
+                       if ($ENV{"CVS_PROXY_PORT"}) {
+                               $proxyport = $ENV{"CVS_PROXY_PORT"};
+                       }
+                       if ($param =~ m/proxyport=([^;]+)/) {
+                               $proxyport = $1;
+                       }
+               }
+
                $user="anonymous" unless defined $user;
                my $rr2 = "-";
-               unless($port) {
+               unless ($port) {
                        $rr2 = ":pserver:$user\@$serv:$repo";
                        $port=2401;
                }
                my $rr = ":pserver:$user\@$serv:$port$repo";
 
-               unless($pass) {
+               unless ($pass) {
                        open(H,$ENV{'HOME'}."/.cvspass") and do {
                                # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-                               while(<H>) {
+                               while (<H>) {
                                        chomp;
                                        s/^\/\d+\s+//;
                                        my ($w,$p) = split(/\s/,$_,2);
-                                       if($w eq $rr or $w eq $rr2) {
+                                       if ($w eq $rr or $w eq $rr2) {
                                                $pass = $p;
                                                last;
                                        }
@@ -187,15 +236,45 @@ sub conn {
                }
                $pass="A" unless $pass;
 
-               my $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
-               die "Socket to $serv: $!\n" unless defined $s;
+               my ($s, $rep);
+               if ($proxyhost) {
+
+                       # Use a HTTP Proxy. Only works for HTTP proxies that
+                       # don't require user authentication
+                       #
+                       # See: http://www.ietf.org/rfc/rfc2817.txt
+
+                       $s = IO::Socket::INET->new(PeerHost => $proxyhost, PeerPort => $proxyport);
+                       die "Socket to $proxyhost: $!\n" unless defined $s;
+                       $s->write("CONNECT $serv:$port HTTP/1.1\r\nHost: $serv:$port\r\n\r\n")
+                               or die "Write to $proxyhost: $!\n";
+                       $s->flush();
+
+                       $rep = <$s>;
+
+                       # The answer should look like 'HTTP/1.x 2yy ....'
+                       if (!($rep =~ m#^HTTP/1\.. 2[0-9][0-9]#)) {
+                               die "Proxy connect: $rep\n";
+                       }
+                       # Skip up to the empty line of the proxy server output
+                       # including the response headers.
+                       while ($rep = <$s>) {
+                               last if (!defined $rep ||
+                                        $rep eq "\n" ||
+                                        $rep eq "\r\n");
+                       }
+               } else {
+                       $s = IO::Socket::INET->new(PeerHost => $serv, PeerPort => $port);
+                       die "Socket to $serv: $!\n" unless defined $s;
+               }
+
                $s->write("BEGIN AUTH REQUEST\n$repo\n$user\n$pass\nEND AUTH REQUEST\n")
                        or die "Write to $serv: $!\n";
                $s->flush();
 
-               my $rep = <$s>;
+               $rep = <$s>;
 
-               if($rep ne "I LOVE YOU\n") {
+               if ($rep ne "I LOVE YOU\n") {
                        $rep="<unknown>" unless $rep;
                        die "AuthReply: $rep\n";
                }
@@ -227,7 +306,7 @@ sub conn {
                    }
                }
 
-               unless($pid) {
+               unless ($pid) {
                        $pr->writer();
                        $pw->reader();
                        dup2($pw->fileno(),0);
@@ -250,7 +329,7 @@ sub conn {
        $self->{'socketo'}->flush();
 
        chomp(my $rep=$self->readline());
-       if($rep !~ s/^Valid-requests\s*//) {
+       if ($rep !~ s/^Valid-requests\s*//) {
                $rep="<unknown>" unless $rep;
                die "Expected Valid-requests from server, but got: $rep\n";
        }
@@ -262,14 +341,14 @@ sub conn {
 }
 
 sub readline {
-       my($self) = @_;
+       my ($self) = @_;
        return $self->{'socketi'}->getline();
 }
 
 sub _file {
        # Request a file with a given revision.
        # Trial and error says this is a good way to do it. :-/
-       my($self,$fn,$rev) = @_;
+       my ($self,$fn,$rev) = @_;
        $self->{'socketo'}->write("Argument -N\n") or return undef;
        $self->{'socketo'}->write("Argument -P\n") or return undef;
        # -kk: Linus' version doesn't use it - defaults to off
@@ -291,12 +370,12 @@ sub _file {
 sub _line {
        # Read a line from the server.
        # ... except that 'line' may be an entire file. ;-)
-       my($self, $fh) = @_;
+       my ($self, $fh) = @_;
        die "Not in lines" unless defined $self->{'lines'};
 
        my $line;
        my $res=0;
-       while(defined($line = $self->readline())) {
+       while (defined($line = $self->readline())) {
                # M U gnupg-cvs-rep/AUTHORS
                # Updated gnupg-cvs-rep/
                # /daten/src/rsync/gnupg-cvs-rep/AUTHORS
@@ -305,7 +384,7 @@ sub _line {
                # 0
                # ok
 
-               if($line =~ s/^(?:Created|Updated) //) {
+               if ($line =~ s/^(?:Created|Updated) //) {
                        $line = $self->readline(); # path
                        $line = $self->readline(); # Entries line
                        my $mode = $self->readline(); chomp $mode;
@@ -316,12 +395,12 @@ sub _line {
                        die "Duh: Filesize $cnt" if $cnt !~ /^\d+$/;
                        $line="";
                        $res = $self->_fetchfile($fh, $cnt);
-               } elsif($line =~ s/^ //) {
+               } elsif ($line =~ s/^ //) {
                        print $fh $line;
                        $res += length($line);
-               } elsif($line =~ /^M\b/) {
+               } elsif ($line =~ /^M\b/) {
                        # output, do nothing
-               } elsif($line =~ /^Mbinary\b/) {
+               } elsif ($line =~ /^Mbinary\b/) {
                        my $cnt;
                        die "EOF from server after 'Mbinary'" unless defined ($cnt = $self->readline());
                        chomp $cnt;
@@ -330,12 +409,12 @@ sub _line {
                        $res += $self->_fetchfile($fh, $cnt);
                } else {
                        chomp $line;
-                       if($line eq "ok") {
+                       if ($line eq "ok") {
                                # print STDERR "S: ok (".length($res).")\n";
                                return $res;
-                       } elsif($line =~ s/^E //) {
+                       } elsif ($line =~ s/^E //) {
                                # print STDERR "S: $line\n";
-                       } elsif($line =~ /^(Remove-entry|Removed) /i) {
+                       } elsif ($line =~ /^(Remove-entry|Removed) /i) {
                                $line = $self->readline(); # filename
                                $line = $self->readline(); # OK
                                chomp $line;
@@ -349,7 +428,7 @@ sub _line {
        return undef;
 }
 sub file {
-       my($self,$fn,$rev) = @_;
+       my ($self,$fn,$rev) = @_;
        my $res;
 
        my ($fh, $name) = tempfile('gitcvs.XXXXXX', 
@@ -373,7 +452,7 @@ sub _fetchfile {
        my ($self, $fh, $cnt) = @_;
        my $res = 0;
        my $bufsize = 1024 * 1024;
-       while($cnt) {
+       while ($cnt) {
            if ($bufsize > $cnt) {
                $bufsize = $cnt;
            }
@@ -394,7 +473,7 @@ my $cvs = CVSconn->new($opt_d, $cvs_tree);
 
 
 sub pdate($) {
-       my($d) = @_;
+       my ($d) = @_;
        m#(\d{2,4})/(\d\d)/(\d\d)\s(\d\d):(\d\d)(?::(\d\d))?#
                or die "Unparseable date: $d\n";
        my $y=$1; $y-=1900 if $y>1900;
@@ -402,22 +481,22 @@ sub pdate($) {
 }
 
 sub pmode($) {
-       my($mode) = @_;
+       my ($mode) = @_;
        my $m = 0;
        my $mm = 0;
        my $um = 0;
        for my $x(split(//,$mode)) {
-               if($x eq ",") {
+               if ($x eq ",") {
                        $m |= $mm&$um;
                        $mm = 0;
                        $um = 0;
-               } elsif($x eq "u") { $um |= 0700;
-               } elsif($x eq "g") { $um |= 0070;
-               } elsif($x eq "o") { $um |= 0007;
-               } elsif($x eq "r") { $mm |= 0444;
-               } elsif($x eq "w") { $mm |= 0222;
-               } elsif($x eq "x") { $mm |= 0111;
-               } elsif($x eq "=") { # do nothing
+               } elsif ($x eq "u") { $um |= 0700;
+               } elsif ($x eq "g") { $um |= 0070;
+               } elsif ($x eq "o") { $um |= 0007;
+               } elsif ($x eq "r") { $mm |= 0444;
+               } elsif ($x eq "w") { $mm |= 0222;
+               } elsif ($x eq "x") { $mm |= 0111;
+               } elsif ($x eq "=") { # do nothing
                } else { die "Unknown mode: $mode\n";
                }
        }
@@ -441,7 +520,7 @@ sub get_headref ($$) {
     my $git_dir = shift; 
     
     my $f = "$git_dir/refs/heads/$name";
-    if(open(my $fh, $f)) {
+    if (open(my $fh, $f)) {
            chomp(my $r = <$fh>);
            is_sha1($r) or die "Cannot get head id for $name ($r): $!";
            return $r;
@@ -468,8 +547,8 @@ $orig_git_index = $ENV{GIT_INDEX_FILE} if exists $ENV{GIT_INDEX_FILE};
 
 my %index; # holds filenames of one index per branch
 
-unless(-d $git_dir) {
-       system("git-init-db");
+unless (-d $git_dir) {
+       system("git-init");
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
        system("git-read-tree");
        die "Cannot init an empty tree: $?\n" if $?;
@@ -487,7 +566,7 @@ unless(-d $git_dir) {
        chomp ($last_branch = <F>);
        $last_branch = basename($last_branch);
        close(F);
-       unless($last_branch) {
+       unless ($last_branch) {
                warn "Cannot read the last branch name: $! -- assuming 'master'\n";
                $last_branch = "master";
        }
@@ -498,7 +577,7 @@ unless(-d $git_dir) {
        my $fmt = '($ref, $author) = (%(refname), %(author));';
        open(H, "git-for-each-ref --perl --format='$fmt' refs/heads |") or
                die "Cannot run git-for-each-ref: $!\n";
-       while(defined(my $entry = <H>)) {
+       while (defined(my $entry = <H>)) {
                my ($ref, $author);
                eval($entry) || die "cannot eval refs list: $@";
                my ($head) = ($ref =~ m|^refs/heads/(.*)|);
@@ -524,11 +603,13 @@ if ($opt_A) {
 # run cvsps into a file unless we are getting
 # it passed as a file via $opt_P
 #
+my $cvspsfile;
 unless ($opt_P) {
        print "Running cvsps...\n" if $opt_v;
        my $pid = open(CVSPS,"-|");
+       my $cvspsfh;
        die "Cannot fork: $!\n" unless defined $pid;
-       unless($pid) {
+       unless ($pid) {
                my @opt;
                @opt = split(/,/,$opt_p) if defined $opt_p;
                unshift @opt, '-z', $opt_z if defined $opt_z;
@@ -539,18 +620,18 @@ unless ($opt_P) {
                exec("cvsps","--norc",@opt,"-u","-A",'--root',$opt_d,$cvs_tree);
                die "Could not start cvsps: $!\n";
        }
-       my ($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
-                                            DIR => File::Spec->tmpdir());
+       ($cvspsfh, $cvspsfile) = tempfile('gitXXXXXX', SUFFIX => '.cvsps',
+                                         DIR => File::Spec->tmpdir());
        while (<CVSPS>) {
            print $cvspsfh $_;
        }
        close CVSPS;
        close $cvspsfh;
-       $opt_P = $cvspsfile;
+} else {
+       $cvspsfile = $opt_P;
 }
 
-
-open(CVS, "<$opt_P") or die $!;
+open(CVS, "<$cvspsfile") or die $!;
 
 ## cvsps output:
 #---------------------
@@ -598,8 +679,8 @@ sub write_tree () {
        return $tree;
 }
 
-my($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
-my(@old,@new,@skipped,%ignorebranch);
+my ($patchset,$date,$author_name,$author_email,$branch,$ancestor,$tag,$logmsg);
+my (@old,@new,@skipped,%ignorebranch);
 
 # commits that cvsps cannot place anywhere...
 $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
@@ -607,7 +688,7 @@ $ignorebranch{'#CVSPS_NO_BRANCH'} = 1;
 sub commit {
        if ($branch eq $opt_o && !$index{branch} && !get_headref($branch, $git_dir)) {
            # looks like an initial commit
-           # use the index primed by git-init-db
+           # use the index primed by git-init
            $ENV{GIT_INDEX_FILE} = '.git/index';
            $index{$branch} = '.git/index';
        } else {
@@ -640,7 +721,7 @@ sub commit {
        foreach my $rx (@mergerx) {
                next unless $logmsg =~ $rx && $1;
                my $mparent = $1 eq 'HEAD' ? $opt_o : $1;
-               if(my $sha1 = get_headref($mparent, $git_dir)) {
+               if (my $sha1 = get_headref($mparent, $git_dir)) {
                        push @commit_args, '-p', $mparent;
                        print "Merge parent branch: $mparent\n" if $opt_v;
                }
@@ -681,9 +762,9 @@ sub commit {
        system("git-update-ref refs/heads/$branch $cid") == 0
                or die "Cannot write branch $branch for update: $!\n";
 
-       if($tag) {
-               my($in, $out) = ('','');
-               my($xtag) = $tag;
+       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;
@@ -718,25 +799,25 @@ sub commit {
 };
 
 my $commitcount = 1;
-while(<CVS>) {
+while (<CVS>) {
        chomp;
-       if($state == 0 and /^-+$/) {
+       if ($state == 0 and /^-+$/) {
                $state = 1;
-       } elsif($state == 0) {
+       } elsif ($state == 0) {
                $state = 1;
                redo;
-       } elsif(($state==0 or $state==1) and s/^PatchSet\s+//) {
+       } elsif (($state==0 or $state==1) and s/^PatchSet\s+//) {
                $patchset = 0+$_;
                $state=2;
-       } elsif($state == 2 and s/^Date:\s+//) {
+       } elsif ($state == 2 and s/^Date:\s+//) {
                $date = pdate($_);
-               unless($date) {
+               unless ($date) {
                        print STDERR "Could not parse date: $_\n";
                        $state=0;
                        next;
                }
                $state=3;
-       } elsif($state == 3 and s/^Author:\s+//) {
+       } elsif ($state == 3 and s/^Author:\s+//) {
                s/\s+$//;
                if (/^(.*?)\s+<(.*)>/) {
                    ($author_name, $author_email) = ($1, $2);
@@ -747,55 +828,64 @@ while(<CVS>) {
                    $author_name = $author_email = $_;
                }
                $state = 4;
-       } elsif($state == 4 and s/^Branch:\s+//) {
+       } elsif ($state == 4 and s/^Branch:\s+//) {
                s/\s+$//;
                s/[\/]/$opt_s/g;
                $branch = $_;
                $state = 5;
-       } elsif($state == 5 and s/^Ancestor branch:\s+//) {
+       } elsif ($state == 5 and s/^Ancestor branch:\s+//) {
                s/\s+$//;
                $ancestor = $_;
                $ancestor = $opt_o if $ancestor eq "HEAD";
                $state = 6;
-       } elsif($state == 5) {
+       } elsif ($state == 5) {
                $ancestor = undef;
                $state = 6;
                redo;
-       } elsif($state == 6 and s/^Tag:\s+//) {
+       } elsif ($state == 6 and s/^Tag:\s+//) {
                s/\s+$//;
-               if($_ eq "(none)") {
+               if ($_ eq "(none)") {
                        $tag = undef;
                } else {
                        $tag = $_;
                }
                $state = 7;
-       } elsif($state == 7 and /^Log:/) {
+       } elsif ($state == 7 and /^Log:/) {
                $logmsg = "";
                $state = 8;
-       } elsif($state == 8 and /^Members:/) {
+       } elsif ($state == 8 and /^Members:/) {
                $branch = $opt_o if $branch eq "HEAD";
-               if(defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
+               if (defined $branch_date{$branch} and $branch_date{$branch} >= $date) {
                        # skip
                        print "skip patchset $patchset: $date before $branch_date{$branch}\n" if $opt_v;
                        $state = 11;
                        next;
                }
+               if (!$opt_a && $starttime - 300 - (defined $opt_z ? $opt_z : 300) <= $date) {
+                       # skip if the commit is too recent
+                       # that the cvsps default fuzz is 300s, we give ourselves another
+                       # 300s just in case -- this also prevents skipping commits
+                       # due to server clock drift
+                       print "skip patchset $patchset: $date too recent\n" if $opt_v;
+                       $state = 11;
+                       next;
+               }
                if (exists $ignorebranch{$branch}) {
                        print STDERR "Skipping $branch\n";
                        $state = 11;
                        next;
                }
-               if($ancestor) {
-                       if($ancestor eq $branch) {
+               if ($ancestor) {
+                       if ($ancestor eq $branch) {
                                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/refs/heads/$branch") {
                                print STDERR "Branch $branch already exists!\n";
                                $state=11;
                                next;
                        }
-                       unless(open(H,"$git_dir/refs/heads/$ancestor")) {
+                       unless (open(H,"$git_dir/refs/heads/$ancestor")) {
                                print STDERR "Branch $ancestor does not exist!\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
@@ -803,7 +893,7 @@ while(<CVS>) {
                        }
                        chomp(my $id = <H>);
                        close(H);
-                       unless(open(H,"> $git_dir/refs/heads/$branch")) {
+                       unless (open(H,"> $git_dir/refs/heads/$branch")) {
                                print STDERR "Could not create branch $branch: $!\n";
                                $ignorebranch{$branch} = 1;
                                $state=11;
@@ -816,9 +906,9 @@ while(<CVS>) {
                }
                $last_branch = $branch if $branch ne $last_branch;
                $state = 9;
-       } elsif($state == 8) {
+       } elsif ($state == 8) {
                $logmsg .= "$_\n";
-       } elsif($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
+       } elsif ($state == 9 and /^\s+(.+?):(INITIAL|\d+(?:\.\d+)+)->(\d+(?:\.\d+)+)\s*$/) {
 #      VERSION:1.96->1.96.2.1
                my $init = ($2 eq "INITIAL");
                my $fn = $1;
@@ -831,7 +921,7 @@ while(<CVS>) {
                }
                print "Fetching $fn   v $rev\n" if $opt_v;
                my ($tmpname, $size) = $cvs->file($fn,$rev);
-               if($size == -1) {
+               if ($size == -1) {
                        push(@old,$fn);
                        print "Drop $fn\n" if $opt_v;
                } else {
@@ -849,14 +939,14 @@ while(<CVS>) {
                        push(@new,[$mode, $sha, $fn]); # may be resurrected!
                }
                unlink($tmpname);
-       } elsif($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
+       } elsif ($state == 9 and /^\s+(.+?):\d+(?:\.\d+)+->(\d+(?:\.\d+)+)\(DEAD\)\s*$/) {
                my $fn = $1;
                $fn =~ s#^/+##;
                push(@old,$fn);
                print "Delete $fn\n" if $opt_v;
-       } elsif($state == 9 and /^\s*$/) {
+       } elsif ($state == 9 and /^\s*$/) {
                $state = 10;
-       } elsif(($state == 9 or $state == 10) and /^-+$/) {
+       } elsif (($state == 9 or $state == 10) and /^-+$/) {
                $commitcount++;
                if ($opt_L && $commitcount > $opt_L) {
                        last;
@@ -866,16 +956,20 @@ while(<CVS>) {
                        system("git repack -a -d");
                }
                $state = 1;
-       } elsif($state == 11 and /^-+$/) {
+       } elsif ($state == 11 and /^-+$/) {
                $state = 1;
-       } elsif(/^-+$/) { # end of unknown-line processing
+       } elsif (/^-+$/) { # end of unknown-line processing
                $state = 1;
-       } elsif($state != 11) { # ignore stuff when skipping
+       } elsif ($state != 11) { # ignore stuff when skipping
                print "* UNKNOWN LINE * $_\n";
        }
 }
 commit() if $branch and $state != 11;
 
+unless ($opt_P) {
+       unlink($cvspsfile);
+}
+
 # The heuristic of repacking every 1024 commits can leave a
 # lot of unpacked data.  If there is more than 1MB worth of
 # not-packed objects, repack once more.
@@ -899,7 +993,7 @@ if (defined $orig_git_index) {
 }
 
 # Now switch back to the branch we were in before all of this happened
-if($orig_branch) {
+if ($orig_branch) {
        print "DONE.\n" if $opt_v;
        if ($opt_i) {
                exit 0;
index ca519b7e49a0b2cc5104f71459fe621041382f19..9371788fab427ab8605cb8c993a1520e36ad819e 100755 (executable)
@@ -17,6 +17,7 @@
 
 use strict;
 use warnings;
+use bytes;
 
 use Fcntl;
 use File::Temp qw/tempdir tempfile/;
@@ -171,11 +172,11 @@ sub req_Root
        return 0;
     }
 
-    my @gitvars = `git-repo-config -l`;
+    my @gitvars = `git-config -l`;
     if ($?) {
-       print "E problems executing git-repo-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
+       print "E problems executing git-config on the server -- this is not a git repository or the PATH is not set correctly.\n";
         print "E \n";
-        print "error 1 - problem executing git-repo-config\n";
+        print "error 1 - problem executing git-config\n";
        return 0;
     }
     foreach my $line ( @gitvars )
@@ -875,9 +876,9 @@ sub req_update
                 print "MT newline\n";
                next;
            }
-           elsif ( !defined($wrev) || $wrev == 0 )
+           elsif ( (!defined($wrev) || $wrev == 0) && (!defined($meta->{revision}) || $meta->{revision} == 0) )
            {
-               $log->info("Tell the client the file will be added");
+               $log->info("Tell the client the file is scheduled for addition");
                print "MT text A \n";
                 print "MT fname $filename\n";
                 print "MT newline\n";
@@ -885,7 +886,7 @@ sub req_update
 
            }
            else {
-                $log->info("Updating '$filename' $wrev");
+                $log->info("Updating '$filename' to ".$meta->{revision});
                 print "MT +updated\n";
                 print "MT text U \n";
                 print "MT fname $filename\n";
@@ -945,7 +946,7 @@ sub req_update
 
             $log->debug("Temporary directory for merge is $dir");
 
-            my $return = system("merge", $file_local, $file_old, $file_new);
+            my $return = system("git", "merge-file", $file_local, $file_old, $file_new);
             $return >>= 8;
 
             if ( $return == 0 )
@@ -1180,12 +1181,15 @@ sub req_ci
         $filename = filecleanup($filename);
 
         my $meta = $updater->getmeta($filename);
+       unless (defined $meta->{revision}) {
+         $meta->{revision} = 1;
+       }
 
         my ( $filepart, $dirpart ) = filenamesplit($filename, 1);
 
         $log->debug("Checked-in $dirpart : $filename");
 
-        if ( $meta->{filehash} eq "deleted" )
+        if ( defined $meta->{filehash} && $meta->{filehash} eq "deleted" )
         {
             print "Remove-entry $dirpart\n";
             print "$filename\n";
@@ -2183,7 +2187,10 @@ sub update
     # first lets get the commit list
     $ENV{GIT_DIR} = $self->{git_path};
 
-    my $commitinfo = `git-cat-file commit $self->{module} 2>&1`;
+    my $commitsha1 = `git rev-parse $self->{module}`;
+    chomp $commitsha1;
+
+    my $commitinfo = `git cat-file commit $self->{module} 2>&1`;
     unless ( $commitinfo =~ /tree\s+[a-zA-Z0-9]{40}/ )
     {
         die("Invalid module '$self->{module}'");
@@ -2193,6 +2200,10 @@ sub update
     my $git_log;
     my $lastcommit = $self->_get_prop("last_commit");
 
+    if (defined $lastcommit && $lastcommit eq $commitsha1) { # up-to-date
+         return 1;
+    }
+
     # Start exclusive lock here...
     $self->{dbh}->begin_work() or die "Cannot lock database for BEGIN";
 
index eb32476bbdc98cc9a34d4026575e4d1a608289be..ca984e739a595131dca4070982f524d3a53f9637 100755 (executable)
@@ -2,7 +2,11 @@
 #
 
 USAGE='<fetch-options> <repository> <refspec>...'
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
+set_reflog_action "fetch $*"
+cd_to_toplevel ;# probably unnecessary...
+
 . git-parse-remote
 _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"
@@ -11,7 +15,6 @@ LF='
 '
 IFS="$LF"
 
-rloga=fetch
 no_tags=
 tags=
 append=
@@ -19,8 +22,8 @@ force=
 verbose=
 update_head_ok=
 exec=
-upload_pack=
 keep=
+shallow_depth=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -30,8 +33,12 @@ do
        --upl|--uplo|--uploa|--upload|--upload-|--upload-p|\
        --upload-pa|--upload-pac|--upload-pack)
                shift
-               exec="--exec=$1" 
-               upload_pack="-u $1"
+               exec="--upload-pack=$1"
+               ;;
+       --upl=*|--uplo=*|--uploa=*|--upload=*|\
+       --upload-=*|--upload-p=*|--upload-pa=*|--upload-pac=*|--upload-pack=*)
+               exec=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)')
+               shift
                ;;
        -f|--f|--fo|--for|--forc|--force)
                force=t
@@ -53,8 +60,12 @@ do
        -k|--k|--ke|--kee|--keep)
                keep='-k -k'
                ;;
-       --reflog-action=*)
-               rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+       --depth=*)
+               shallow_depth="--depth=`expr "z$1" : 'z-[^=]*=\(.*\)'`"
+               ;;
+       --depth)
+               shift
+               shallow_depth="--depth=$1"
                ;;
        -*)
                usage
@@ -74,20 +85,27 @@ case "$#" in
        set x $origin ; shift ;;
 esac
 
+if test -z "$exec"
+then
+       # No command line override and we have configuration for the remote.
+       exec="--upload-pack=$(get_uploadpack $1)"
+fi
+
 remote_nick="$1"
 remote=$(get_remote_url "$@")
 refs=
 rref=
 rsync_slurped_objects=
 
-rloga="$rloga $remote_nick"
-test "$remote_nick" = "$remote" || rloga="$rloga $remote"
-
 if test "" = "$append"
 then
        : >"$GIT_DIR/FETCH_HEAD"
 fi
 
+# Global that is reused later
+ls_remote_result=$(git ls-remote $exec "$remote") ||
+       die "Cannot get the repository state from $remote"
+
 append_fetch_head () {
     head_="$1"
     remote_="$2"
@@ -162,12 +180,12 @@ update_local_ref () {
                else
                        echo >&2 "* $1: updating with $3"
                        echo >&2 "  $label_: $newshort_"
-                       git-update-ref -m "$rloga: updating tag" "$1" "$2"
+                       git-update-ref -m "$GIT_REFLOG_ACTION: updating tag" "$1" "$2"
                fi
        else
                echo >&2 "* $1: storing $3"
                echo >&2 "  $label_: $newshort_"
-               git-update-ref -m "$rloga: storing tag" "$1" "$2"
+               git-update-ref -m "$GIT_REFLOG_ACTION: storing tag" "$1" "$2"
        fi
        ;;
 
@@ -190,7 +208,7 @@ update_local_ref () {
            *,$local)
                echo >&2 "* $1: fast forward to $3"
                echo >&2 "  old..new: $oldshort_..$newshort_"
-               git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
+               git-update-ref -m "$GIT_REFLOG_ACTION: fast-forward" "$1" "$2" "$local"
                ;;
            *)
                false
@@ -200,7 +218,7 @@ update_local_ref () {
                *,t,*)
                        echo >&2 "* $1: forcing update to non-fast forward $3"
                        echo >&2 "  old...new: $oldshort_...$newshort_"
-                       git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
+                       git-update-ref -m "$GIT_REFLOG_ACTION: forced-update" "$1" "$2" "$local"
                        ;;
                *)
                        echo >&2 "* $1: not updating to non-fast forward $3"
@@ -212,17 +230,18 @@ update_local_ref () {
        else
            echo >&2 "* $1: storing $3"
            echo >&2 "  $label_: $newshort_"
-           git-update-ref -m "$rloga: storing head" "$1" "$2"
+           git-update-ref -m "$GIT_REFLOG_ACTION: storing head" "$1" "$2"
        fi
        ;;
     esac
 }
 
-case "$update_head_ok" in
-'')
+# 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)
-       ;;
-esac
+fi
 
 # If --tags (and later --heads or --all) is specified, then we are
 # not talking about defaults stored in Pull: line of remotes or
@@ -232,26 +251,12 @@ esac
 reflist=$(get_remote_refs_for_fetch "$@")
 if test "$tags"
 then
-       taglist=`IFS="  " &&
-                 (
-                       git-ls-remote $upload_pack --tags "$remote" ||
-                       echo fail ouch
-                 ) |
+       taglist=`IFS='  ' &&
+                 echo "$ls_remote_result" |
+                 git-show-ref --exclude-existing=refs/tags/ |
                  while read sha1 name
                  do
-                       case "$sha1" in
-                       fail)
-                               exit 1
-                       esac
-                       case "$name" in
-                       *^*) continue ;;
-                       esac
-                       if git-check-ref-format "$name"
-                       then
-                           echo ".${name}:${name}"
-                       else
-                           echo >&2 "warning: tag ${name} ignored"
-                       fi
+                       echo ".${name}:${name}"
                  done` || exit
        if test "$#" -gt 1
        then
@@ -296,34 +301,36 @@ fetch_main () {
       # There are transports that can fetch only one head at a time...
       case "$remote" in
       http://* | https://* | ftp://*)
+         test -n "$shallow_depth" &&
+               die "shallow clone with http not supported"
          proto=`expr "$remote" : '\([^:]*\):'`
          if [ -n "$GIT_SSL_NO_VERIFY" ]; then
              curl_extra_args="-k"
          fi
          if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
-               "`git-repo-config --bool http.noEPSV`" = true ]; then
+               "`git-config --bool http.noEPSV`" = true ]; then
              noepsv_opt="--disable-epsv"
          fi
-         max_depth=5
-         depth=0
-         head="ref: $remote_name"
-         while (expr "z$head" : "zref:" && expr $depth \< $max_depth) >/dev/null
-         do
-           remote_name_quoted=$(@@PERL@@ -e '
-             my $u = $ARGV[0];
-              $u =~ s/^ref:\s*//;
-             $u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
-             print "$u";
-         ' "$head")
-           head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted")
-           depth=$( expr \( $depth + 1 \) )
-         done
+
+         # Find $remote_name from ls-remote output.
+         head=$(
+               IFS='   '
+               echo "$ls_remote_result" |
+               while read sha1 name
+               do
+                       test "z$name" = "z$remote_name" || continue
+                       echo "$sha1"
+                       break
+               done
+         )
          expr "z$head" : "z$_x40\$" >/dev/null ||
-             die "Failed to fetch $remote_name from $remote"
+               die "No such ref $remote_name at $remote"
          echo >&2 "Fetching $remote_name from $remote using $proto"
          git-http-fetch -v -a "$head" "$remote/" || exit
          ;;
       rsync://*)
+         test -n "$shallow_depth" &&
+               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)
@@ -359,7 +366,7 @@ fetch_main () {
       esac
 
       append_fetch_head "$head" "$remote" \
-         "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
+         "$remote_name" "$remote_nick" "$local_name" "$not_for_merge" || exit
 
   done
 
@@ -368,13 +375,22 @@ fetch_main () {
       ;; # we are already done.
   *)
     ( : subshell because we muck with IFS
-      pack_lockfile=
       IFS="    $LF"
       (
-         git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote"
+         git-fetch-pack --thin $exec $keep $shallow_depth "$remote" $rref ||
+         echo failed "$remote"
       ) |
-      while read sha1 remote_name
-      do
+      (
+       trap '
+               if test -n "$keepfile" && test -f "$keepfile"
+               then
+                       rm -f "$keepfile"
+               fi
+       ' 0
+
+        keepfile=
+       while read sha1 remote_name
+       do
          case "$sha1" in
          failed)
                  echo >&2 "Fetch failure: $remote"
@@ -383,7 +399,7 @@ fetch_main () {
          pack)
                  continue ;;
          keep)
-                 pack_lockfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep"
+                 keepfile="$GIT_OBJECT_DIRECTORY/pack/pack-$remote_name.keep"
                  continue ;;
          esac
          found=
@@ -413,15 +429,16 @@ fetch_main () {
          done
          local_name=$(expr "z$found" : 'z[^:]*:\(.*\)')
          append_fetch_head "$sha1" "$remote" \
-                 "$remote_name" "$remote_nick" "$local_name" "$not_for_merge"
-      done
-      if [ "$pack_lockfile" ]; then rm -f "$pack_lockfile"; fi
+                 "$remote_name" "$remote_nick" "$local_name" \
+                 "$not_for_merge" || exit
+        done
+      )
     ) || exit ;;
   esac
 
 }
 
-fetch_main "$reflist"
+fetch_main "$reflist" || exit
 
 # automated tag following
 case "$no_tags$tags" in
@@ -430,17 +447,11 @@ case "$no_tags$tags" in
        *:refs/*)
                # effective only when we are following remote branch
                # using local tracking branch.
-               taglist=$(IFS=" " &&
-               git-ls-remote $upload_pack --tags "$remote" |
-               sed -n  -e 's|^\('"$_x40"'\)    \(refs/tags/.*\)^{}$|\1 \2|p' \
-                       -e 's|^\('"$_x40"'\)    \(refs/tags/.*\)$|\1 \2|p' |
+               taglist=$(IFS=' ' &&
+               echo "$ls_remote_result" |
+               git-show-ref --exclude-existing=refs/tags/ |
                while read sha1 name
                do
-                       git-show-ref --verify --quiet -- "$name" && continue
-                       git-check-ref-format "$name" || {
-                               echo >&2 "warning: tag ${name} ignored"
-                               continue
-                       }
                        git-cat-file -t "$sha1" >/dev/null 2>&1 || continue
                        echo >&2 "Auto-following $name"
                        echo ".${name}:${name}"
@@ -449,7 +460,9 @@ case "$no_tags$tags" in
        case "$taglist" in
        '') ;;
        ?*)
-               fetch_main "$taglist" ;;
+               # do not deepen a shallow tree when following tags
+               shallow_depth=
+               fetch_main "$taglist" || exit ;;
        esac
 esac
 
@@ -463,7 +476,7 @@ case "$orig_head" in
        if test "$curr_head" != "$orig_head"
        then
            git-update-ref \
-                       -m "$rloga: Undoing incorrectly fetched HEAD." \
+                       -m "$GIT_REFLOG_ACTION: Undoing incorrectly fetched HEAD." \
                        HEAD "$orig_head"
                die "Cannot fetch into the current branch."
        fi
diff --git a/git-gc.sh b/git-gc.sh
new file mode 100755 (executable)
index 0000000..1a45de5
--- /dev/null
+++ b/git-gc.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+#
+# Copyright (c) 2006, Shawn O. Pearce
+#
+# Cleanup unreachable files and optimize the repository.
+
+USAGE='git-gc [--prune]'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+
+no_prune=:
+while case $# in 0) break ;; esac
+do
+       case "$1" in
+       --prune)
+               no_prune=
+               ;;
+       --)
+               usage
+               ;;
+       esac
+       shift
+done
+
+case "$(git config --get gc.packrefs)" in
+notbare|"")
+       test $(is_bare_repository) = true || pack_refs=true;;
+*)
+       pack_refs=$(git config --bool --get gc.packrefs)
+esac
+
+test "true" != "$pack_refs" ||
+git-pack-refs --prune &&
+git-reflog expire --all &&
+git-repack -a -d -l &&
+$no_prune git-prune &&
+git-rerere gc || exit
diff --git a/git-gui/.gitignore b/git-gui/.gitignore
new file mode 100644 (file)
index 0000000..c714d38
--- /dev/null
@@ -0,0 +1,3 @@
+GIT-VERSION-FILE
+git-citool
+git-gui
diff --git a/git-gui/GIT-VERSION-GEN b/git-gui/GIT-VERSION-GEN
new file mode 100755 (executable)
index 0000000..9966126
--- /dev/null
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+GVF=GIT-VERSION-FILE
+DEF_VER=0.6.GITGUI
+
+LF='
+'
+
+tree_search ()
+{
+       head=$1
+       tree=$2
+       for p in $(git rev-list --parents --max-count=1 $head 2>/dev/null)
+       do
+               test $tree = $(git rev-parse $p^{tree} 2>/dev/null) &&
+               vn=$(git describe --abbrev=4 $p 2>/dev/null) &&
+               case "$vn" in
+               gitgui-[0-9]*) echo $vn; break;;
+               esac
+       done
+}
+
+# We may be a subproject, so try looking for the merge
+# commit that supplied this directory content if we are
+# not at the toplevel.  We probably will always be the
+# second parent in the commit, but we shouldn't rely on
+# that fact.
+#
+# If we are at the toplevel or the merge assumption fails
+# try looking for a gitgui-* tag, or fallback onto the
+# distributed version file.
+
+if prefix="$(git rev-parse --show-prefix 2>/dev/null)"
+   test -n "$prefix" &&
+   head=$(git rev-list --max-count=1 HEAD -- . 2>/dev/null) &&
+   tree=$(git rev-parse --verify "HEAD:$prefix" 2>/dev/null) &&
+   VN=$(tree_search $head $tree)
+   case "$VN" in
+   gitgui-[0-9]*) : happy ;;
+   *) (exit 1) ;;
+   esac
+then
+       VN=$(echo "$VN" | sed -e 's/^gitgui-//;s/-/./g');
+elif VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
+   case "$VN" in
+   gitgui-[0-9]*) : happy ;;
+   *) (exit 1) ;;
+   esac
+then
+       VN=$(echo "$VN" | sed -e 's/^gitgui-//;s/-/./g');
+elif test -f version
+then
+       VN=$(cat version) || VN="$DEF_VER"
+else
+       VN="$DEF_VER"
+fi
+
+dirty=$(sh -c 'git diff-index --name-only HEAD' 2>/dev/null) || dirty=
+case "$dirty" in
+'')
+       ;;
+*)
+       VN="$VN-dirty" ;;
+esac
+
+if test -r $GVF
+then
+       VC=$(sed -e 's/^GITGUI_VERSION = //' <$GVF)
+else
+       VC=unset
+fi
+test "$VN" = "$VC" || {
+       echo >&2 "GITGUI_VERSION = $VN"
+       echo "GITGUI_VERSION = $VN" >$GVF
+}
+
+
diff --git a/git-gui/Makefile b/git-gui/Makefile
new file mode 100644 (file)
index 0000000..fd82d9d
--- /dev/null
@@ -0,0 +1,56 @@
+all::
+
+GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+       @$(SHELL_PATH) ./GIT-VERSION-GEN
+-include GIT-VERSION-FILE
+
+SCRIPT_SH = git-gui.sh
+GITGUI_BUILT_INS = git-citool
+ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH))
+
+ifndef SHELL_PATH
+       SHELL_PATH = /bin/sh
+endif
+
+ifndef gitexecdir
+       gitexecdir := $(shell git --exec-path)
+endif
+
+ifndef INSTALL
+       INSTALL = install
+endif
+
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
+       rm -f $@ $@+
+       sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+               -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \
+               $@.sh >$@+
+       chmod +x $@+
+       mv $@+ $@
+
+$(GITGUI_BUILT_INS): git-gui
+       rm -f $@ && ln git-gui $@
+
+# These can record GITGUI_VERSION
+$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE
+
+all:: $(ALL_PROGRAMS)
+
+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' ;)
+
+dist-version:
+       @mkdir -p $(TARDIR)
+       @echo $(GITGUI_VERSION) > $(TARDIR)/version
+
+clean::
+       rm -f $(ALL_PROGRAMS) GIT-VERSION-FILE
+
+.PHONY: all install dist-version clean
+.PHONY: .FORCE-GIT-VERSION-FILE
diff --git a/git-gui/TODO b/git-gui/TODO
new file mode 100644 (file)
index 0000000..b95a137
--- /dev/null
@@ -0,0 +1,44 @@
+Items outstanding:
+
+ * Add file to .gitignore or info/excludes.
+
+ * Populate the pull menu with local branches.
+
+ * Make use of the new default merge data stored in repo-config.
+
+ * Checkout a different local branch.
+
+ * Push any local branch to a remote branch.
+
+ * Merge any local branches through a real merge UI.
+
+ * Allow user to define keyboard shortcuts for frequently used fetch
+   or merge operations.  Or maybe just define a keyboard shortcut
+   for default fetch/default merge of current branch is enough;
+   but I do know a few users who merge a couple of common branches
+   also into the same branch so one default isn't quite enough.
+
+ * Better organize fetch/push/pull console windows.
+
+ * Clone UI (to download a new repository).
+
+ * Remotes editor (for .git/config format only).
+
+ * Show a shortlog of the last couple of commits in the main window,
+   to give the user warm fuzzy feelings that we have their data
+   saved.  Actually this may be the set of commits not yet in
+   the upstream (aka default merge branch remote repository).
+
+ * GUI configuration editor for options listed in
+   git.git/Documentation/config.txt.  Ideally this would
+   parse that file and generate the options dialog from
+   the documentation itself, and include the help text
+   from the documentation as part of the UI somehow.
+
+Known bugs:
+
+ * git-gui sometimes just closes on Windows with no error message.
+   I'm not sure what the problem is here.  I suspect the wish
+   process is just terminating due to a segfault or something,
+   as the do_quit proc in git-gui doesn't run.  It often seems to
+   occur while writing a commit message in the buffer.  Odd.
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
new file mode 100755 (executable)
index 0000000..f5010dd
--- /dev/null
@@ -0,0 +1,5924 @@
+#!/bin/sh
+# Tcl ignores the next line -*- tcl -*- \
+exec wish "$0" -- "$@"
+
+set appvers {@@GITGUI_VERSION@@}
+set copyright {
+Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras.
+
+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}
+
+######################################################################
+##
+## read only globals
+
+set _appname [lindex [file split $argv0] end]
+set _gitdir {}
+set _gitexec {}
+set _reponame {}
+set _iscygwin {}
+
+proc appname {} {
+       global _appname
+       return $_appname
+}
+
+proc gitdir {args} {
+       global _gitdir
+       if {$args eq {}} {
+               return $_gitdir
+       }
+       return [eval [concat [list file join $_gitdir] $args]]
+}
+
+proc gitexec {args} {
+       global _gitexec
+       if {$_gitexec eq {}} {
+               if {[catch {set _gitexec [exec git --exec-path]} err]} {
+                       error "Git not installed?\n\n$err"
+               }
+       }
+       if {$args eq {}} {
+               return $_gitexec
+       }
+       return [eval [concat [list file join $_gitexec] $args]]
+}
+
+proc reponame {} {
+       global _reponame
+       return $_reponame
+}
+
+proc is_MacOSX {} {
+       global tcl_platform tk_library
+       if {[tk windowingsystem] eq {aqua}} {
+               return 1
+       }
+       return 0
+}
+
+proc is_Windows {} {
+       global tcl_platform
+       if {$tcl_platform(platform) eq {windows}} {
+               return 1
+       }
+       return 0
+}
+
+proc is_Cygwin {} {
+       global tcl_platform _iscygwin
+       if {$_iscygwin eq {}} {
+               if {$tcl_platform(platform) eq {windows}} {
+                       if {[catch {set p [exec cygpath --windir]} err]} {
+                               set _iscygwin 0
+                       } else {
+                               set _iscygwin 1
+                       }
+               } else {
+                       set _iscygwin 0
+               }
+       }
+       return $_iscygwin
+}
+
+proc is_enabled {option} {
+       global enabled_options
+       if {[catch {set on $enabled_options($option)}]} {return 0}
+       return $on
+}
+
+proc enable_option {option} {
+       global enabled_options
+       set enabled_options($option) 1
+}
+
+proc disable_option {option} {
+       global enabled_options
+       set enabled_options($option) 0
+}
+
+######################################################################
+##
+## config
+
+proc is_many_config {name} {
+       switch -glob -- $name {
+       remote.*.fetch -
+       remote.*.push
+               {return 1}
+       *
+               {return 0}
+       }
+}
+
+proc is_config_true {name} {
+       global repo_config
+       if {[catch {set v $repo_config($name)}]} {
+               return 0
+       } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} {
+               return 1
+       } else {
+               return 0
+       }
+}
+
+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]
+                       while {[gets $fd_rc line] >= 0} {
+                               if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
+                                       if {[is_many_config $name]} {
+                                               lappend global_config($name) $value
+                                       } else {
+                                               set global_config($name) $value
+                                       }
+                               }
+                       }
+                       close $fd_rc
+               }
+       }
+
+       array unset repo_config
+       catch {
+               set fd_rc [open "| git config --list" r]
+               while {[gets $fd_rc line] >= 0} {
+                       if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
+                               if {[is_many_config $name]} {
+                                       lappend repo_config($name) $value
+                               } else {
+                                       set repo_config($name) $value
+                               }
+                       }
+               }
+               close $fd_rc
+       }
+
+       foreach name [array names default_config] {
+               if {[catch {set v $global_config($name)}]} {
+                       set global_config($name) $default_config($name)
+               }
+               if {[catch {set v $repo_config($name)}]} {
+                       set repo_config($name) $default_config($name)
+               }
+       }
+}
+
+proc save_config {} {
+       global default_config font_descs
+       global repo_config global_config
+       global repo_config_new global_config_new
+
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set font [lindex $option 1]
+               font configure $font \
+                       -family $global_config_new(gui.$font^^family) \
+                       -size $global_config_new(gui.$font^^size)
+               font configure ${font}bold \
+                       -family $global_config_new(gui.$font^^family) \
+                       -size $global_config_new(gui.$font^^size)
+               set global_config_new(gui.$name) [font configure $font]
+               unset global_config_new(gui.$font^^family)
+               unset global_config_new(gui.$font^^size)
+       }
+
+       foreach name [array names default_config] {
+               set value $global_config_new($name)
+               if {$value ne $global_config($name)} {
+                       if {$value eq $default_config($name)} {
+                               catch {exec git config --global --unset $name}
+                       } else {
+                               regsub -all "\[{}\]" $value {"} value
+                               exec git config --global $name $value
+                       }
+                       set global_config($name) $value
+                       if {$value eq $repo_config($name)} {
+                               catch {exec git config --unset $name}
+                               set repo_config($name) $value
+                       }
+               }
+       }
+
+       foreach name [array names default_config] {
+               set value $repo_config_new($name)
+               if {$value ne $repo_config($name)} {
+                       if {$value eq $global_config($name)} {
+                               catch {exec git config --unset $name}
+                       } else {
+                               regsub -all "\[{}\]" $value {"} value
+                               exec git config $name $value
+                       }
+                       set repo_config($name) $value
+               }
+       }
+}
+
+proc error_popup {msg} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       set cmd [list tk_messageBox \
+               -icon error \
+               -type ok \
+               -title "$title: error" \
+               -message $msg]
+       if {[winfo ismapped .]} {
+               lappend cmd -parent .
+       }
+       eval $cmd
+}
+
+proc warn_popup {msg} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       set cmd [list tk_messageBox \
+               -icon warning \
+               -type ok \
+               -title "$title: warning" \
+               -message $msg]
+       if {[winfo ismapped .]} {
+               lappend cmd -parent .
+       }
+       eval $cmd
+}
+
+proc info_popup {msg {parent .}} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       tk_messageBox \
+               -parent $parent \
+               -icon info \
+               -type ok \
+               -title $title \
+               -message $msg
+}
+
+proc ask_popup {msg} {
+       set title [appname]
+       if {[reponame] ne {}} {
+               append title " ([reponame])"
+       }
+       return [tk_messageBox \
+               -parent . \
+               -icon question \
+               -type yesno \
+               -title $title \
+               -message $msg]
+}
+
+######################################################################
+##
+## repository setup
+
+if {   [catch {set _gitdir $env(GIT_DIR)}]
+       && [catch {set _gitdir [exec git rev-parse --git-dir]} err]} {
+       catch {wm withdraw .}
+       error_popup "Cannot find the git directory:\n\n$err"
+       exit 1
+}
+if {![file isdirectory $_gitdir] && [is_Cygwin]} {
+       catch {set _gitdir [exec cygpath --unix $_gitdir]}
+}
+if {![file isdirectory $_gitdir]} {
+       catch {wm withdraw .}
+       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 {[catch {cd [file dirname $_gitdir]} err]} {
+       catch {wm withdraw .}
+       error_popup "No working directory [file dirname $_gitdir]:\n\n$err"
+       exit 1
+}
+set _reponame [lindex [file split \
+       [file normalize [file dirname $_gitdir]]] \
+       end]
+
+######################################################################
+##
+## task management
+
+set rescan_active 0
+set diff_active 0
+set last_clicked {}
+
+set disable_on_lock [list]
+set index_lock_type none
+
+proc lock_index {type} {
+       global index_lock_type disable_on_lock
+
+       if {$index_lock_type eq {none}} {
+               set index_lock_type $type
+               foreach w $disable_on_lock {
+                       uplevel #0 $w disabled
+               }
+               return 1
+       } elseif {$index_lock_type eq "begin-$type"} {
+               set index_lock_type $type
+               return 1
+       }
+       return 0
+}
+
+proc unlock_index {} {
+       global index_lock_type disable_on_lock
+
+       set index_lock_type none
+       foreach w $disable_on_lock {
+               uplevel #0 $w normal
+       }
+}
+
+######################################################################
+##
+## status
+
+proc repository_state {ctvar hdvar mhvar} {
+       global current_branch
+       upvar $ctvar ct $hdvar hd $mhvar mh
+
+       set mh [list]
+
+       if {[catch {set current_branch [exec git symbolic-ref HEAD]}]} {
+               set current_branch {}
+       } else {
+               regsub ^refs/((heads|tags|remotes)/)? \
+                       $current_branch \
+                       {} \
+                       current_branch
+       }
+
+       if {[catch {set hd [exec git rev-parse --verify HEAD]}]} {
+               set hd {}
+               set ct initial
+               return
+       }
+
+       set merge_head [gitdir MERGE_HEAD]
+       if {[file exists $merge_head]} {
+               set ct merge
+               set fd_mh [open $merge_head r]
+               while {[gets $fd_mh line] >= 0} {
+                       lappend mh $line
+               }
+               close $fd_mh
+               return
+       }
+
+       set ct normal
+}
+
+proc PARENT {} {
+       global PARENT empty_tree
+
+       set p [lindex $PARENT 0]
+       if {$p ne {}} {
+               return $p
+       }
+       if {$empty_tree eq {}} {
+               set empty_tree [exec git mktree << {}]
+       }
+       return $empty_tree
+}
+
+proc rescan {after {honor_trustmtime 1}} {
+       global HEAD PARENT MERGE_HEAD commit_type
+       global ui_index ui_workdir ui_status_value ui_comm
+       global rescan_active file_states
+       global repo_config
+
+       if {$rescan_active > 0 || ![lock_index read]} return
+
+       repository_state newType newHEAD newMERGE_HEAD
+       if {[string match amend* $commit_type]
+               && $newType eq {normal}
+               && $newHEAD eq $HEAD} {
+       } else {
+               set HEAD $newHEAD
+               set PARENT $newHEAD
+               set MERGE_HEAD $newMERGE_HEAD
+               set commit_type $newType
+       }
+
+       array unset file_states
+
+       if {![$ui_comm edit modified]
+               || [string trim [$ui_comm get 0.0 end]] eq {}} {
+               if {[load_message GITGUI_MSG]} {
+               } elseif {[load_message MERGE_MSG]} {
+               } elseif {[load_message SQUASH_MSG]} {
+               }
+               $ui_comm edit reset
+               $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]
+               fconfigure $fd_rf -blocking 0 -translation binary
+               fileevent $fd_rf readable \
+                       [list rescan_stage2 $fd_rf $after]
+       }
+}
+
+proc rescan_stage2 {fd after} {
+       global ui_status_value
+       global rescan_active buf_rdi buf_rdf buf_rlo
+
+       if {$fd ne {}} {
+               read $fd
+               if {![eof $fd]} return
+               close $fd
+       }
+
+       set ls_others [list | git ls-files --others -z \
+               --exclude-per-directory=.gitignore]
+       set info_exclude [gitdir info exclude]
+       if {[file readable $info_exclude]} {
+               lappend ls_others "--exclude-from=$info_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]
+
+       fconfigure $fd_di -blocking 0 -translation binary -encoding binary
+       fconfigure $fd_df -blocking 0 -translation binary -encoding binary
+       fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
+       fileevent $fd_di readable [list read_diff_index $fd_di $after]
+       fileevent $fd_df readable [list read_diff_files $fd_df $after]
+       fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
+}
+
+proc load_message {file} {
+       global ui_comm
+
+       set f [gitdir $file]
+       if {[file isfile $f]} {
+               if {[catch {set fd [open $f r]}]} {
+                       return 0
+               }
+               set content [string trim [read $fd]]
+               close $fd
+               regsub -all -line {[ \r\t]+$} $content {} content
+               $ui_comm delete 0.0 end
+               $ui_comm insert end $content
+               return 1
+       }
+       return 0
+}
+
+proc read_diff_index {fd after} {
+       global buf_rdi
+
+       append buf_rdi [read $fd]
+       set c 0
+       set n [string length $buf_rdi]
+       while {$c < $n} {
+               set z1 [string first "\0" $buf_rdi $c]
+               if {$z1 == -1} break
+               incr z1
+               set z2 [string first "\0" $buf_rdi $z1]
+               if {$z2 == -1} break
+
+               incr c
+               set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }]
+               set p [string range $buf_rdi $z1 [expr {$z2 - 1}]]
+               merge_state \
+                       [encoding convertfrom $p] \
+                       [lindex $i 4]? \
+                       [list [lindex $i 0] [lindex $i 2]] \
+                       [list]
+               set c $z2
+               incr c
+       }
+       if {$c < $n} {
+               set buf_rdi [string range $buf_rdi $c end]
+       } else {
+               set buf_rdi {}
+       }
+
+       rescan_done $fd buf_rdi $after
+}
+
+proc read_diff_files {fd after} {
+       global buf_rdf
+
+       append buf_rdf [read $fd]
+       set c 0
+       set n [string length $buf_rdf]
+       while {$c < $n} {
+               set z1 [string first "\0" $buf_rdf $c]
+               if {$z1 == -1} break
+               incr z1
+               set z2 [string first "\0" $buf_rdf $z1]
+               if {$z2 == -1} break
+
+               incr c
+               set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }]
+               set p [string range $buf_rdf $z1 [expr {$z2 - 1}]]
+               merge_state \
+                       [encoding convertfrom $p] \
+                       ?[lindex $i 4] \
+                       [list] \
+                       [list [lindex $i 0] [lindex $i 2]]
+               set c $z2
+               incr c
+       }
+       if {$c < $n} {
+               set buf_rdf [string range $buf_rdf $c end]
+       } else {
+               set buf_rdf {}
+       }
+
+       rescan_done $fd buf_rdf $after
+}
+
+proc read_ls_others {fd after} {
+       global buf_rlo
+
+       append buf_rlo [read $fd]
+       set pck [split $buf_rlo "\0"]
+       set buf_rlo [lindex $pck end]
+       foreach p [lrange $pck 0 end-1] {
+               merge_state [encoding convertfrom $p] ?O
+       }
+       rescan_done $fd buf_rlo $after
+}
+
+proc rescan_done {fd buf after} {
+       global rescan_active
+       global file_states repo_config
+       upvar $buf to_clear
+
+       if {![eof $fd]} return
+       set to_clear {}
+       close $fd
+       if {[incr rescan_active -1] > 0} return
+
+       prune_selection
+       unlock_index
+       display_all_files
+       reshow_diff
+       uplevel #0 $after
+}
+
+proc prune_selection {} {
+       global file_states selected_paths
+
+       foreach path [array names selected_paths] {
+               if {[catch {set still_here $file_states($path)}]} {
+                       unset selected_paths($path)
+               }
+       }
+}
+
+######################################################################
+##
+## diff
+
+proc clear_diff {} {
+       global ui_diff current_diff_path current_diff_header
+       global ui_index ui_workdir
+
+       $ui_diff conf -state normal
+       $ui_diff delete 0.0 end
+       $ui_diff conf -state disabled
+
+       set current_diff_path {}
+       set current_diff_header {}
+
+       $ui_index tag remove in_diff 0.0 end
+       $ui_workdir tag remove in_diff 0.0 end
+}
+
+proc reshow_diff {} {
+       global ui_status_value file_states file_lists
+       global current_diff_path current_diff_side
+
+       set p $current_diff_path
+       if {$p eq {}
+               || $current_diff_side eq {}
+               || [catch {set s $file_states($p)}]
+               || [lsearch -sorted -exact $file_lists($current_diff_side) $p] == -1} {
+               clear_diff
+       } else {
+               show_diff $p $current_diff_side
+       }
+}
+
+proc handle_empty_diff {} {
+       global current_diff_path file_states file_lists
+
+       set path $current_diff_path
+       set s $file_states($path)
+       if {[lindex $s 0] ne {_M}} return
+
+       info_popup "No differences detected.
+
+[short_path $path] has no changes.
+
+The modification date of this file was updated
+by another application, but the content within
+the file was not changed.
+
+A rescan will be automatically started to find
+other files which may have the same state."
+
+       clear_diff
+       display_file $path __
+       rescan {set ui_status_value {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 current_diff_path current_diff_side current_diff_header
+
+       if {$diff_active || ![lock_index read]} return
+
+       clear_diff
+       if {$lno == {}} {
+               set lno [lsearch -sorted -exact $file_lists($w) $path]
+               if {$lno >= 0} {
+                       incr lno
+               }
+       }
+       if {$lno >= 1} {
+               $w tag add in_diff $lno.0 [expr {$lno + 1}].0
+       }
+
+       set s $file_states($path)
+       set m [lindex $s 0]
+       set is_3way_diff 0
+       set diff_active 1
+       set current_diff_path $path
+       set current_diff_side $w
+       set current_diff_header {}
+       set ui_status_value "Loading diff of [escape_path $path]..."
+
+       # - Git won't give us the diff, there's nothing to compare to!
+       #
+       if {$m eq {_O}} {
+               set max_sz [expr {128 * 1024}]
+               if {[catch {
+                               set fd [open $path r]
+                               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]"
+                       error_popup "Error loading file:\n\n$err"
+                       return
+               }
+               $ui_diff conf -state normal
+               if {![catch {set type [exec file $path]}]} {
+                       set n [string length $path]
+                       if {[string equal -length $n $path $type]} {
+                               set type [string range $type $n end]
+                               regsub {^:?\s*} $type {} type
+                       }
+                       $ui_diff insert end "* $type\n" d_@
+               }
+               if {[string first "\0" $content] != -1} {
+                       $ui_diff insert end \
+                               "* Binary file (not showing content)." \
+                               d_@
+               } else {
+                       if {$sz > $max_sz} {
+                               $ui_diff insert end \
+"* Untracked file is $sz bytes.
+* Showing only first $max_sz bytes.
+" d_@
+                       }
+                       $ui_diff insert end $content
+                       if {$sz > $max_sz} {
+                               $ui_diff insert end "
+* Untracked file clipped here by [appname].
+* To see the entire file, use an external editor.
+" d_@
+                       }
+               }
+               $ui_diff conf -state disabled
+               set diff_active 0
+               unlock_index
+               set ui_status_value {Ready.}
+               return
+       }
+
+       set cmd [list | git]
+       if {$w eq $ui_index} {
+               lappend cmd diff-index
+               lappend cmd --cached
+       } elseif {$w eq $ui_workdir} {
+               if {[string index $m 0] eq {U}} {
+                       lappend cmd diff
+               } else {
+                       lappend cmd diff-files
+               }
+       }
+
+       lappend cmd -p
+       lappend cmd --no-color
+       if {$repo_config(gui.diffcontext) > 0} {
+               lappend cmd "-U$repo_config(gui.diffcontext)"
+       }
+       if {$w eq $ui_index} {
+               lappend cmd [PARENT]
+       }
+       lappend cmd --
+       lappend cmd $path
+
+       if {[catch {set fd [open $cmd r]} err]} {
+               set diff_active 0
+               unlock_index
+               set ui_status_value "Unable to display [escape_path $path]"
+               error_popup "Error loading diff:\n\n$err"
+               return
+       }
+
+       fconfigure $fd \
+               -blocking 0 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd readable [list read_diff $fd]
+}
+
+proc read_diff {fd} {
+       global ui_diff ui_status_value diff_active
+       global is_3way_diff current_diff_header
+
+       $ui_diff conf -state normal
+       while {[gets $fd line] >= 0} {
+               # -- Cleanup uninteresting diff header lines.
+               #
+               if {   [string match {diff --git *}      $line]
+                       || [string match {diff --cc *}       $line]
+                       || [string match {diff --combined *} $line]
+                       || [string match {--- *}             $line]
+                       || [string match {+++ *}             $line]} {
+                       append current_diff_header $line "\n"
+                       continue
+               }
+               if {[string match {index *} $line]} continue
+               if {$line eq {deleted file mode 120000}} {
+                       set line "deleted symlink"
+               }
+
+               # -- Automatically detect if this is a 3 way diff.
+               #
+               if {[string match {@@@ *} $line]} {set is_3way_diff 1}
+
+               if {[string match {mode *} $line]
+                       || [string match {new file *} $line]
+                       || [string match {deleted file *} $line]
+                       || [string match {Binary files * and * differ} $line]
+                       || $line eq {\ No newline at end of file}
+                       || [regexp {^\* Unmerged path } $line]} {
+                       set tags {}
+               } elseif {$is_3way_diff} {
+                       set op [string range $line 0 1]
+                       switch -- $op {
+                       {  } {set tags {}}
+                       {@@} {set tags d_@}
+                       { +} {set tags d_s+}
+                       { -} {set tags d_s-}
+                       {+ } {set tags d_+s}
+                       {- } {set tags d_-s}
+                       {--} {set tags d_--}
+                       {++} {
+                               if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} {
+                                       set line [string replace $line 0 1 {  }]
+                                       set tags d$op
+                               } else {
+                                       set tags d_++
+                               }
+                       }
+                       default {
+                               puts "error: Unhandled 3 way diff marker: {$op}"
+                               set tags {}
+                       }
+                       }
+               } else {
+                       set op [string index $line 0]
+                       switch -- $op {
+                       { } {set tags {}}
+                       {@} {set tags d_@}
+                       {-} {set tags d_-}
+                       {+} {
+                               if {[regexp {^\+([<>]{7} |={7})} $line _g op]} {
+                                       set line [string replace $line 0 0 { }]
+                                       set tags d$op
+                               } else {
+                                       set tags d_+
+                               }
+                       }
+                       default {
+                               puts "error: Unhandled 2 way diff marker: {$op}"
+                               set tags {}
+                       }
+                       }
+               }
+               $ui_diff insert end $line $tags
+               if {[string index $line end] eq "\r"} {
+                       $ui_diff tag add d_cr {end - 2c}
+               }
+               $ui_diff insert end "\n" $tags
+       }
+       $ui_diff conf -state disabled
+
+       if {[eof $fd]} {
+               close $fd
+               set diff_active 0
+               unlock_index
+               set ui_status_value {Ready.}
+
+               if {[$ui_diff index end] eq {2.0}} {
+                       handle_empty_diff
+               }
+       }
+}
+
+proc apply_hunk {x y} {
+       global current_diff_path current_diff_header current_diff_side
+       global ui_diff ui_index file_states
+
+       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 mi [lindex $file_states($current_diff_path) 0]
+       if {$current_diff_side eq $ui_index} {
+               set mode unstage
+               lappend apply_cmd --reverse
+               if {[string index $mi 0] ne {M}} {
+                       unlock_index
+                       return
+               }
+       } else {
+               set mode stage
+               if {[string index $mi 1] ne {M}} {
+                       unlock_index
+                       return
+               }
+       }
+
+       set s_lno [lindex [split [$ui_diff index @$x,$y] .] 0]
+       set s_lno [$ui_diff search -backwards -regexp ^@@ $s_lno.0 0.0]
+       if {$s_lno eq {}} {
+               unlock_index
+               return
+       }
+
+       set e_lno [$ui_diff search -forwards -regexp ^@@ "$s_lno + 1 lines" end]
+       if {$e_lno eq {}} {
+               set e_lno end
+       }
+
+       if {[catch {
+               set p [open "| $apply_cmd" w]
+               fconfigure $p -translation binary -encoding binary
+               puts -nonewline $p $current_diff_header
+               puts -nonewline $p [$ui_diff get $s_lno $e_lno]
+               close $p} err]} {
+               error_popup "Failed to $mode selected hunk.\n\n$err"
+               unlock_index
+               return
+       }
+
+       $ui_diff conf -state normal
+       $ui_diff delete $s_lno $e_lno
+       $ui_diff conf -state disabled
+
+       if {[$ui_diff get 1.0 end] eq "\n"} {
+               set o _
+       } else {
+               set o ?
+       }
+
+       if {$current_diff_side eq $ui_index} {
+               set mi ${o}M
+       } elseif {[string index $mi 0] eq {_}} {
+               set mi M$o
+       } else {
+               set mi ?$o
+       }
+       unlock_index
+       display_file $current_diff_path $mi
+       if {$o eq {_}} {
+               clear_diff
+       }
+}
+
+######################################################################
+##
+## commit
+
+proc load_last_commit {} {
+       global HEAD PARENT MERGE_HEAD commit_type ui_comm
+       global repo_config
+
+       if {[llength $PARENT] == 0} {
+               error_popup {There is nothing to amend.
+
+You are about to create the initial commit.
+There is no commit before this to amend.
+}
+               return
+       }
+
+       repository_state curType curHEAD curMERGE_HEAD
+       if {$curType eq {merge}} {
+               error_popup {Cannot amend while merging.
+
+You are currently in the middle of a merge that
+has not been fully completed.  You cannot amend
+the prior commit unless you first abort the
+current merge activity.
+}
+               return
+       }
+
+       set msg {}
+       set parents [list]
+       if {[catch {
+                       set fd [open "| git cat-file commit $curHEAD" r]
+                       fconfigure $fd -encoding binary -translation lf
+                       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+                               set enc utf-8
+                       }
+                       while {[gets $fd line] > 0} {
+                               if {[string match {parent *} $line]} {
+                                       lappend parents [string range $line 7 end]
+                               } elseif {[string match {encoding *} $line]} {
+                                       set enc [string tolower [string range $line 9 end]]
+                               }
+                       }
+                       fconfigure $fd -encoding $enc
+                       set msg [string trim [read $fd]]
+                       close $fd
+               } err]} {
+               error_popup "Error loading commit data for amend:\n\n$err"
+               return
+       }
+
+       set HEAD $curHEAD
+       set PARENT $parents
+       set MERGE_HEAD [list]
+       switch -- [llength $parents] {
+       0       {set commit_type amend-initial}
+       1       {set commit_type amend}
+       default {set commit_type amend-merge}
+       }
+
+       $ui_comm delete 0.0 end
+       $ui_comm insert end $msg
+       $ui_comm edit reset
+       $ui_comm edit modified false
+       rescan {set ui_status_value {Ready.}}
+}
+
+proc create_new_commit {} {
+       global commit_type ui_comm
+
+       set commit_type normal
+       $ui_comm delete 0.0 end
+       $ui_comm edit reset
+       $ui_comm edit modified false
+       rescan {set ui_status_value {Ready.}}
+}
+
+set GIT_COMMITTER_IDENT {}
+
+proc committer_ident {} {
+       global GIT_COMMITTER_IDENT
+
+       if {$GIT_COMMITTER_IDENT eq {}} {
+               if {[catch {set me [exec git var GIT_COMMITTER_IDENT]} err]} {
+                       error_popup "Unable to obtain your identity:\n\n$err"
+                       return {}
+               }
+               if {![regexp {^(.*) [0-9]+ [-+0-9]+$} \
+                       $me me GIT_COMMITTER_IDENT]} {
+                       error_popup "Invalid GIT_COMMITTER_IDENT:\n\n$me"
+                       return {}
+               }
+       }
+
+       return $GIT_COMMITTER_IDENT
+}
+
+proc commit_tree {} {
+       global HEAD commit_type file_states ui_comm repo_config
+       global ui_status_value pch_error
+
+       if {[committer_ident] eq {}} return
+       if {![lock_index update]} 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 another commit can be created.
+
+The rescan will be automatically started now.
+}
+               unlock_index
+               rescan {set ui_status_value {Ready.}}
+               return
+       }
+
+       # -- At least one file should differ in the index.
+       #
+       set files_ready 0
+       foreach path [array names file_states] {
+               switch -glob -- [lindex $file_states($path) 0] {
+               _? {continue}
+               A? -
+               D? -
+               M? {set files_ready 1}
+               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.
+"
+                       unlock_index
+                       return
+               }
+               default {
+                       error_popup "Unknown file state [lindex $s 0] detected.
+
+File [short_path $path] cannot be committed by this program.
+"
+               }
+               }
+       }
+       if {!$files_ready} {
+               info_popup {No changes to commit.
+
+You must add at least 1 file before you can commit.
+}
+               unlock_index
+               return
+       }
+
+       # -- A message is required.
+       #
+       set msg [string trim [$ui_comm get 1.0 end]]
+       regsub -all -line {[ \t\r]+$} $msg {} msg
+       if {$msg eq {}} {
+               error_popup {Please supply a commit message.
+
+A good commit message has the following format:
+
+- First line: Describe in one sentance what you did.
+- Second line: Blank
+- Remaining lines: Describe why this change is good.
+}
+               unlock_index
+               return
+       }
+
+       # -- Run the pre-commit hook.
+       #
+       set pchook [gitdir hooks pre-commit]
+
+       # On Cygwin [file executable] might lie so we need to ask
+       # the shell if the hook is executable.  Yes that's annoying.
+       #
+       if {[is_Cygwin] && [file isfile $pchook]} {
+               set pchook [list sh -c [concat \
+                       "if test -x \"$pchook\";" \
+                       "then exec \"$pchook\" 2>&1;" \
+                       "fi"]]
+       } elseif {[file executable $pchook]} {
+               set pchook [list $pchook |& cat]
+       } else {
+               commit_writetree $curHEAD $msg
+               return
+       }
+
+       set ui_status_value {Calling pre-commit hook...}
+       set pch_error {}
+       set fd_ph [open "| $pchook" r]
+       fconfigure $fd_ph -blocking 0 -translation binary
+       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
+
+       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.}
+                       hook_failed_popup pre-commit $pch_error
+                       unlock_index
+               } else {
+                       commit_writetree $curHEAD $msg
+               }
+               set pch_error {}
+               return
+       }
+       fconfigure $fd_ph -blocking 0
+}
+
+proc commit_writetree {curHEAD msg} {
+       global ui_status_value
+
+       set ui_status_value {Committing changes...}
+       set fd_wt [open "| git write-tree" r]
+       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 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.}
+               unlock_index
+               return
+       }
+
+       # -- Build the message.
+       #
+       set msg_p [gitdir COMMIT_EDITMSG]
+       set msg_wt [open $msg_p w]
+       if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+               set enc utf-8
+       }
+       fconfigure $msg_wt -encoding $enc -translation binary
+       puts -nonewline $msg_wt $msg
+       close $msg_wt
+
+       # -- Create the commit.
+       #
+       set cmd [list git commit-tree $tree_id]
+       set parents [concat $PARENT $MERGE_HEAD]
+       if {[llength $parents] > 0} {
+               foreach p $parents {
+                       lappend cmd -p $p
+               }
+       } else {
+               # git commit-tree writes to stderr during initial commit.
+               lappend cmd 2>/dev/null
+       }
+       lappend cmd <$msg_p
+       if {[catch {set cmt_id [eval exec $cmd]} err]} {
+               error_popup "commit-tree failed:\n\n$err"
+               set ui_status_value {Commit failed.}
+               unlock_index
+               return
+       }
+
+       # -- Update the HEAD ref.
+       #
+       set reflogm commit
+       if {$commit_type ne {normal}} {
+               append reflogm " ($commit_type)"
+       }
+       set i [string first "\n" $msg]
+       if {$i >= 0} {
+               append reflogm {: } [string range $msg 0 [expr {$i - 1}]]
+       } else {
+               append reflogm {: } $msg
+       }
+       set cmd [list git update-ref -m $reflogm HEAD $cmt_id $curHEAD]
+       if {[catch {eval exec $cmd} err]} {
+               error_popup "update-ref failed:\n\n$err"
+               set ui_status_value {Commit failed.}
+               unlock_index
+               return
+       }
+
+       # -- 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
+       }
+
+       # -- Cleanup after ourselves.
+       #
+       catch {file delete $msg_p}
+       catch {file delete [gitdir MERGE_HEAD]}
+       catch {file delete [gitdir MERGE_MSG]}
+       catch {file delete [gitdir SQUASH_MSG]}
+       catch {file delete [gitdir GITGUI_MSG]}
+
+       # -- Let rerere do its thing.
+       #
+       if {[file isdirectory [gitdir rr-cache]]} {
+               catch {exec git rerere}
+       }
+
+       # -- Run the post-commit hook.
+       #
+       set pchook [gitdir hooks post-commit]
+       if {[is_Cygwin] && [file isfile $pchook]} {
+               set pchook [list sh -c [concat \
+                       "if test -x \"$pchook\";" \
+                       "then exec \"$pchook\";" \
+                       "fi"]]
+       } elseif {![file executable $pchook]} {
+               set pchook {}
+       }
+       if {$pchook ne {}} {
+               catch {exec $pchook &}
+       }
+
+       $ui_comm delete 0.0 end
+       $ui_comm edit reset
+       $ui_comm edit modified false
+
+       if {[is_enabled singlecommit]} do_quit
+
+       # -- Update in memory status
+       #
+       set selected_commit_type new
+       set commit_type normal
+       set HEAD $cmt_id
+       set PARENT $cmt_id
+       set MERGE_HEAD [list]
+
+       foreach path [array names file_states] {
+               set s $file_states($path)
+               set m [lindex $s 0]
+               switch -glob -- $m {
+               _O -
+               _M -
+               _D {continue}
+               __ -
+               A_ -
+               M_ -
+               D_ {
+                       unset file_states($path)
+                       catch {unset selected_paths($path)}
+               }
+               DO {
+                       set file_states($path) [list _O [lindex $s 1] {} {}]
+               }
+               AM -
+               AD -
+               MM -
+               MD {
+                       set file_states($path) [list \
+                               _[string index $m 1] \
+                               [lindex $s 1] \
+                               [lindex $s 3] \
+                               {}]
+               }
+               }
+       }
+
+       display_all_files
+       unlock_index
+       reshow_diff
+       set ui_status_value \
+               "Changes committed as [string range $cmt_id 0 7]."
+}
+
+######################################################################
+##
+## fetch push
+
+proc fetch_from {remote} {
+       set w [new_console \
+               "fetch $remote" \
+               "Fetching new changes from $remote"]
+       set cmd [list git fetch]
+       lappend cmd $remote
+       console_exec $w $cmd console_done
+}
+
+proc push_to {remote} {
+       set w [new_console \
+               "push $remote" \
+               "Pushing changes to $remote"]
+       set cmd [list git push]
+       lappend cmd -v
+       lappend cmd $remote
+       console_exec $w $cmd console_done
+}
+
+######################################################################
+##
+## ui helpers
+
+proc mapicon {w state path} {
+       global all_icons
+
+       if {[catch {set r $all_icons($state$w)}]} {
+               puts "error: no icon for $w state={$state} $path"
+               return file_plain
+       }
+       return $r
+}
+
+proc mapdesc {state path} {
+       global all_descs
+
+       if {[catch {set r $all_descs($state)}]} {
+               puts "error: no desc for state={$state} $path"
+               return $state
+       }
+       return $r
+}
+
+proc escape_path {path} {
+       regsub -all {\\} $path "\\\\" path
+       regsub -all "\n" $path "\\n" path
+       return $path
+}
+
+proc short_path {path} {
+       return [escape_path [lindex [file split $path] end]]
+}
+
+set next_icon_id 0
+set null_sha1 [string repeat 0 40]
+
+proc merge_state {path new_state {head_info {}} {index_info {}}} {
+       global file_states next_icon_id null_sha1
+
+       set s0 [string index $new_state 0]
+       set s1 [string index $new_state 1]
+
+       if {[catch {set info $file_states($path)}]} {
+               set state __
+               set icon n[incr next_icon_id]
+       } else {
+               set state [lindex $info 0]
+               set icon [lindex $info 1]
+               if {$head_info eq {}}  {set head_info  [lindex $info 2]}
+               if {$index_info eq {}} {set index_info [lindex $info 3]}
+       }
+
+       if     {$s0 eq {?}} {set s0 [string index $state 0]} \
+       elseif {$s0 eq {_}} {set s0 _}
+
+       if     {$s1 eq {?}} {set s1 [string index $state 1]} \
+       elseif {$s1 eq {_}} {set s1 _}
+
+       if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} {
+               set head_info [list 0 $null_sha1]
+       } elseif {$s0 ne {_} && [string index $state 0] eq {_}
+               && $head_info eq {}} {
+               set head_info $index_info
+       }
+
+       set file_states($path) [list $s0$s1 $icon \
+               $head_info $index_info \
+               ]
+       return $state
+}
+
+proc display_file_helper {w path icon_name old_m new_m} {
+       global file_lists
+
+       if {$new_m eq {_}} {
+               set lno [lsearch -sorted -exact $file_lists($w) $path]
+               if {$lno >= 0} {
+                       set file_lists($w) [lreplace $file_lists($w) $lno $lno]
+                       incr lno
+                       $w conf -state normal
+                       $w delete $lno.0 [expr {$lno + 1}].0
+                       $w conf -state disabled
+               }
+       } elseif {$old_m eq {_} && $new_m ne {_}} {
+               lappend file_lists($w) $path
+               set file_lists($w) [lsort -unique $file_lists($w)]
+               set lno [lsearch -sorted -exact $file_lists($w) $path]
+               incr lno
+               $w conf -state normal
+               $w image create $lno.0 \
+                       -align center -padx 5 -pady 1 \
+                       -name $icon_name \
+                       -image [mapicon $w $new_m $path]
+               $w insert $lno.1 "[escape_path $path]\n"
+               $w conf -state disabled
+       } elseif {$old_m ne $new_m} {
+               $w conf -state normal
+               $w image conf $icon_name -image [mapicon $w $new_m $path]
+               $w conf -state disabled
+       }
+}
+
+proc display_file {path state} {
+       global file_states selected_paths
+       global ui_index ui_workdir
+
+       set old_m [merge_state $path $state]
+       set s $file_states($path)
+       set new_m [lindex $s 0]
+       set icon_name [lindex $s 1]
+
+       set o [string index $old_m 0]
+       set n [string index $new_m 0]
+       if {$o eq {U}} {
+               set o _
+       }
+       if {$n eq {U}} {
+               set n _
+       }
+       display_file_helper     $ui_index $path $icon_name $o $n
+
+       if {[string index $old_m 0] eq {U}} {
+               set o U
+       } else {
+               set o [string index $old_m 1]
+       }
+       if {[string index $new_m 0] eq {U}} {
+               set n U
+       } else {
+               set n [string index $new_m 1]
+       }
+       display_file_helper     $ui_workdir $path $icon_name $o $n
+
+       if {$new_m eq {__}} {
+               unset file_states($path)
+               catch {unset selected_paths($path)}
+       }
+}
+
+proc display_all_files_helper {w path icon_name m} {
+       global file_lists
+
+       lappend file_lists($w) $path
+       set lno [expr {[lindex [split [$w index end] .] 0] - 1}]
+       $w image create end \
+               -align center -padx 5 -pady 1 \
+               -name $icon_name \
+               -image [mapicon $w $m $path]
+       $w insert end "[escape_path $path]\n"
+}
+
+proc display_all_files {} {
+       global ui_index ui_workdir
+       global file_states file_lists
+       global last_clicked
+
+       $ui_index conf -state normal
+       $ui_workdir conf -state normal
+
+       $ui_index delete 0.0 end
+       $ui_workdir delete 0.0 end
+       set last_clicked {}
+
+       set file_lists($ui_index) [list]
+       set file_lists($ui_workdir) [list]
+
+       foreach path [lsort [array names file_states]] {
+               set s $file_states($path)
+               set m [lindex $s 0]
+               set icon_name [lindex $s 1]
+
+               set s [string index $m 0]
+               if {$s ne {U} && $s ne {_}} {
+                       display_all_files_helper $ui_index $path \
+                               $icon_name $s
+               }
+
+               if {[string index $m 0] eq {U}} {
+                       set s U
+               } else {
+                       set s [string index $m 1]
+               }
+               if {$s ne {_}} {
+                       display_all_files_helper $ui_workdir $path \
+                               $icon_name $s
+               }
+       }
+
+       $ui_index conf -state disabled
+       $ui_workdir conf -state disabled
+}
+
+proc update_indexinfo {msg pathList after} {
+       global update_index_cp ui_status_value
+
+       if {![lock_index update]} return
+
+       set update_index_cp 0
+       set pathList [lsort $pathList]
+       set totalCnt [llength $pathList]
+       set batch [expr {int($totalCnt * .01) + 1}]
+       if {$batch > 25} {set batch 25}
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               0.0]
+       set fd [open "| git update-index -z --index-info" w]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+               -buffersize 512 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd writable [list \
+               write_update_indexinfo \
+               $fd \
+               $pathList \
+               $totalCnt \
+               $batch \
+               $msg \
+               $after \
+               ]
+}
+
+proc write_update_indexinfo {fd pathList totalCnt batch msg after} {
+       global update_index_cp ui_status_value
+       global file_states current_diff_path
+
+       if {$update_index_cp >= $totalCnt} {
+               close $fd
+               unlock_index
+               uplevel #0 $after
+               return
+       }
+
+       for {set i $batch} \
+               {$update_index_cp < $totalCnt && $i > 0} \
+               {incr i -1} {
+               set path [lindex $pathList $update_index_cp]
+               incr update_index_cp
+
+               set s $file_states($path)
+               switch -glob -- [lindex $s 0] {
+               A? {set new _O}
+               M? {set new _M}
+               D_ {set new _D}
+               D? {set new _?}
+               ?? {continue}
+               }
+               set info [lindex $s 2]
+               if {$info eq {}} continue
+
+               puts -nonewline $fd "$info\t[encoding convertto $path]\0"
+               display_file $path $new
+       }
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               [expr {100.0 * $update_index_cp / $totalCnt}]]
+}
+
+proc update_index {msg pathList after} {
+       global update_index_cp ui_status_value
+
+       if {![lock_index update]} return
+
+       set update_index_cp 0
+       set pathList [lsort $pathList]
+       set totalCnt [llength $pathList]
+       set batch [expr {int($totalCnt * .01) + 1}]
+       if {$batch > 25} {set batch 25}
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               0.0]
+       set fd [open "| git update-index --add --remove -z --stdin" w]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+               -buffersize 512 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd writable [list \
+               write_update_index \
+               $fd \
+               $pathList \
+               $totalCnt \
+               $batch \
+               $msg \
+               $after \
+               ]
+}
+
+proc write_update_index {fd pathList totalCnt batch msg after} {
+       global update_index_cp ui_status_value
+       global file_states current_diff_path
+
+       if {$update_index_cp >= $totalCnt} {
+               close $fd
+               unlock_index
+               uplevel #0 $after
+               return
+       }
+
+       for {set i $batch} \
+               {$update_index_cp < $totalCnt && $i > 0} \
+               {incr i -1} {
+               set path [lindex $pathList $update_index_cp]
+               incr update_index_cp
+
+               switch -glob -- [lindex $file_states($path) 0] {
+               AD {set new __}
+               ?D {set new D_}
+               _O -
+               AM {set new A_}
+               U? {
+                       if {[file exists $path]} {
+                               set new M_
+                       } else {
+                               set new D_
+                       }
+               }
+               ?M {set new M_}
+               ?? {continue}
+               }
+               puts -nonewline $fd "[encoding convertto $path]\0"
+               display_file $path $new
+       }
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               [expr {100.0 * $update_index_cp / $totalCnt}]]
+}
+
+proc checkout_index {msg pathList after} {
+       global update_index_cp ui_status_value
+
+       if {![lock_index update]} return
+
+       set update_index_cp 0
+       set pathList [lsort $pathList]
+       set totalCnt [llength $pathList]
+       set batch [expr {int($totalCnt * .01) + 1}]
+       if {$batch > 25} {set batch 25}
+
+       set ui_status_value [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]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+               -buffersize 512 \
+               -encoding binary \
+               -translation binary
+       fileevent $fd writable [list \
+               write_checkout_index \
+               $fd \
+               $pathList \
+               $totalCnt \
+               $batch \
+               $msg \
+               $after \
+               ]
+}
+
+proc write_checkout_index {fd pathList totalCnt batch msg after} {
+       global update_index_cp ui_status_value
+       global file_states current_diff_path
+
+       if {$update_index_cp >= $totalCnt} {
+               close $fd
+               unlock_index
+               uplevel #0 $after
+               return
+       }
+
+       for {set i $batch} \
+               {$update_index_cp < $totalCnt && $i > 0} \
+               {incr i -1} {
+               set path [lindex $pathList $update_index_cp]
+               incr update_index_cp
+               switch -glob -- [lindex $file_states($path) 0] {
+               U? {continue}
+               ?M -
+               ?D {
+                       puts -nonewline $fd "[encoding convertto $path]\0"
+                       display_file $path ?_
+               }
+               }
+       }
+
+       set ui_status_value [format \
+               "$msg... %i/%i files (%.2f%%)" \
+               $update_index_cp \
+               $totalCnt \
+               [expr {100.0 * $update_index_cp / $totalCnt}]]
+}
+
+######################################################################
+##
+## branch management
+
+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]} {
+                       return 1
+               }
+       }
+       return 0
+}
+
+proc load_all_heads {} {
+       global all_heads
+
+       set all_heads [list]
+       set fd [open "| git for-each-ref --format=%(refname) refs/heads" r]
+       while {[gets $fd line] > 0} {
+               if {[is_tracking_branch $line]} continue
+               if {![regsub ^refs/heads/ $line {} name]} continue
+               lappend all_heads $name
+       }
+       close $fd
+
+       set all_heads [lsort $all_heads]
+}
+
+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 \
+                       -font font_ui
+               lappend disable_on_lock \
+                       [list $m entryconf [$m index last] -state]
+       }
+}
+
+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
+               } else {
+                       regsub ^refs/(heads|remotes)/ $name {} name
+                       lappend all_trackings $name
+               }
+       }
+
+       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
+               }
+               close $fd
+       }
+
+       return [lsort -unique $all_trackings]
+}
+
+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
+
+       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 {exec 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 {exec 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}
+       expression {set rev $create_branch_revexp}
+       }
+       if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} {
+               tk_messageBox \
+                       -icon error \
+                       -type ok \
+                       -title [wm title $w] \
+                       -parent $w \
+                       -message "Invalid starting revision: $rev"
+               return
+       }
+       set cmd [list git update-ref]
+       lappend cmd -m
+       lappend cmd "branch: Created from $rev"
+       lappend cmd "refs/heads/$newbranch"
+       lappend cmd $cmt
+       lappend cmd $null_sha1
+       if {[catch {eval exec $cmd} 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
+       }
+}
+
+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 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
+
+       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 \
+               -font font_ui \
+               -default active \
+               -command [list do_create_branch_action $w]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -font font_ui \
+               -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} \
+               -font font_ui
+       label $w.desc.name_l -text {Name:} -font font_ui
+       entry $w.desc.name_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 40 \
+               -textvariable create_branch_name \
+               -font font_ui \
+               -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} \
+               -font font_ui
+       radiobutton $w.from.head_r \
+               -text {Local Branch:} \
+               -value head \
+               -variable create_branch_revtype \
+               -font font_ui
+       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 \
+                       -font font_ui
+               eval tk_optionMenu $w.from.tracking_m \
+                       create_branch_trackinghead \
+                       $all_trackings
+               grid $w.from.tracking_r $w.from.tracking_m -sticky w
+       }
+       radiobutton $w.from.exp_r \
+               -text {Revision Expression:} \
+               -value expression \
+               -variable create_branch_revtype \
+               -font font_ui
+       entry $w.from.exp_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 50 \
+               -textvariable create_branch_revexp \
+               -font font_ui \
+               -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} \
+               -font font_ui
+       checkbutton $w.postActions.checkout \
+               -text {Checkout after creation} \
+               -variable create_branch_checkout \
+               -font font_ui
+       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 [exec 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 [exec git rev-parse --verify $b]}]} continue
+               if {$check_cmt ne {}} {
+                       if {$b eq $check_rev} continue
+                       if {[catch {set m [exec 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 {exec 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 \
+               -font font_ui \
+               -command [list do_delete_branch_action $w]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -font font_ui \
+               -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} \
+               -font font_ui
+       listbox $w.list.l \
+               -height 10 \
+               -width 70 \
+               -selectmode extended \
+               -yscrollcommand [list $w.list.sby set] \
+               -font font_ui
+       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} \
+               -font font_ui
+       radiobutton $w.validate.head_r \
+               -text {Merged Into Local Branch:} \
+               -value head \
+               -variable delete_branch_checktype \
+               -font font_ui
+       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 \
+                       -font font_ui
+               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 \
+               -font font_ui
+       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 {exec 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'."
+       }
+}
+
+######################################################################
+##
+## remote management
+
+proc load_all_remotes {} {
+       global repo_config
+       global all_remotes tracking_branches
+
+       set all_remotes [list]
+       array unset tracking_branches
+
+       set rm_dir [gitdir remotes]
+       if {[file isdirectory $rm_dir]} {
+               set all_remotes [glob \
+                       -types f \
+                       -tails \
+                       -nocomplain \
+                       -directory $rm_dir *]
+
+               foreach name $all_remotes {
+                       catch {
+                               set fd [open [file join $rm_dir $name] r]
+                               while {[gets $fd line] >= 0} {
+                                       if {![regexp {^Pull:[   ]*([^:]+):(.+)$} \
+                                               $line line src dst]} continue
+                                       if {![regexp ^refs/ $dst]} {
+                                               set dst "refs/heads/$dst"
+                                       }
+                                       set tracking_branches($dst) [list $name $src]
+                               }
+                               close $fd
+                       }
+               }
+       }
+
+       foreach line [array names repo_config remote.*.url] {
+               if {![regexp ^remote\.(.*)\.url\$ $line line name]} continue
+               lappend all_remotes $name
+
+               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"
+                       }
+                       set tracking_branches($dst) [list $name $src]
+               }
+       }
+
+       set all_remotes [lsort -unique $all_remotes]
+}
+
+proc populate_fetch_menu {} {
+       global all_remotes repo_config
+
+       set m .mbar.fetch
+       foreach r $all_remotes {
+               set enable 0
+               if {![catch {set a $repo_config(remote.$r.url)}]} {
+                       if {![catch {set a $repo_config(remote.$r.fetch)}]} {
+                               set enable 1
+                       }
+               } else {
+                       catch {
+                               set fd [open [gitdir remotes $r] r]
+                               while {[gets $fd n] >= 0} {
+                                       if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
+                                               set enable 1
+                                               break
+                                       }
+                               }
+                               close $fd
+                       }
+               }
+
+               if {$enable} {
+                       $m add command \
+                               -label "Fetch from $r..." \
+                               -command [list fetch_from $r] \
+                               -font font_ui
+               }
+       }
+}
+
+proc populate_push_menu {} {
+       global all_remotes repo_config
+
+       set m .mbar.push
+       set fast_count 0
+       foreach r $all_remotes {
+               set enable 0
+               if {![catch {set a $repo_config(remote.$r.url)}]} {
+                       if {![catch {set a $repo_config(remote.$r.push)}]} {
+                               set enable 1
+                       }
+               } else {
+                       catch {
+                               set fd [open [gitdir remotes $r] r]
+                               while {[gets $fd n] >= 0} {
+                                       if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
+                                               set enable 1
+                                               break
+                                       }
+                               }
+                               close $fd
+                       }
+               }
+
+               if {$enable} {
+                       if {!$fast_count} {
+                               $m add separator
+                       }
+                       $m add command \
+                               -label "Push to $r..." \
+                               -command [list push_to $r] \
+                               -font font_ui
+                       incr fast_count
+               }
+       }
+}
+
+proc start_push_anywhere_action {w} {
+       global push_urltype push_remote push_url push_thin push_tags
+
+       set r_url {}
+       switch -- $push_urltype {
+       remote {set r_url $push_remote}
+       url {set r_url $push_url}
+       }
+       if {$r_url eq {}} return
+
+       set cmd [list git push]
+       lappend cmd -v
+       if {$push_thin} {
+               lappend cmd --thin
+       }
+       if {$push_tags} {
+               lappend cmd --tags
+       }
+       lappend cmd $r_url
+       set cnt 0
+       foreach i [$w.source.l curselection] {
+               set b [$w.source.l get $i]
+               lappend cmd "refs/heads/$b:refs/heads/$b"
+               incr cnt
+       }
+       if {$cnt == 0} {
+               return
+       } elseif {$cnt == 1} {
+               set unit branch
+       } else {
+               set unit branches
+       }
+
+       set cons [new_console "push $r_url" "Pushing $cnt $unit to $r_url"]
+       console_exec $cons $cmd console_done
+       destroy $w
+}
+
+trace add variable push_remote write \
+       [list radio_selector push_urltype remote]
+
+proc do_push_anywhere {} {
+       global all_heads all_remotes current_branch
+       global push_urltype push_remote push_url push_thin push_tags
+
+       set w .push_setup
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text {Push Branches} -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.create -text Push \
+               -font font_ui \
+               -command [list start_push_anywhere_action $w]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -font font_ui \
+               -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.source \
+               -text {Source Branches} \
+               -font font_ui
+       listbox $w.source.l \
+               -height 10 \
+               -width 70 \
+               -selectmode extended \
+               -yscrollcommand [list $w.source.sby set] \
+               -font font_ui
+       foreach h $all_heads {
+               $w.source.l insert end $h
+               if {$h eq $current_branch} {
+                       $w.source.l select set end
+               }
+       }
+       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
+
+       labelframe $w.dest \
+               -text {Destination Repository} \
+               -font font_ui
+       if {$all_remotes ne {}} {
+               radiobutton $w.dest.remote_r \
+                       -text {Remote:} \
+                       -value remote \
+                       -variable push_urltype \
+                       -font font_ui
+               eval tk_optionMenu $w.dest.remote_m push_remote $all_remotes
+               grid $w.dest.remote_r $w.dest.remote_m -sticky w
+               if {[lsearch -sorted -exact $all_remotes origin] != -1} {
+                       set push_remote origin
+               } else {
+                       set push_remote [lindex $all_remotes 0]
+               }
+               set push_urltype remote
+       } else {
+               set push_urltype url
+       }
+       radiobutton $w.dest.url_r \
+               -text {Arbitrary URL:} \
+               -value url \
+               -variable push_urltype \
+               -font font_ui
+       entry $w.dest.url_t \
+               -borderwidth 1 \
+               -relief sunken \
+               -width 50 \
+               -textvariable push_url \
+               -font font_ui \
+               -validate key \
+               -validatecommand {
+                       if {%d == 1 && [regexp {\s} %S]} {return 0}
+                       if {%d == 1 && [string length %S] > 0} {
+                               set push_urltype url
+                       }
+                       return 1
+               }
+       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.options \
+               -text {Transfer Options} \
+               -font font_ui
+       checkbutton $w.options.thin \
+               -text {Use thin pack (for slow network connections)} \
+               -variable push_thin \
+               -font font_ui
+       grid $w.options.thin -columnspan 2 -sticky w
+       checkbutton $w.options.tags \
+               -text {Include tags} \
+               -variable push_tags \
+               -font font_ui
+       grid $w.options.tags -columnspan 2 -sticky w
+       grid columnconfigure $w.options 1 -weight 1
+       pack $w.options -anchor nw -fill x -pady 5 -padx 5
+
+       set push_url {}
+       set push_thin 0
+       set push_tags 0
+
+       bind $w <Visibility> "grab $w"
+       bind $w <Key-Escape> "destroy $w"
+       wm title $w "[appname] ([reponame]): Push"
+       tkwait window $w
+}
+
+######################################################################
+##
+## merge
+
+proc can_merge {} {
+       global HEAD commit_type file_states
+
+       if {[string match amend* $commit_type]} {
+               info_popup {Cannot merge while amending.
+
+You must finish amending this commit before
+starting any type of merge.
+}
+               return 0
+       }
+
+       if {[committer_ident] eq {}} {return 0}
+       if {![lock_index merge]} {return 0}
+
+       # -- Our in memory state should match the repository.
+       #
+       repository_state curType curHEAD curMERGE_HEAD
+       if {$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 a merge can be performed.
+
+The rescan will be automatically started now.
+}
+               unlock_index
+               rescan {set ui_status_value {Ready.}}
+               return 0
+       }
+
+       foreach path [array names file_states] {
+               switch -glob -- [lindex $file_states($path) 0] {
+               _O {
+                       continue; # and pray it works!
+               }
+               U? {
+                       error_popup "You are in the middle of a conflicted merge.
+
+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.
+"
+                       unlock_index
+                       return 0
+               }
+               ?? {
+                       error_popup "You are in the middle of a change.
+
+File [short_path $path] is modified.
+
+You should complete the current commit before
+starting a merge.  Doing so will help you abort
+a failed merge, should the need arise.
+"
+                       unlock_index
+                       return 0
+               }
+               }
+       }
+
+       return 1
+}
+
+proc visualize_local_merge {w} {
+       set revs {}
+       foreach i [$w.source.l curselection] {
+               lappend revs [$w.source.l get $i]
+       }
+       if {$revs eq {}} return
+       lappend revs --not HEAD
+       do_gitk $revs
+}
+
+proc start_local_merge_action {w} {
+       global HEAD ui_status_value current_branch
+
+       set cmd [list git merge]
+       set names {}
+       set revcnt 0
+       foreach i [$w.source.l curselection] {
+               set b [$w.source.l get $i]
+               lappend cmd $b
+               lappend names $b
+               incr revcnt
+       }
+
+       if {$revcnt == 0} {
+               return
+       } elseif {$revcnt == 1} {
+               set unit branch
+       } elseif {$revcnt <= 15} {
+               set unit branches
+       } 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.
+
+Please select fewer branches.  To merge more
+than 15 branches, merge the branches in batches.
+"
+               return
+       }
+
+       set msg "Merging $current_branch, [join $names {, }]"
+       set ui_status_value "$msg..."
+       set cons [new_console "Merge" $msg]
+       console_exec $cons $cmd [list finish_merge $revcnt]
+       bind $w <Destroy> {}
+       destroy $w
+}
+
+proc finish_merge {revcnt w ok} {
+       console_done $w $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 [list reset_hard_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]
+}
+
+proc do_local_merge {} {
+       global current_branch
+
+       if {![can_merge]} return
+
+       set w .merge_setup
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header \
+               -text "Merge Into $current_branch" \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.visualize -text Visualize \
+               -font font_ui \
+               -command [list visualize_local_merge $w]
+       pack $w.buttons.visualize -side left
+       button $w.buttons.create -text Merge \
+               -font font_ui \
+               -command [list start_local_merge_action $w]
+       pack $w.buttons.create -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -font font_ui \
+               -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.source \
+               -text {Source Branches} \
+               -font font_ui
+       listbox $w.source.l \
+               -height 10 \
+               -width 70 \
+               -selectmode extended \
+               -yscrollcommand [list $w.source.sby set] \
+               -font font_ui
+       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
+
+       set cmd [list git for-each-ref]
+       lappend cmd {--format=%(objectname) %(refname)}
+       lappend cmd refs/heads
+       lappend cmd refs/remotes
+       set fr_fd [open "| $cmd" r]
+       fconfigure $fr_fd -translation binary
+       while {[gets $fr_fd line] > 0} {
+               set line [split $line { }]
+               set sha1([lindex $line 0]) [lindex $line 1]
+       }
+       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
+               regsub ^refs/(heads|remotes)/ $ref {} ref
+               lappend to_show $ref
+       }
+       close $fr_fd
+
+       foreach ref [lsort -unique $to_show] {
+               $w.source.l insert end $ref
+       }
+
+       bind $w <Visibility> "grab $w"
+       bind $w <Key-Escape> "unlock_index;destroy $w"
+       bind $w <Destroy> unlock_index
+       wm title $w "[appname] ([reponame]): Merge"
+       tkwait window $w
+}
+
+proc do_reset_hard {} {
+       global HEAD commit_type file_states
+
+       if {[string match amend* $commit_type]} {
+               info_popup {Cannot abort while amending.
+
+You must finish amending this commit.
+}
+               return
+       }
+
+       if {![lock_index abort]} return
+
+       if {[string match *merge* $commit_type]} {
+               set op merge
+       } else {
+               set op commit
+       }
+
+       if {[ask_popup "Abort $op?
+
+Aborting the current $op will cause
+*ALL* uncommitted changes to be lost.
+
+Continue with aborting the current $op?"] eq {yes}} {
+               set fd [open "| git read-tree --reset -u HEAD" r]
+               fconfigure $fd -blocking 0 -translation binary
+               fileevent $fd readable [list reset_hard_wait $fd]
+               set ui_status_value {Aborting... please wait...}
+       } else {
+               unlock_index
+       }
+}
+
+proc reset_hard_wait {fd} {
+       global ui_comm
+
+       read $fd
+       if {[eof $fd]} {
+               close $fd
+               unlock_index
+
+               $ui_comm delete 0.0 end
+               $ui_comm edit modified false
+
+               catch {file delete [gitdir MERGE_HEAD]}
+               catch {file delete [gitdir rr-cache MERGE_RR]}
+               catch {file delete [gitdir SQUASH_MSG]}
+               catch {file delete [gitdir MERGE_MSG]}
+               catch {file delete [gitdir GITGUI_MSG]}
+
+               rescan {set ui_status_value {Abort completed.  Ready.}}
+       }
+}
+
+######################################################################
+##
+## browser
+
+set next_browser_id 0
+
+proc new_browser {commit} {
+       global next_browser_id cursor_ptr M1B
+       global browser_commit browser_status browser_stack browser_path browser_busy
+
+       set w .browser[incr next_browser_id]
+       set w_list $w.list.l
+       set browser_commit($w_list) $commit
+       set browser_status($w_list) {Starting...}
+       set browser_stack($w_list) {}
+       set browser_path($w_list) $browser_commit($w_list):
+       set browser_busy($w_list) 1
+
+       toplevel $w
+       label $w.path -textvariable browser_path($w_list) \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken \
+               -font font_uibold
+       pack $w.path -anchor w -side top -fill x
+
+       frame $w.list
+       text $w_list -background white -borderwidth 0 \
+               -cursor $cursor_ptr \
+               -state disabled \
+               -wrap none \
+               -height 20 \
+               -width 70 \
+               -xscrollcommand [list $w.list.sbx set] \
+               -yscrollcommand [list $w.list.sby set] \
+               -font font_ui
+       $w_list tag conf in_sel \
+               -background [$w_list cget -foreground] \
+               -foreground [$w_list cget -background]
+       scrollbar $w.list.sbx -orient h -command [list $w_list xview]
+       scrollbar $w.list.sby -orient v -command [list $w_list yview]
+       pack $w.list.sbx -side bottom -fill x
+       pack $w.list.sby -side right -fill y
+       pack $w_list -side left -fill both -expand 1
+       pack $w.list -side top -fill both -expand 1
+
+       label $w.status -textvariable browser_status($w_list) \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken \
+               -font font_ui
+       pack $w.status -anchor w -side bottom -fill x
+
+       bind $w_list <Button-1>        "browser_click 0 $w_list @%x,%y;break"
+       bind $w_list <Double-Button-1> "browser_click 1 $w_list @%x,%y;break"
+       bind $w_list <$M1B-Up>         "browser_parent $w_list;break"
+       bind $w_list <$M1B-Left>       "browser_parent $w_list;break"
+       bind $w_list <Up>              "browser_move -1 $w_list;break"
+       bind $w_list <Down>            "browser_move 1 $w_list;break"
+       bind $w_list <$M1B-Right>      "browser_enter $w_list;break"
+       bind $w_list <Return>          "browser_enter $w_list;break"
+       bind $w_list <Prior>           "browser_page -1 $w_list;break"
+       bind $w_list <Next>            "browser_page 1 $w_list;break"
+       bind $w_list <Left>            break
+       bind $w_list <Right>           break
+
+       bind $w <Visibility> "focus $w"
+       bind $w <Destroy> "
+               array unset browser_buffer $w_list
+               array unset browser_files $w_list
+               array unset browser_status $w_list
+               array unset browser_stack $w_list
+               array unset browser_path $w_list
+               array unset browser_commit $w_list
+               array unset browser_busy $w_list
+       "
+       wm title $w "[appname] ([reponame]): File Browser"
+       ls_tree $w_list $browser_commit($w_list) {}
+}
+
+proc browser_move {dir w} {
+       global browser_files browser_busy
+
+       if {$browser_busy($w)} return
+       set lno [lindex [split [$w index in_sel.first] .] 0]
+       incr lno $dir
+       if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
+               $w tag remove in_sel 0.0 end
+               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
+               $w see $lno.0
+       }
+}
+
+proc browser_page {dir w} {
+       global browser_files browser_busy
+
+       if {$browser_busy($w)} return
+       $w yview scroll $dir pages
+       set lno [expr {int(
+                 [lindex [$w yview] 0]
+               * [llength $browser_files($w)]
+               + 1)}]
+       if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
+               $w tag remove in_sel 0.0 end
+               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
+               $w see $lno.0
+       }
+}
+
+proc browser_parent {w} {
+       global browser_files browser_status browser_path
+       global browser_stack browser_busy
+
+       if {$browser_busy($w)} return
+       set info [lindex $browser_files($w) 0]
+       if {[lindex $info 0] eq {parent}} {
+               set parent [lindex $browser_stack($w) end-1]
+               set browser_stack($w) [lrange $browser_stack($w) 0 end-2]
+               if {$browser_stack($w) eq {}} {
+                       regsub {:.*$} $browser_path($w) {:} browser_path($w)
+               } else {
+                       regsub {/[^/]+$} $browser_path($w) {} browser_path($w)
+               }
+               set browser_status($w) "Loading $browser_path($w)..."
+               ls_tree $w [lindex $parent 0] [lindex $parent 1]
+       }
+}
+
+proc browser_enter {w} {
+       global browser_files browser_status browser_path
+       global browser_commit browser_stack browser_busy
+
+       if {$browser_busy($w)} return
+       set lno [lindex [split [$w index in_sel.first] .] 0]
+       set info [lindex $browser_files($w) [expr {$lno - 1}]]
+       if {$info ne {}} {
+               switch -- [lindex $info 0] {
+               parent {
+                       browser_parent $w
+               }
+               tree {
+                       set name [lindex $info 2]
+                       set escn [escape_path $name]
+                       set browser_status($w) "Loading $escn..."
+                       append browser_path($w) $escn
+                       ls_tree $w [lindex $info 1] $name
+               }
+               blob {
+                       set name [lindex $info 2]
+                       set p {}
+                       foreach n $browser_stack($w) {
+                               append p [lindex $n 1]
+                       }
+                       append p $name
+                       show_blame $browser_commit($w) $p
+               }
+               }
+       }
+}
+
+proc browser_click {was_double_click w pos} {
+       global browser_files browser_busy
+
+       if {$browser_busy($w)} return
+       set lno [lindex [split [$w index $pos] .] 0]
+       focus $w
+
+       if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} {
+               $w tag remove in_sel 0.0 end
+               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
+               if {$was_double_click} {
+                       browser_enter $w
+               }
+       }
+}
+
+proc ls_tree {w tree_id name} {
+       global browser_buffer browser_files browser_stack browser_busy
+
+       set browser_buffer($w) {}
+       set browser_files($w) {}
+       set browser_busy($w) 1
+
+       $w conf -state normal
+       $w tag remove in_sel 0.0 end
+       $w delete 0.0 end
+       if {$browser_stack($w) ne {}} {
+               $w image create end \
+                       -align center -padx 5 -pady 1 \
+                       -name icon0 \
+                       -image file_uplevel
+               $w insert end {[Up To Parent]}
+               lappend browser_files($w) parent
+       }
+       lappend browser_stack($w) [list $tree_id $name]
+       $w conf -state disabled
+
+       set cmd [list git ls-tree -z $tree_id]
+       set fd [open "| $cmd" r]
+       fconfigure $fd -blocking 0 -translation binary -encoding binary
+       fileevent $fd readable [list read_ls_tree $fd $w]
+}
+
+proc read_ls_tree {fd w} {
+       global browser_buffer browser_files browser_status browser_busy
+
+       if {![winfo exists $w]} {
+               catch {close $fd}
+               return
+       }
+
+       append browser_buffer($w) [read $fd]
+       set pck [split $browser_buffer($w) "\0"]
+       set browser_buffer($w) [lindex $pck end]
+
+       set n [llength $browser_files($w)]
+       $w conf -state normal
+       foreach p [lrange $pck 0 end-1] {
+               set info [split $p "\t"]
+               set path [lindex $info 1]
+               set info [split [lindex $info 0] { }]
+               set type [lindex $info 1]
+               set object [lindex $info 2]
+
+               switch -- $type {
+               blob {
+                       set image file_mod
+               }
+               tree {
+                       set image file_dir
+                       append path /
+               }
+               default {
+                       set image file_question
+               }
+               }
+
+               if {$n > 0} {$w insert end "\n"}
+               $w image create end \
+                       -align center -padx 5 -pady 1 \
+                       -name icon[incr n] \
+                       -image $image
+               $w insert end [escape_path $path]
+               lappend browser_files($w) [list $type $object $path]
+       }
+       $w conf -state disabled
+
+       if {[eof $fd]} {
+               close $fd
+               set browser_status($w) Ready.
+               set browser_busy($w) 0
+               array unset browser_buffer $w
+               if {$n > 0} {
+                       $w tag add in_sel 1.0 2.0
+                       focus -force $w
+               }
+       }
+}
+
+proc show_blame {commit path} {
+       global next_browser_id blame_status blame_data
+
+       if {[winfo ismapped .]} {
+               set w .browser[incr next_browser_id]
+               set tl $w
+               toplevel $w
+       } else {
+               set w {}
+               set tl .
+       }
+       set blame_status($w) {Loading current file content...}
+
+       label $w.path -text "$commit:$path" \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken \
+               -font font_uibold
+       pack $w.path -side top -fill x
+
+       frame $w.out
+       text $w.out.loaded_t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 40 \
+               -width 1 \
+               -font font_diff
+       $w.out.loaded_t tag conf annotated -background grey
+
+       text $w.out.linenumber_t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 40 \
+               -width 5 \
+               -font font_diff
+       $w.out.linenumber_t tag conf linenumber -justify right
+
+       text $w.out.file_t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 40 \
+               -width 80 \
+               -xscrollcommand [list $w.out.sbx set] \
+               -font font_diff
+
+       scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview]
+       scrollbar $w.out.sby -orient v \
+               -command [list scrollbar2many [list \
+               $w.out.loaded_t \
+               $w.out.linenumber_t \
+               $w.out.file_t \
+               ] yview]
+       grid \
+               $w.out.linenumber_t \
+               $w.out.loaded_t \
+               $w.out.file_t \
+               $w.out.sby \
+               -sticky nsew
+       grid conf $w.out.sbx -column 2 -sticky we
+       grid columnconfigure $w.out 2 -weight 1
+       grid rowconfigure $w.out 0 -weight 1
+       pack $w.out -fill both -expand 1
+
+       label $w.status -textvariable blame_status($w) \
+               -anchor w \
+               -justify left \
+               -borderwidth 1 \
+               -relief sunken \
+               -font font_ui
+       pack $w.status -side bottom -fill x
+
+       frame $w.cm
+       text $w.cm.t \
+               -background white -borderwidth 0 \
+               -state disabled \
+               -wrap none \
+               -height 10 \
+               -width 80 \
+               -xscrollcommand [list $w.cm.sbx set] \
+               -yscrollcommand [list $w.cm.sby set] \
+               -font font_diff
+       scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview]
+       scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview]
+       pack $w.cm.sby -side right -fill y
+       pack $w.cm.sbx -side bottom -fill x
+       pack $w.cm.t -expand 1 -fill both
+       pack $w.cm -side bottom -fill x
+
+       menu $w.ctxm -tearoff 0
+       $w.ctxm add command -label "Copy Commit" \
+               -font font_ui \
+               -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY"
+
+       foreach i [list \
+               $w.out.loaded_t \
+               $w.out.linenumber_t \
+               $w.out.file_t] {
+               $i tag conf in_sel \
+                       -background [$i cget -foreground] \
+                       -foreground [$i cget -background]
+               $i conf -yscrollcommand \
+                       [list many2scrollbar [list \
+                       $w.out.loaded_t \
+                       $w.out.linenumber_t \
+                       $w.out.file_t \
+                       ] yview $w.out.sby]
+               bind $i <Button-1> "
+                       blame_click {$w} \\
+                               $w.cm.t \\
+                               $w.out.linenumber_t \\
+                               $w.out.file_t \\
+                               $i @%x,%y
+                       focus $i
+               "
+               bind_button3 $i "
+                       set cursorX %x
+                       set cursorY %y
+                       set cursorW %W
+                       tk_popup $w.ctxm %X %Y
+               "
+       }
+
+       bind $w.cm.t <Button-1> "focus $w.cm.t"
+       bind $tl <Visibility> "focus $tl"
+       bind $tl <Destroy> "
+               array unset blame_status {$w}
+               array unset blame_data $w,*
+       "
+       wm title $tl "[appname] ([reponame]): File Viewer"
+
+       set blame_data($w,commit_count) 0
+       set blame_data($w,commit_list) {}
+       set blame_data($w,total_lines) 0
+       set blame_data($w,blame_lines) 0
+       set blame_data($w,highlight_commit) {}
+       set blame_data($w,highlight_line) -1
+
+       set cmd [list git cat-file blob "$commit:$path"]
+       set fd [open "| $cmd" r]
+       fconfigure $fd -blocking 0 -translation lf -encoding binary
+       fileevent $fd readable [list read_blame_catfile \
+               $fd $w $commit $path \
+               $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t]
+}
+
+proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} {
+       global blame_status blame_data
+
+       if {![winfo exists $w_file]} {
+               catch {close $fd}
+               return
+       }
+
+       set n $blame_data($w,total_lines)
+       $w_load conf -state normal
+       $w_line conf -state normal
+       $w_file conf -state normal
+       while {[gets $fd line] >= 0} {
+               regsub "\r\$" $line {} line
+               incr n
+               $w_load insert end "\n"
+               $w_line insert end "$n\n" linenumber
+               $w_file insert end "$line\n"
+       }
+       $w_load conf -state disabled
+       $w_line conf -state disabled
+       $w_file conf -state disabled
+       set blame_data($w,total_lines) $n
+
+       if {[eof $fd]} {
+               close $fd
+               blame_incremental_status $w
+               set cmd [list git blame -M -C --incremental]
+               lappend cmd $commit -- $path
+               set fd [open "| $cmd" r]
+               fconfigure $fd -blocking 0 -translation lf -encoding binary
+               fileevent $fd readable [list read_blame_incremental $fd $w \
+                       $w_load $w_cmit $w_line $w_file]
+       }
+}
+
+proc read_blame_incremental {fd w w_load w_cmit w_line w_file} {
+       global blame_status blame_data
+
+       if {![winfo exists $w_file]} {
+               catch {close $fd}
+               return
+       }
+
+       while {[gets $fd line] >= 0} {
+               if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \
+                       cmit original_line final_line line_count]} {
+                       set blame_data($w,commit) $cmit
+                       set blame_data($w,original_line) $original_line
+                       set blame_data($w,final_line) $final_line
+                       set blame_data($w,line_count) $line_count
+
+                       if {[catch {set g $blame_data($w,$cmit,order)}]} {
+                               $w_line tag conf g$cmit
+                               $w_file tag conf g$cmit
+                               $w_line tag raise in_sel
+                               $w_file tag raise in_sel
+                               $w_file tag raise sel
+                               set blame_data($w,$cmit,order) $blame_data($w,commit_count)
+                               incr blame_data($w,commit_count)
+                               lappend blame_data($w,commit_list) $cmit
+                       }
+               } elseif {[string match {filename *} $line]} {
+                       set file [string range $line 9 end]
+                       set n $blame_data($w,line_count)
+                       set lno $blame_data($w,final_line)
+                       set cmit $blame_data($w,commit)
+
+                       while {$n > 0} {
+                               if {[catch {set g g$blame_data($w,line$lno,commit)}]} {
+                                       $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c"
+                               } else {
+                                       $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c"
+                                       $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c"
+                               }
+
+                               set blame_data($w,line$lno,commit) $cmit
+                               set blame_data($w,line$lno,file) $file
+                               $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
+                               $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c"
+
+                               if {$blame_data($w,highlight_line) == -1} {
+                                       if {[lindex [$w_file yview] 0] == 0} {
+                                               $w_file see $lno.0
+                                               blame_showcommit $w $w_cmit $w_line $w_file $lno
+                                       }
+                               } elseif {$blame_data($w,highlight_line) == $lno} {
+                                       blame_showcommit $w $w_cmit $w_line $w_file $lno
+                               }
+
+                               incr n -1
+                               incr lno
+                               incr blame_data($w,blame_lines)
+                       }
+
+                       set hc $blame_data($w,highlight_commit)
+                       if {$hc ne {}
+                               && [expr {$blame_data($w,$hc,order) + 1}]
+                                       == $blame_data($w,$cmit,order)} {
+                               blame_showcommit $w $w_cmit $w_line $w_file \
+                                       $blame_data($w,highlight_line)
+                       }
+               } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} {
+                       set blame_data($w,$blame_data($w,commit),$header) $data
+               }
+       }
+
+       if {[eof $fd]} {
+               close $fd
+               set blame_status($w) {Annotation complete.}
+       } else {
+               blame_incremental_status $w
+       }
+}
+
+proc blame_incremental_status {w} {
+       global blame_status blame_data
+
+       set blame_status($w) [format \
+               "Loading annotations... %i of %i lines annotated (%2i%%)" \
+               $blame_data($w,blame_lines) \
+               $blame_data($w,total_lines) \
+               [expr {100 * $blame_data($w,blame_lines)
+                       / $blame_data($w,total_lines)}]]
+}
+
+proc blame_click {w w_cmit w_line w_file cur_w pos} {
+       set lno [lindex [split [$cur_w index $pos] .] 0]
+       if {$lno eq {}} return
+
+       $w_line tag remove in_sel 0.0 end
+       $w_file tag remove in_sel 0.0 end
+       $w_line tag add in_sel $lno.0 "$lno.0 + 1 line"
+       $w_file tag add in_sel $lno.0 "$lno.0 + 1 line"
+
+       blame_showcommit $w $w_cmit $w_line $w_file $lno
+}
+
+set blame_colors {
+       #ff4040
+       #ff40ff
+       #4040ff
+}
+
+proc blame_showcommit {w w_cmit w_line w_file lno} {
+       global blame_colors blame_data repo_config
+
+       set cmit $blame_data($w,highlight_commit)
+       if {$cmit ne {}} {
+               set idx $blame_data($w,$cmit,order)
+               set i 0
+               foreach c $blame_colors {
+                       set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
+                       $w_line tag conf g$h -background white
+                       $w_file tag conf g$h -background white
+                       incr i
+               }
+       }
+
+       $w_cmit conf -state normal
+       $w_cmit delete 0.0 end
+       if {[catch {set cmit $blame_data($w,line$lno,commit)}]} {
+               set cmit {}
+               $w_cmit insert end "Loading annotation..."
+       } else {
+               set idx $blame_data($w,$cmit,order)
+               set i 0
+               foreach c $blame_colors {
+                       set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]]
+                       $w_line tag conf g$h -background $c
+                       $w_file tag conf g$h -background $c
+                       incr i
+               }
+
+               if {[catch {set msg $blame_data($w,$cmit,message)}]} {
+                       set msg {}
+                       catch {
+                               set fd [open "| git cat-file commit $cmit" r]
+                               fconfigure $fd -encoding binary -translation lf
+                               if {[catch {set enc $repo_config(i18n.commitencoding)}]} {
+                                       set enc utf-8
+                               }
+                               while {[gets $fd line] > 0} {
+                                       if {[string match {encoding *} $line]} {
+                                               set enc [string tolower [string range $line 9 end]]
+                                       }
+                               }
+                               fconfigure $fd -encoding $enc
+                               set msg [string trim [read $fd]]
+                               close $fd
+                       }
+                       set blame_data($w,$cmit,message) $msg
+               }
+
+               set author_name {}
+               set author_email {}
+               set author_time {}
+               catch {set author_name $blame_data($w,$cmit,author)}
+               catch {set author_email $blame_data($w,$cmit,author-mail)}
+               catch {set author_time [clock format $blame_data($w,$cmit,author-time)]}
+
+               set committer_name {}
+               set committer_email {}
+               set committer_time {}
+               catch {set committer_name $blame_data($w,$cmit,committer)}
+               catch {set committer_email $blame_data($w,$cmit,committer-mail)}
+               catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]}
+
+               $w_cmit insert end "commit $cmit\n"
+               $w_cmit insert end "Author: $author_name $author_email $author_time\n"
+               $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n"
+               $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n"
+               $w_cmit insert end "\n"
+               $w_cmit insert end $msg
+       }
+       $w_cmit conf -state disabled
+
+       set blame_data($w,highlight_line) $lno
+       set blame_data($w,highlight_commit) $cmit
+}
+
+proc blame_copycommit {w i pos} {
+       global blame_data
+       set lno [lindex [split [$i index $pos] .] 0]
+       if {![catch {set commit $blame_data($w,line$lno,commit)}]} {
+               clipboard clear
+               clipboard append \
+                       -format STRING \
+                       -type STRING \
+                       -- $commit
+       }
+}
+
+######################################################################
+##
+## icons
+
+set filemask {
+#define mask_width 14
+#define mask_height 15
+static unsigned char mask_bits[] = {
+   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
+   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f,
+   0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f};
+}
+
+image create bitmap file_plain -background white -foreground black -data {
+#define plain_width 14
+#define plain_height 15
+static unsigned char plain_bits[] = {
+   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
+   0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10,
+   0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
+image create bitmap file_mod -background white -foreground blue -data {
+#define mod_width 14
+#define mod_height 15
+static unsigned char mod_bits[] = {
+   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
+   0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
+   0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
+image create bitmap file_fulltick -background white -foreground "#007000" -data {
+#define file_fulltick_width 14
+#define file_fulltick_height 15
+static unsigned char file_fulltick_bits[] = {
+   0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16,
+   0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10,
+   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
+image create bitmap file_parttick -background white -foreground "#005050" -data {
+#define parttick_width 14
+#define parttick_height 15
+static unsigned char parttick_bits[] = {
+   0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10,
+   0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10,
+   0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
+image create bitmap file_question -background white -foreground black -data {
+#define file_question_width 14
+#define file_question_height 15
+static unsigned char file_question_bits[] = {
+   0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13,
+   0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10,
+   0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
+image create bitmap file_removed -background white -foreground red -data {
+#define file_removed_width 14
+#define file_removed_height 15
+static unsigned char file_removed_bits[] = {
+   0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10,
+   0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13,
+   0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f};
+} -maskdata $filemask
+
+image create bitmap file_merge -background white -foreground blue -data {
+#define file_merge_width 14
+#define file_merge_height 15
+static unsigned char file_merge_bits[] = {
+   0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10,
+   0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10,
+   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
+
+set all_icons(_$ui_index)   file_plain
+set all_icons(A$ui_index)   file_fulltick
+set all_icons(M$ui_index)   file_fulltick
+set all_icons(D$ui_index)   file_removed
+set all_icons(U$ui_index)   file_merge
+
+set all_icons(_$ui_workdir) file_plain
+set all_icons(M$ui_workdir) file_mod
+set all_icons(D$ui_workdir) file_question
+set all_icons(U$ui_workdir) file_merge
+set all_icons(O$ui_workdir) file_plain
+
+set max_status_desc 0
+foreach i {
+               {__ "Unmodified"}
+
+               {_M "Modified, not staged"}
+               {M_ "Staged for commit"}
+               {MM "Portions staged for commit"}
+               {MD "Staged for commit, missing"}
+
+               {_O "Untracked, not staged"}
+               {A_ "Staged for commit"}
+               {AM "Portions staged for commit"}
+               {AD "Staged for commit, missing"}
+
+               {_D "Missing"}
+               {D_ "Staged for removal"}
+               {DO "Staged for removal, still present"}
+
+               {U_ "Requires merge resolution"}
+               {UU "Requires merge resolution"}
+               {UM "Requires merge resolution"}
+               {UD "Requires merge resolution"}
+       } {
+       if {$max_status_desc < [string length [lindex $i 1]]} {
+               set max_status_desc [string length [lindex $i 1]]
+       }
+       set all_descs([lindex $i 0]) [lindex $i 1]
+}
+unset i
+
+######################################################################
+##
+## util
+
+proc bind_button3 {w cmd} {
+       bind $w <Any-Button-3> $cmd
+       if {[is_MacOSX]} {
+               bind $w <Control-Button-1> $cmd
+       }
+}
+
+proc scrollbar2many {list mode args} {
+       foreach w $list {eval $w $mode $args}
+}
+
+proc many2scrollbar {list mode sb top bottom} {
+       $sb set $top $bottom
+       foreach w $list {$w $mode moveto $top}
+}
+
+proc incr_font_size {font {amt 1}} {
+       set sz [font configure $font -size]
+       incr sz $amt
+       font configure $font -size $sz
+       font configure ${font}bold -size $sz
+}
+
+proc hook_failed_popup {hook msg} {
+       set w .hookfail
+       toplevel $w
+
+       frame $w.m
+       label $w.m.l1 -text "$hook hook failed:" \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       text $w.m.t \
+               -background white -borderwidth 1 \
+               -relief sunken \
+               -width 80 -height 10 \
+               -font font_diff \
+               -yscrollcommand [list $w.m.sby set]
+       label $w.m.l2 \
+               -text {You must correct the above errors before committing.} \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       scrollbar $w.m.sby -command [list $w.m.t yview]
+       pack $w.m.l1 -side top -fill x
+       pack $w.m.l2 -side bottom -fill x
+       pack $w.m.sby -side right -fill y
+       pack $w.m.t -side left -fill both -expand 1
+       pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
+
+       $w.m.t insert 1.0 $msg
+       $w.m.t conf -state disabled
+
+       button $w.ok -text OK \
+               -width 15 \
+               -font font_ui \
+               -command "destroy $w"
+       pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+
+       bind $w <Visibility> "grab $w; focus $w"
+       bind $w <Key-Return> "destroy $w"
+       wm title $w "[appname] ([reponame]): error"
+       tkwait window $w
+}
+
+set next_console_id 0
+
+proc new_console {short_title long_title} {
+       global next_console_id console_data
+       set w .console[incr next_console_id]
+       set console_data($w) [list $short_title $long_title]
+       return [console_init $w]
+}
+
+proc console_init {w} {
+       global console_cr console_data M1B
+
+       set console_cr($w) 1.0
+       toplevel $w
+       frame $w.m
+       label $w.m.l1 -text "[lindex $console_data($w) 1]:" \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       text $w.m.t \
+               -background white -borderwidth 1 \
+               -relief sunken \
+               -width 80 -height 10 \
+               -font font_diff \
+               -state disabled \
+               -yscrollcommand [list $w.m.sby set]
+       label $w.m.s -text {Working... please wait...} \
+               -anchor w \
+               -justify left \
+               -font font_uibold
+       scrollbar $w.m.sby -command [list $w.m.t yview]
+       pack $w.m.l1 -side top -fill x
+       pack $w.m.s -side bottom -fill x
+       pack $w.m.sby -side right -fill y
+       pack $w.m.t -side left -fill both -expand 1
+       pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10
+
+       menu $w.ctxm -tearoff 0
+       $w.ctxm add command -label "Copy" \
+               -font font_ui \
+               -command "tk_textCopy $w.m.t"
+       $w.ctxm add command -label "Select All" \
+               -font font_ui \
+               -command "focus $w.m.t;$w.m.t tag add sel 0.0 end"
+       $w.ctxm add command -label "Copy All" \
+               -font font_ui \
+               -command "
+                       $w.m.t tag add sel 0.0 end
+                       tk_textCopy $w.m.t
+                       $w.m.t tag remove sel 0.0 end
+               "
+
+       button $w.ok -text {Close} \
+               -font font_ui \
+               -state disabled \
+               -command "destroy $w"
+       pack $w.ok -side bottom -anchor e -pady 10 -padx 10
+
+       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"
+       wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]"
+       return $w
+}
+
+proc console_exec {w 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 { }]"]
+       }
+
+       # -- 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 [list console_read $w $fd_f $after]
+}
+
+proc console_read {w fd after} {
+       global console_cr
+
+       set buf [read $fd]
+       if {$buf ne {}} {
+               if {![winfo exists $w]} {console_init $w}
+               $w.m.t conf -state normal
+               set c 0
+               set n [string length $buf]
+               while {$c < $n} {
+                       set cr [string first "\r" $buf $c]
+                       set lf [string first "\n" $buf $c]
+                       if {$cr < 0} {set cr [expr {$n + 1}]}
+                       if {$lf < 0} {set lf [expr {$n + 1}]}
+
+                       if {$lf < $cr} {
+                               $w.m.t insert end [string range $buf $c $lf]
+                               set console_cr($w) [$w.m.t index {end -1c}]
+                               set c $lf
+                               incr c
+                       } else {
+                               $w.m.t delete $console_cr($w) end
+                               $w.m.t insert end "\n"
+                               $w.m.t insert end [string range $buf $c $cr]
+                               set c $cr
+                               incr c
+                       }
+               }
+               $w.m.t conf -state disabled
+               $w.m.t see end
+       }
+
+       fconfigure $fd -blocking 1
+       if {[eof $fd]} {
+               if {[catch {close $fd}]} {
+                       set ok 0
+               } else {
+                       set ok 1
+               }
+               uplevel #0 $after $w $ok
+               return
+       }
+       fconfigure $fd -blocking 0
+}
+
+proc console_chain {cmdlist w {ok 1}} {
+       if {$ok} {
+               if {[llength $cmdlist] == 0} {
+                       console_done $w $ok
+                       return
+               }
+
+               set cmd [lindex $cmdlist 0]
+               set cmdlist [lrange $cmdlist 1 end]
+
+               if {[lindex $cmd 0] eq {console_exec}} {
+                       console_exec $w \
+                               [lindex $cmd 1] \
+                               [list console_chain $cmdlist]
+               } else {
+                       uplevel #0 $cmd $cmdlist $w $ok
+               }
+       } else {
+               console_done $w $ok
+       }
+}
+
+proc console_done {args} {
+       global console_cr console_data
+
+       switch -- [llength $args] {
+       2 {
+               set w [lindex $args 0]
+               set ok [lindex $args 1]
+       }
+       3 {
+               set w [lindex $args 1]
+               set ok [lindex $args 2]
+       }
+       default {
+               error "wrong number of args: console_done ?ignored? w ok"
+       }
+       }
+
+       if {$ok} {
+               if {[winfo exists $w]} {
+                       $w.m.s conf -background green -text {Success}
+                       $w.ok conf -state normal
+               }
+       } else {
+               if {![winfo exists $w]} {
+                       console_init $w
+               }
+               $w.m.s conf -background red -text {Error: Command Failed}
+               $w.ok conf -state normal
+       }
+
+       array unset console_cr $w
+       array unset console_data $w
+}
+
+######################################################################
+##
+## ui commands
+
+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 [info nameofexecutable]
+       lappend cmd [gitexec gitk]
+       if {$revs ne {}} {
+               append cmd { }
+               append cmd $revs
+       }
+
+       if {[catch {eval exec $cmd &} err]} {
+               error_popup "Failed to start gitk:\n\n$err"
+       } else {
+               set ui_status_value $starting_gitk_msg
+               after 10000 {
+                       if {$ui_status_value eq $starting_gitk_msg} {
+                               set ui_status_value {Ready.}
+                       }
+               }
+       }
+}
+
+proc do_stats {} {
+       set fd [open "| git count-objects -v" r]
+       while {[gets $fd line] > 0} {
+               if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
+                       set stats($name) $value
+               }
+       }
+       close $fd
+
+       set packed_sz 0
+       foreach p [glob -directory [gitdir objects pack] \
+               -type f \
+               -nocomplain -- *] {
+               incr packed_sz [file size $p]
+       }
+       if {$packed_sz > 0} {
+               set stats(size-pack) [expr {$packed_sz / 1024}]
+       }
+
+       set w .stats_view
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text {Database Statistics} \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons -border 1
+       button $w.buttons.close -text Close \
+               -font font_ui \
+               -command [list destroy $w]
+       button $w.buttons.gc -text {Compress Database} \
+               -font font_ui \
+               -command "destroy $w;do_gc"
+       pack $w.buttons.close -side right
+       pack $w.buttons.gc -side left
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       frame $w.stat -borderwidth 1 -relief solid
+       foreach s {
+               {count           {Number of loose objects}}
+               {size            {Disk space used by loose objects} { KiB}}
+               {in-pack         {Number of packed objects}}
+               {packs           {Number of packs}}
+               {size-pack       {Disk space used by packed objects} { KiB}}
+               {prune-packable  {Packed objects waiting for pruning}}
+               {garbage         {Garbage files}}
+               } {
+               set name [lindex $s 0]
+               set label [lindex $s 1]
+               if {[catch {set value $stats($name)}]} continue
+               if {[llength $s] > 2} {
+                       set value "$value[lindex $s 2]"
+               }
+
+               label $w.stat.l_$name -text "$label:" -anchor w -font font_ui
+               label $w.stat.v_$name -text $value -anchor w -font font_ui
+               grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5}
+       }
+       pack $w.stat -pady 10 -padx 10
+
+       bind $w <Visibility> "grab $w; focus $w"
+       bind $w <Key-Escape> [list destroy $w]
+       bind $w <Key-Return> [list destroy $w]
+       wm title $w "[appname] ([reponame]): Database Statistics"
+       tkwait window $w
+}
+
+proc do_gc {} {
+       set w [new_console {gc} {Compressing the object database}]
+       console_chain {
+               {console_exec {git pack-refs --prune}}
+               {console_exec {git reflog expire --all}}
+               {console_exec {git repack -a -d -l}}
+               {console_exec {git rerere gc}}
+       } $w
+}
+
+proc do_fsck_objects {} {
+       set w [new_console {fsck-objects} \
+               {Verifying the object database with fsck-objects}]
+       set cmd [list git fsck-objects]
+       lappend cmd --full
+       lappend cmd --cache
+       lappend cmd --strict
+       console_exec $w $cmd console_done
+}
+
+set is_quitting 0
+
+proc do_quit {} {
+       global ui_comm is_quitting repo_config commit_type
+
+       if {$is_quitting} return
+       set is_quitting 1
+
+       if {[winfo exists $ui_comm]} {
+               # -- 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
+                       }
+               } else {
+                       catch {file delete $save}
+               }
+
+               # -- Stash our current window geometry into this repository.
+               #
+               set cfg_geometry [list]
+               lappend cfg_geometry [wm geometry .]
+               lappend cfg_geometry [lindex [.vpane sash coord 0] 1]
+               lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0]
+               if {[catch {set rc_geometry $repo_config(gui.geometry)}]} {
+                       set rc_geometry {}
+               }
+               if {$cfg_geometry ne $rc_geometry} {
+                       catch {exec git config gui.geometry $cfg_geometry}
+               }
+       }
+
+       destroy .
+}
+
+proc do_rescan {} {
+       rescan {set ui_status_value {Ready.}}
+}
+
+proc unstage_helper {txt paths} {
+       global file_states current_diff_path
+
+       if {![lock_index begin-update]} return
+
+       set pathList [list]
+       set after {}
+       foreach path $paths {
+               switch -glob -- [lindex $file_states($path) 0] {
+               A? -
+               M? -
+               D? {
+                       lappend pathList $path
+                       if {$path eq $current_diff_path} {
+                               set after {reshow_diff;}
+                       }
+               }
+               }
+       }
+       if {$pathList eq {}} {
+               unlock_index
+       } else {
+               update_indexinfo \
+                       $txt \
+                       $pathList \
+                       [concat $after {set ui_status_value {Ready.}}]
+       }
+}
+
+proc do_unstage_selection {} {
+       global current_diff_path selected_paths
+
+       if {[array size selected_paths] > 0} {
+               unstage_helper \
+                       {Unstaging selected files from commit} \
+                       [array names selected_paths]
+       } elseif {$current_diff_path ne {}} {
+               unstage_helper \
+                       "Unstaging [short_path $current_diff_path] from commit" \
+                       [list $current_diff_path]
+       }
+}
+
+proc add_helper {txt paths} {
+       global file_states current_diff_path
+
+       if {![lock_index begin-update]} return
+
+       set pathList [list]
+       set after {}
+       foreach path $paths {
+               switch -glob -- [lindex $file_states($path) 0] {
+               _O -
+               ?M -
+               ?D -
+               U? {
+                       lappend pathList $path
+                       if {$path eq $current_diff_path} {
+                               set after {reshow_diff;}
+                       }
+               }
+               }
+       }
+       if {$pathList eq {}} {
+               unlock_index
+       } else {
+               update_index \
+                       $txt \
+                       $pathList \
+                       [concat $after {set ui_status_value {Ready to commit.}}]
+       }
+}
+
+proc do_add_selection {} {
+       global current_diff_path selected_paths
+
+       if {[array size selected_paths] > 0} {
+               add_helper \
+                       {Adding selected files} \
+                       [array names selected_paths]
+       } elseif {$current_diff_path ne {}} {
+               add_helper \
+                       "Adding [short_path $current_diff_path]" \
+                       [list $current_diff_path]
+       }
+}
+
+proc do_add_all {} {
+       global file_states
+
+       set paths [list]
+       foreach path [array names file_states] {
+               switch -glob -- [lindex $file_states($path) 0] {
+               U? {continue}
+               ?M -
+               ?D {lappend paths $path}
+               }
+       }
+       add_helper {Adding all changed files} $paths
+}
+
+proc revert_helper {txt paths} {
+       global file_states current_diff_path
+
+       if {![lock_index begin-update]} return
+
+       set pathList [list]
+       set after {}
+       foreach path $paths {
+               switch -glob -- [lindex $file_states($path) 0] {
+               U? {continue}
+               ?M -
+               ?D {
+                       lappend pathList $path
+                       if {$path eq $current_diff_path} {
+                               set after {reshow_diff;}
+                       }
+               }
+               }
+       }
+
+       set n [llength $pathList]
+       if {$n == 0} {
+               unlock_index
+               return
+       } elseif {$n == 1} {
+               set s "[short_path [lindex $pathList]]"
+       } else {
+               set s "these $n files"
+       }
+
+       set reply [tk_dialog \
+               .confirm_revert \
+               "[appname] ([reponame])" \
+               "Revert changes in $s?
+
+Any unadded changes will be permanently lost by the revert." \
+               question \
+               1 \
+               {Do Nothing} \
+               {Revert Changes} \
+               ]
+       if {$reply == 1} {
+               checkout_index \
+                       $txt \
+                       $pathList \
+                       [concat $after {set ui_status_value {Ready.}}]
+       } else {
+               unlock_index
+       }
+}
+
+proc do_revert_selection {} {
+       global current_diff_path selected_paths
+
+       if {[array size selected_paths] > 0} {
+               revert_helper \
+                       {Reverting selected files} \
+                       [array names selected_paths]
+       } elseif {$current_diff_path ne {}} {
+               revert_helper \
+                       "Reverting [short_path $current_diff_path]" \
+                       [list $current_diff_path]
+       }
+}
+
+proc do_signoff {} {
+       global ui_comm
+
+       set me [committer_ident]
+       if {$me eq {}} return
+
+       set sob "Signed-off-by: $me"
+       set last [$ui_comm get {end -1c linestart} {end -1c}]
+       if {$last ne $sob} {
+               $ui_comm edit separator
+               if {$last ne {}
+                       && ![regexp {^[A-Z][A-Za-z]*-[A-Za-z-]+: *} $last]} {
+                       $ui_comm insert end "\n"
+               }
+               $ui_comm insert end "\n$sob"
+               $ui_comm edit separator
+               $ui_comm see end
+       }
+}
+
+proc do_select_commit_type {} {
+       global commit_type selected_commit_type
+
+       if {$selected_commit_type eq {new}
+               && [string match amend* $commit_type]} {
+               create_new_commit
+       } elseif {$selected_commit_type eq {amend}
+               && ![string match amend* $commit_type]} {
+               load_last_commit
+
+               # The amend request was rejected...
+               #
+               if {![string match amend* $commit_type]} {
+                       set selected_commit_type new
+               }
+       }
+}
+
+proc do_commit {} {
+       commit_tree
+}
+
+proc do_about {} {
+       global appvers copyright
+       global tcl_patchLevel tk_patchLevel
+
+       set w .about_dialog
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text "About [appname]" \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.close -text {Close} \
+               -font font_ui \
+               -command [list destroy $w]
+       pack $w.buttons.close -side right
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+
+       label $w.desc \
+               -text "[appname] - a commit creation tool for Git.
+$copyright" \
+               -padx 5 -pady 5 \
+               -justify left \
+               -anchor w \
+               -borderwidth 1 \
+               -relief solid \
+               -font font_ui
+       pack $w.desc -side top -fill x -padx 5 -pady 5
+
+       set v {}
+       append v "[appname] version $appvers\n"
+       append v "[exec git version]\n"
+       append v "\n"
+       if {$tcl_patchLevel eq $tk_patchLevel} {
+               append v "Tcl/Tk version $tcl_patchLevel"
+       } else {
+               append v "Tcl version $tcl_patchLevel"
+               append v ", Tk version $tk_patchLevel"
+       }
+
+       label $w.vers \
+               -text $v \
+               -padx 5 -pady 5 \
+               -justify left \
+               -anchor w \
+               -borderwidth 1 \
+               -relief solid \
+               -font font_ui
+       pack $w.vers -side top -fill x -padx 5 -pady 5
+
+       menu $w.ctxm -tearoff 0
+       $w.ctxm add command \
+               -label {Copy} \
+               -font font_ui \
+               -command "
+               clipboard clear
+               clipboard append -format STRING -type STRING -- \[$w.vers cget -text\]
+       "
+
+       bind $w <Visibility> "grab $w; focus $w"
+       bind $w <Key-Escape> "destroy $w"
+       bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w"
+       wm title $w "About [appname]"
+       tkwait window $w
+}
+
+proc do_options {} {
+       global repo_config global_config font_descs
+       global repo_config_new global_config_new
+
+       array unset repo_config_new
+       array unset global_config_new
+       foreach name [array names repo_config] {
+               set repo_config_new($name) $repo_config($name)
+       }
+       load_config 1
+       foreach name [array names repo_config] {
+               switch -- $name {
+               gui.diffcontext {continue}
+               }
+               set repo_config_new($name) $repo_config($name)
+       }
+       foreach name [array names global_config] {
+               set global_config_new($name) $global_config($name)
+       }
+
+       set w .options_editor
+       toplevel $w
+       wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
+
+       label $w.header -text "[appname] Options" \
+               -font font_uibold
+       pack $w.header -side top -fill x
+
+       frame $w.buttons
+       button $w.buttons.restore -text {Restore Defaults} \
+               -font font_ui \
+               -command do_restore_defaults
+       pack $w.buttons.restore -side left
+       button $w.buttons.save -text Save \
+               -font font_ui \
+               -command [list do_save_config $w]
+       pack $w.buttons.save -side right
+       button $w.buttons.cancel -text {Cancel} \
+               -font font_ui \
+               -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.repo -text "[reponame] Repository" \
+               -font font_ui
+       labelframe $w.global -text {Global (All Repositories)} \
+               -font font_ui
+       pack $w.repo -side left -fill both -expand 1 -pady 5 -padx 5
+       pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5
+
+       set optid 0
+       foreach option {
+               {t user.name {User Name}}
+               {t user.email {Email Address}}
+
+               {b merge.summary {Summarize Merge Commits}}
+               {i-1..5 merge.verbosity {Merge Verbosity}}
+
+               {b gui.trustmtime  {Trust File Modification Timestamps}}
+               {i-1..99 gui.diffcontext {Number of Diff Context Lines}}
+               {t gui.newbranchtemplate {New Branch Name Template}}
+               } {
+               set type [lindex $option 0]
+               set name [lindex $option 1]
+               set text [lindex $option 2]
+               incr optid
+               foreach f {repo global} {
+                       switch -glob -- $type {
+                       b {
+                               checkbutton $w.$f.$optid -text $text \
+                                       -variable ${f}_config_new($name) \
+                                       -onvalue true \
+                                       -offvalue false \
+                                       -font font_ui
+                               pack $w.$f.$optid -side top -anchor w
+                       }
+                       i-* {
+                               regexp -- {-(\d+)\.\.(\d+)$} $type _junk min max
+                               frame $w.$f.$optid
+                               label $w.$f.$optid.l -text "$text:" -font font_ui
+                               pack $w.$f.$optid.l -side left -anchor w -fill x
+                               spinbox $w.$f.$optid.v \
+                                       -textvariable ${f}_config_new($name) \
+                                       -from $min \
+                                       -to $max \
+                                       -increment 1 \
+                                       -width [expr {1 + [string length $max]}] \
+                                       -font font_ui
+                               bind $w.$f.$optid.v <FocusIn> {%W selection range 0 end}
+                               pack $w.$f.$optid.v -side right -anchor e -padx 5
+                               pack $w.$f.$optid -side top -anchor w -fill x
+                       }
+                       t {
+                               frame $w.$f.$optid
+                               label $w.$f.$optid.l -text "$text:" -font font_ui
+                               entry $w.$f.$optid.v \
+                                       -borderwidth 1 \
+                                       -relief sunken \
+                                       -width 20 \
+                                       -textvariable ${f}_config_new($name) \
+                                       -font font_ui
+                               pack $w.$f.$optid.l -side left -anchor w
+                               pack $w.$f.$optid.v -side left -anchor w \
+                                       -fill x -expand 1 \
+                                       -padx 5
+                               pack $w.$f.$optid -side top -anchor w -fill x
+                       }
+                       }
+               }
+       }
+
+       set all_fonts [lsort [font families]]
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set font [lindex $option 1]
+               set text [lindex $option 2]
+
+               set global_config_new(gui.$font^^family) \
+                       [font configure $font -family]
+               set global_config_new(gui.$font^^size) \
+                       [font configure $font -size]
+
+               frame $w.global.$name
+               label $w.global.$name.l -text "$text:" -font font_ui
+               pack $w.global.$name.l -side left -anchor w -fill x
+               eval tk_optionMenu $w.global.$name.family \
+                       global_config_new(gui.$font^^family) \
+                       $all_fonts
+               spinbox $w.global.$name.size \
+                       -textvariable global_config_new(gui.$font^^size) \
+                       -from 2 -to 80 -increment 1 \
+                       -width 3 \
+                       -font font_ui
+               bind $w.global.$name.size <FocusIn> {%W selection range 0 end}
+               pack $w.global.$name.size -side right -anchor e
+               pack $w.global.$name.family -side right -anchor e
+               pack $w.global.$name -side top -anchor w -fill x
+       }
+
+       bind $w <Visibility> "grab $w; focus $w"
+       bind $w <Key-Escape> "destroy $w"
+       wm title $w "[appname] ([reponame]): Options"
+       tkwait window $w
+}
+
+proc do_restore_defaults {} {
+       global font_descs default_config repo_config
+       global repo_config_new global_config_new
+
+       foreach name [array names default_config] {
+               set repo_config_new($name) $default_config($name)
+               set global_config_new($name) $default_config($name)
+       }
+
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set repo_config(gui.$name) $default_config(gui.$name)
+       }
+       apply_config
+
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set font [lindex $option 1]
+               set global_config_new(gui.$font^^family) \
+                       [font configure $font -family]
+               set global_config_new(gui.$font^^size) \
+                       [font configure $font -size]
+       }
+}
+
+proc do_save_config {w} {
+       if {[catch {save_config} err]} {
+               error_popup "Failed to completely save options:\n\n$err"
+       }
+       reshow_diff
+       destroy $w
+}
+
+proc do_windows_shortcut {} {
+       global argv0
+
+       set fn [tk_getSaveFile \
+               -parent . \
+               -title "[appname] ([reponame]): Create Desktop Icon" \
+               -initialfile "Git [reponame].bat"]
+       if {$fn != {}} {
+               if {[catch {
+                               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 GIT_DIR=[file normalize [gitdir]]"
+                               puts -nonewline $fd "@\"[info nameofexecutable]\""
+                               puts $fd " \"[file normalize $argv0]\""
+                               close $fd
+                       } err]} {
+                       error_popup "Cannot write script:\n\n$err"
+               }
+       }
+}
+
+proc do_cygwin_shortcut {} {
+       global argv0
+
+       if {[catch {
+               set desktop [exec cygpath \
+                       --windows \
+                       --absolute \
+                       --long-name \
+                       --desktop]
+               }]} {
+                       set desktop .
+       }
+       set fn [tk_getSaveFile \
+               -parent . \
+               -title "[appname] ([reponame]): Create Desktop Icon" \
+               -initialdir $desktop \
+               -initialfile "Git [reponame].bat"]
+       if {$fn != {}} {
+               if {[catch {
+                               set fd [open $fn w]
+                               set sh [exec cygpath \
+                                       --windows \
+                                       --absolute \
+                                       /bin/sh]
+                               set me [exec cygpath \
+                                       --unix \
+                                       --absolute \
+                                       $argv0]
+                               set gd [exec cygpath \
+                                       --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 Starting git-gui... please wait..."
+                               puts -nonewline $fd "@\"$sh\" --login -c \""
+                               puts -nonewline $fd "GIT_DIR='$gd'"
+                               puts -nonewline $fd " '$me'"
+                               puts $fd "&\""
+                               close $fd
+                       } err]} {
+                       error_popup "Cannot write script:\n\n$err"
+               }
+       }
+}
+
+proc do_macosx_app {} {
+       global argv0 env
+
+       set fn [tk_getSaveFile \
+               -parent . \
+               -title "[appname] ([reponame]): Create Desktop Icon" \
+               -initialdir [file join $env(HOME) Desktop] \
+               -initialfile "Git [reponame].app"]
+       if {$fn != {}} {
+               if {[catch {
+                               set Contents [file join $fn Contents]
+                               set MacOS [file join $Contents MacOS]
+                               set exe [file join $MacOS git-gui]
+
+                               file mkdir $MacOS
+
+                               set fd [open [file join $Contents Info.plist] w]
+                               puts $fd {<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+       <key>CFBundleDevelopmentRegion</key>
+       <string>English</string>
+       <key>CFBundleExecutable</key>
+       <string>git-gui</string>
+       <key>CFBundleIdentifier</key>
+       <string>org.spearce.git-gui</string>
+       <key>CFBundleInfoDictionaryVersion</key>
+       <string>6.0</string>
+       <key>CFBundlePackageType</key>
+       <string>APPL</string>
+       <key>CFBundleSignature</key>
+       <string>????</string>
+       <key>CFBundleVersion</key>
+       <string>1.0</string>
+       <key>NSPrincipalClass</key>
+       <string>NSApplication</string>
+</dict>
+</plist>}
+                               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'"
+                                       }
+                               }
+                               puts $fd "export PATH='$ep':\$PATH"
+                               puts $fd "export GIT_DIR='$gd'"
+                               puts $fd "exec [file normalize $argv0]"
+                               close $fd
+
+                               file attributes $exe -permissions u+x,g+x,o+x
+                       } err]} {
+                       error_popup "Cannot write icon:\n\n$err"
+               }
+       }
+}
+
+proc toggle_or_diff {w x y} {
+       global file_states file_lists current_diff_path ui_index ui_workdir
+       global last_clicked selected_paths
+
+       set pos [split [$w index @$x,$y] .]
+       set lno [lindex $pos 0]
+       set col [lindex $pos 1]
+       set path [lindex $file_lists($w) [expr {$lno - 1}]]
+       if {$path eq {}} {
+               set last_clicked {}
+               return
+       }
+
+       set last_clicked [list $w $lno]
+       array unset selected_paths
+       $ui_index tag remove in_sel 0.0 end
+       $ui_workdir tag remove in_sel 0.0 end
+
+       if {$col == 0} {
+               if {$current_diff_path eq $path} {
+                       set after {reshow_diff;}
+               } else {
+                       set after {}
+               }
+               if {$w eq $ui_index} {
+                       update_indexinfo \
+                               "Unstaging [short_path $path] from commit" \
+                               [list $path] \
+                               [concat $after {set ui_status_value {Ready.}}]
+               } elseif {$w eq $ui_workdir} {
+                       update_index \
+                               "Adding [short_path $path]" \
+                               [list $path] \
+                               [concat $after {set ui_status_value {Ready.}}]
+               }
+       } else {
+               show_diff $path $w $lno
+       }
+}
+
+proc add_one_to_selection {w x y} {
+       global file_lists last_clicked selected_paths
+
+       set lno [lindex [split [$w index @$x,$y] .] 0]
+       set path [lindex $file_lists($w) [expr {$lno - 1}]]
+       if {$path eq {}} {
+               set last_clicked {}
+               return
+       }
+
+       if {$last_clicked ne {}
+               && [lindex $last_clicked 0] ne $w} {
+               array unset selected_paths
+               [lindex $last_clicked 0] tag remove in_sel 0.0 end
+       }
+
+       set last_clicked [list $w $lno]
+       if {[catch {set in_sel $selected_paths($path)}]} {
+               set in_sel 0
+       }
+       if {$in_sel} {
+               unset selected_paths($path)
+               $w tag remove in_sel $lno.0 [expr {$lno + 1}].0
+       } else {
+               set selected_paths($path) 1
+               $w tag add in_sel $lno.0 [expr {$lno + 1}].0
+       }
+}
+
+proc add_range_to_selection {w x y} {
+       global file_lists last_clicked selected_paths
+
+       if {[lindex $last_clicked 0] ne $w} {
+               toggle_or_diff $w $x $y
+               return
+       }
+
+       set lno [lindex [split [$w index @$x,$y] .] 0]
+       set lc [lindex $last_clicked 1]
+       if {$lc < $lno} {
+               set begin $lc
+               set end $lno
+       } else {
+               set begin $lno
+               set end $lc
+       }
+
+       foreach path [lrange $file_lists($w) \
+               [expr {$begin - 1}] \
+               [expr {$end - 1}]] {
+               set selected_paths($path) 1
+       }
+       $w tag add in_sel $begin.0 [expr {$end + 1}].0
+}
+
+######################################################################
+##
+## config defaults
+
+set cursor_ptr arrow
+font create font_diff -family Courier -size 10
+font create font_ui
+catch {
+       label .dummy
+       eval font configure font_ui [font actual [.dummy cget -font]]
+       destroy .dummy
+}
+
+font create font_uibold
+font create font_diffbold
+
+if {[is_Windows]} {
+       set M1B Control
+       set M1T Ctrl
+} elseif {[is_MacOSX]} {
+       set M1B M1
+       set M1T Cmd
+} else {
+       set M1B M1
+       set M1T M1
+}
+
+proc apply_config {} {
+       global repo_config font_descs
+
+       foreach option $font_descs {
+               set name [lindex $option 0]
+               set font [lindex $option 1]
+               if {[catch {
+                       foreach {cn cv} $repo_config(gui.$name) {
+                               font configure $font $cn $cv
+                       }
+                       } err]} {
+                       error_popup "Invalid font specified in gui.$name:\n\n$err"
+               }
+               foreach {cn cv} [font configure $font] {
+                       font configure ${font}bold $cn $cv
+               }
+               font configure ${font}bold -weight bold
+       }
+}
+
+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.trustmtime) false
+set default_config(gui.diffcontext) 5
+set default_config(gui.newbranchtemplate) {}
+set default_config(gui.fontui) [font configure font_ui]
+set default_config(gui.fontdiff) [font configure font_diff]
+set font_descs {
+       {fontui   font_ui   {Main Font}}
+       {fontdiff font_diff {Diff/Console Font}}
+}
+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 {
+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
+
+set ui_comm {}
+
+# -- Menu Bar
+#
+menu .mbar -tearoff 0
+.mbar add cascade -label Repository -menu .mbar.repository
+.mbar add cascade -label Edit -menu .mbar.edit
+if {[is_enabled branch]} {
+       .mbar add cascade -label Branch -menu .mbar.branch
+}
+if {[is_enabled multicommit] || [is_enabled singlecommit]} {
+       .mbar add cascade -label Commit -menu .mbar.commit
+}
+if {[is_enabled transport]} {
+       .mbar add cascade -label Merge -menu .mbar.merge
+       .mbar add cascade -label Fetch -menu .mbar.fetch
+       .mbar add cascade -label Push -menu .mbar.push
+}
+. configure -menu .mbar
+
+# -- Repository Menu
+#
+menu .mbar.repository
+
+.mbar.repository add command \
+       -label {Browse Current Branch} \
+       -command {new_browser $current_branch} \
+       -font font_ui
+trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#"
+.mbar.repository add separator
+
+.mbar.repository add command \
+       -label {Visualize Current Branch} \
+       -command {do_gitk $current_branch} \
+       -font font_ui
+trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#"
+.mbar.repository add command \
+       -label {Visualize All Branches} \
+       -command {do_gitk --all} \
+       -font font_ui
+.mbar.repository add separator
+
+if {[is_enabled multicommit]} {
+       .mbar.repository add command -label {Database Statistics} \
+               -command do_stats \
+               -font font_ui
+
+       .mbar.repository add command -label {Compress Database} \
+               -command do_gc \
+               -font font_ui
+
+       .mbar.repository add command -label {Verify Database} \
+               -command do_fsck_objects \
+               -font font_ui
+
+       .mbar.repository add separator
+
+       if {[is_Cygwin]} {
+               .mbar.repository add command \
+                       -label {Create Desktop Icon} \
+                       -command do_cygwin_shortcut \
+                       -font font_ui
+       } elseif {[is_Windows]} {
+               .mbar.repository add command \
+                       -label {Create Desktop Icon} \
+                       -command do_windows_shortcut \
+                       -font font_ui
+       } elseif {[is_MacOSX]} {
+               .mbar.repository add command \
+                       -label {Create Desktop Icon} \
+                       -command do_macosx_app \
+                       -font font_ui
+       }
+}
+
+.mbar.repository add command -label Quit \
+       -command do_quit \
+       -accelerator $M1T-Q \
+       -font font_ui
+
+# -- Edit Menu
+#
+menu .mbar.edit
+.mbar.edit add command -label Undo \
+       -command {catch {[focus] edit undo}} \
+       -accelerator $M1T-Z \
+       -font font_ui
+.mbar.edit add command -label Redo \
+       -command {catch {[focus] edit redo}} \
+       -accelerator $M1T-Y \
+       -font font_ui
+.mbar.edit add separator
+.mbar.edit add command -label Cut \
+       -command {catch {tk_textCut [focus]}} \
+       -accelerator $M1T-X \
+       -font font_ui
+.mbar.edit add command -label Copy \
+       -command {catch {tk_textCopy [focus]}} \
+       -accelerator $M1T-C \
+       -font font_ui
+.mbar.edit add command -label Paste \
+       -command {catch {tk_textPaste [focus]; [focus] see insert}} \
+       -accelerator $M1T-V \
+       -font font_ui
+.mbar.edit add command -label Delete \
+       -command {catch {[focus] delete sel.first sel.last}} \
+       -accelerator Del \
+       -font font_ui
+.mbar.edit add separator
+.mbar.edit add command -label {Select All} \
+       -command {catch {[focus] tag add sel 0.0 end}} \
+       -accelerator $M1T-A \
+       -font font_ui
+
+# -- Branch Menu
+#
+if {[is_enabled branch]} {
+       menu .mbar.branch
+
+       .mbar.branch add command -label {Create...} \
+               -command do_create_branch \
+               -accelerator $M1T-N \
+               -font font_ui
+       lappend disable_on_lock [list .mbar.branch entryconf \
+               [.mbar.branch index last] -state]
+
+       .mbar.branch add command -label {Delete...} \
+               -command do_delete_branch \
+               -font font_ui
+       lappend disable_on_lock [list .mbar.branch entryconf \
+               [.mbar.branch index last] -state]
+}
+
+# -- Commit Menu
+#
+if {[is_enabled multicommit] || [is_enabled singlecommit]} {
+       menu .mbar.commit
+
+       .mbar.commit add radiobutton \
+               -label {New Commit} \
+               -command do_select_commit_type \
+               -variable selected_commit_type \
+               -value new \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+
+       .mbar.commit add radiobutton \
+               -label {Amend Last Commit} \
+               -command do_select_commit_type \
+               -variable selected_commit_type \
+               -value amend \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+
+       .mbar.commit add separator
+
+       .mbar.commit add command -label Rescan \
+               -command do_rescan \
+               -accelerator F5 \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+
+       .mbar.commit add command -label {Add To Commit} \
+               -command do_add_selection \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+
+       .mbar.commit add command -label {Add Existing To Commit} \
+               -command do_add_all \
+               -accelerator $M1T-I \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+
+       .mbar.commit add command -label {Unstage From Commit} \
+               -command do_unstage_selection \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+
+       .mbar.commit add command -label {Revert Changes} \
+               -command do_revert_selection \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+
+       .mbar.commit add separator
+
+       .mbar.commit add command -label {Sign Off} \
+               -command do_signoff \
+               -accelerator $M1T-S \
+               -font font_ui
+
+       .mbar.commit add command -label Commit \
+               -command do_commit \
+               -accelerator $M1T-Return \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.commit entryconf [.mbar.commit index last] -state]
+}
+
+if {[is_MacOSX]} {
+       # -- Apple Menu (Mac OS X only)
+       #
+       .mbar add cascade -label Apple -menu .mbar.apple
+       menu .mbar.apple
+
+       .mbar.apple add command -label "About [appname]" \
+               -command do_about \
+               -font font_ui
+       .mbar.apple add command -label "[appname] Options..." \
+               -command do_options \
+               -font font_ui
+} else {
+       # -- Edit Menu
+       #
+       .mbar.edit add separator
+       .mbar.edit add command -label {Options...} \
+               -command do_options \
+               -font font_ui
+
+       # -- Tools Menu
+       #
+       if {[file exists /usr/local/miga/lib/gui-miga]
+               && [file exists .pvcsrc]} {
+       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 \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.tools entryconf [.mbar.tools index last] -state]
+       }
+}
+
+# -- Help Menu
+#
+.mbar add cascade -label Help -menu .mbar.help
+menu .mbar.help
+
+if {![is_MacOSX]} {
+       .mbar.help add command -label "About [appname]" \
+               -command do_about \
+               -font font_ui
+}
+
+set browser {}
+catch {set browser $repo_config(instaweb.browser)}
+set doc_path [file dirname [gitexec]]
+set doc_path [file join $doc_path Documentation index.html]
+
+if {[is_Cygwin]} {
+       set doc_path [exec cygpath --windows $doc_path]
+}
+
+if {$browser eq {}} {
+       if {[is_MacOSX]} {
+               set browser open
+       } elseif {[is_Cygwin]} {
+               set program_files [file dirname [exec cygpath --windir]]
+               set program_files [file join $program_files {Program Files}]
+               set firefox [file join $program_files {Mozilla Firefox} firefox.exe]
+               set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE]
+               if {[file exists $firefox]} {
+                       set browser $firefox
+               } elseif {[file exists $ie]} {
+                       set browser $ie
+               }
+               unset program_files firefox ie
+       }
+}
+
+if {[file isfile $doc_path]} {
+       set doc_url "file:$doc_path"
+} else {
+       set doc_url {http://www.kernel.org/pub/software/scm/git/docs/}
+}
+
+if {$browser ne {}} {
+       .mbar.help add command -label {Online Documentation} \
+               -command [list exec $browser $doc_url &] \
+               -font font_ui
+}
+unset browser doc_path doc_url
+
+# -- Standard bindings
+#
+bind .   <Destroy> do_quit
+bind all <$M1B-Key-q> do_quit
+bind all <$M1B-Key-Q> do_quit
+bind all <$M1B-Key-w> {destroy [winfo toplevel %W]}
+bind all <$M1B-Key-W> {destroy [winfo toplevel %W]}
+
+# -- Not a normal commit type invocation?  Do that instead!
+#
+switch -- $subcommand {
+blame {
+       if {[llength $argv] != 2} {
+               puts stderr "usage: $argv0 blame commit path"
+               exit 1
+       }
+       set current_branch [lindex $argv 0]
+       show_blame $current_branch [lindex $argv 1]
+       return
+}
+citool -
+gui {
+       if {[llength $argv] != 0} {
+               puts -nonewline stderr "usage: $argv0"
+               if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} {
+                       puts -nonewline stderr " $subcommand"
+               }
+               puts stderr {}
+               exit 1
+       }
+       # fall through to setup UI for commits
+}
+default {
+       puts stderr "usage: $argv0 \[{blame|citool}\]"
+       exit 1
+}
+}
+
+# -- Branch Control
+#
+frame .branch \
+       -borderwidth 1 \
+       -relief sunken
+label .branch.l1 \
+       -text {Current Branch:} \
+       -anchor w \
+       -justify left \
+       -font font_ui
+label .branch.cb \
+       -textvariable current_branch \
+       -anchor w \
+       -justify left \
+       -font font_ui
+pack .branch.l1 -side left
+pack .branch.cb -side left -fill x
+pack .branch -side top -fill x
+
+if {[is_enabled branch]} {
+       menu .mbar.merge
+       .mbar.merge add command -label {Local Merge...} \
+               -command do_local_merge \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.merge entryconf [.mbar.merge index last] -state]
+       .mbar.merge add command -label {Abort Merge...} \
+               -command do_reset_hard \
+               -font font_ui
+       lappend disable_on_lock \
+               [list .mbar.merge entryconf [.mbar.merge index last] -state]
+
+
+       menu .mbar.fetch
+
+       menu .mbar.push
+       .mbar.push add command -label {Push...} \
+               -command do_push_anywhere \
+               -font font_ui
+}
+
+# -- Main Window Layout
+#
+panedwindow .vpane -orient vertical
+panedwindow .vpane.files -orient horizontal
+.vpane add .vpane.files -sticky nsew -height 100 -width 200
+pack .vpane -anchor n -side top -fill both -expand 1
+
+# -- Index File List
+#
+frame .vpane.files.index -height 100 -width 200
+label .vpane.files.index.title -text {Changes To Be Committed} \
+       -background green \
+       -font font_ui
+text $ui_index -background white -borderwidth 0 \
+       -width 20 -height 10 \
+       -wrap none \
+       -font font_ui \
+       -cursor $cursor_ptr \
+       -xscrollcommand {.vpane.files.index.sx set} \
+       -yscrollcommand {.vpane.files.index.sy set} \
+       -state disabled
+scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview]
+scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview]
+pack .vpane.files.index.title -side top -fill x
+pack .vpane.files.index.sx -side bottom -fill x
+pack .vpane.files.index.sy -side right -fill y
+pack $ui_index -side left -fill both -expand 1
+.vpane.files add .vpane.files.index -sticky nsew
+
+# -- Working Directory File List
+#
+frame .vpane.files.workdir -height 100 -width 200
+label .vpane.files.workdir.title -text {Changed But Not Updated} \
+       -background red \
+       -font font_ui
+text $ui_workdir -background white -borderwidth 0 \
+       -width 20 -height 10 \
+       -wrap none \
+       -font font_ui \
+       -cursor $cursor_ptr \
+       -xscrollcommand {.vpane.files.workdir.sx set} \
+       -yscrollcommand {.vpane.files.workdir.sy set} \
+       -state disabled
+scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview]
+scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview]
+pack .vpane.files.workdir.title -side top -fill x
+pack .vpane.files.workdir.sx -side bottom -fill x
+pack .vpane.files.workdir.sy -side right -fill y
+pack $ui_workdir -side left -fill both -expand 1
+.vpane.files add .vpane.files.workdir -sticky nsew
+
+foreach i [list $ui_index $ui_workdir] {
+       $i tag conf in_diff -font font_uibold
+       $i tag conf in_sel \
+               -background [$i cget -foreground] \
+               -foreground [$i cget -background]
+}
+unset i
+
+# -- Diff and Commit Area
+#
+frame .vpane.lower -height 300 -width 400
+frame .vpane.lower.commarea
+frame .vpane.lower.diff -relief sunken -borderwidth 1
+pack .vpane.lower.commarea -side top -fill x
+pack .vpane.lower.diff -side bottom -fill both -expand 1
+.vpane add .vpane.lower -sticky nsew
+
+# -- Commit Area Buttons
+#
+frame .vpane.lower.commarea.buttons
+label .vpane.lower.commarea.buttons.l -text {} \
+       -anchor w \
+       -justify left \
+       -font font_ui
+pack .vpane.lower.commarea.buttons.l -side top -fill x
+pack .vpane.lower.commarea.buttons -side left -fill y
+
+button .vpane.lower.commarea.buttons.rescan -text {Rescan} \
+       -command do_rescan \
+       -font font_ui
+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} \
+       -command do_add_all \
+       -font font_ui
+pack .vpane.lower.commarea.buttons.incall -side top -fill x
+lappend disable_on_lock \
+       {.vpane.lower.commarea.buttons.incall conf -state}
+
+button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \
+       -command do_signoff \
+       -font font_ui
+pack .vpane.lower.commarea.buttons.signoff -side top -fill x
+
+button .vpane.lower.commarea.buttons.commit -text {Commit} \
+       -command do_commit \
+       -font font_ui
+pack .vpane.lower.commarea.buttons.commit -side top -fill x
+lappend disable_on_lock \
+       {.vpane.lower.commarea.buttons.commit conf -state}
+
+# -- Commit Message Buffer
+#
+frame .vpane.lower.commarea.buffer
+frame .vpane.lower.commarea.buffer.header
+set ui_comm .vpane.lower.commarea.buffer.t
+set ui_coml .vpane.lower.commarea.buffer.header.l
+radiobutton .vpane.lower.commarea.buffer.header.new \
+       -text {New Commit} \
+       -command do_select_commit_type \
+       -variable selected_commit_type \
+       -value new \
+       -font font_ui
+lappend disable_on_lock \
+       [list .vpane.lower.commarea.buffer.header.new conf -state]
+radiobutton .vpane.lower.commarea.buffer.header.amend \
+       -text {Amend Last Commit} \
+       -command do_select_commit_type \
+       -variable selected_commit_type \
+       -value amend \
+       -font font_ui
+lappend disable_on_lock \
+       [list .vpane.lower.commarea.buffer.header.amend conf -state]
+label $ui_coml \
+       -anchor w \
+       -justify left \
+       -font font_ui
+proc trace_commit_type {varname args} {
+       global ui_coml commit_type
+       switch -glob -- $commit_type {
+       initial       {set txt {Initial Commit Message:}}
+       amend         {set txt {Amended Commit Message:}}
+       amend-initial {set txt {Amended Initial Commit Message:}}
+       amend-merge   {set txt {Amended Merge Commit Message:}}
+       merge         {set txt {Merge Commit Message:}}
+       *             {set txt {Commit Message:}}
+       }
+       $ui_coml conf -text $txt
+}
+trace add variable commit_type write trace_commit_type
+pack $ui_coml -side left -fill x
+pack .vpane.lower.commarea.buffer.header.amend -side right
+pack .vpane.lower.commarea.buffer.header.new -side right
+
+text $ui_comm -background white -borderwidth 1 \
+       -undo true \
+       -maxundo 20 \
+       -autoseparators true \
+       -relief sunken \
+       -width 75 -height 9 -wrap none \
+       -font font_diff \
+       -yscrollcommand {.vpane.lower.commarea.buffer.sby set}
+scrollbar .vpane.lower.commarea.buffer.sby \
+       -command [list $ui_comm yview]
+pack .vpane.lower.commarea.buffer.header -side top -fill x
+pack .vpane.lower.commarea.buffer.sby -side right -fill y
+pack $ui_comm -side left -fill y
+pack .vpane.lower.commarea.buffer -side left -fill y
+
+# -- Commit Message Buffer Context Menu
+#
+set ctxm .vpane.lower.commarea.buffer.ctxm
+menu $ctxm -tearoff 0
+$ctxm add command \
+       -label {Cut} \
+       -font font_ui \
+       -command {tk_textCut $ui_comm}
+$ctxm add command \
+       -label {Copy} \
+       -font font_ui \
+       -command {tk_textCopy $ui_comm}
+$ctxm add command \
+       -label {Paste} \
+       -font font_ui \
+       -command {tk_textPaste $ui_comm}
+$ctxm add command \
+       -label {Delete} \
+       -font font_ui \
+       -command {$ui_comm delete sel.first sel.last}
+$ctxm add separator
+$ctxm add command \
+       -label {Select All} \
+       -font font_ui \
+       -command {focus $ui_comm;$ui_comm tag add sel 0.0 end}
+$ctxm add command \
+       -label {Copy All} \
+       -font font_ui \
+       -command {
+               $ui_comm tag add sel 0.0 end
+               tk_textCopy $ui_comm
+               $ui_comm tag remove sel 0.0 end
+       }
+$ctxm add separator
+$ctxm add command \
+       -label {Sign Off} \
+       -font font_ui \
+       -command do_signoff
+bind_button3 $ui_comm "tk_popup $ctxm %X %Y"
+
+# -- Diff Header
+#
+set current_diff_path {}
+set current_diff_side {}
+set diff_actions [list]
+proc trace_current_diff_path {varname args} {
+       global current_diff_path diff_actions file_states
+       if {$current_diff_path eq {}} {
+               set s {}
+               set f {}
+               set p {}
+               set o disabled
+       } else {
+               set p $current_diff_path
+               set s [mapdesc [lindex $file_states($p) 0] $p]
+               set f {File:}
+               set p [escape_path $p]
+               set o normal
+       }
+
+       .vpane.lower.diff.header.status configure -text $s
+       .vpane.lower.diff.header.file configure -text $f
+       .vpane.lower.diff.header.path configure -text $p
+       foreach w $diff_actions {
+               uplevel #0 $w $o
+       }
+}
+trace add variable current_diff_path write trace_current_diff_path
+
+frame .vpane.lower.diff.header -background orange
+label .vpane.lower.diff.header.status \
+       -background orange \
+       -width $max_status_desc \
+       -anchor w \
+       -justify left \
+       -font font_ui
+label .vpane.lower.diff.header.file \
+       -background orange \
+       -anchor w \
+       -justify left \
+       -font font_ui
+label .vpane.lower.diff.header.path \
+       -background orange \
+       -anchor w \
+       -justify left \
+       -font font_ui
+pack .vpane.lower.diff.header.status -side left
+pack .vpane.lower.diff.header.file -side left
+pack .vpane.lower.diff.header.path -fill x
+set ctxm .vpane.lower.diff.header.ctxm
+menu $ctxm -tearoff 0
+$ctxm add command \
+       -label {Copy} \
+       -font font_ui \
+       -command {
+               clipboard clear
+               clipboard append \
+                       -format STRING \
+                       -type STRING \
+                       -- $current_diff_path
+       }
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y"
+
+# -- Diff Body
+#
+frame .vpane.lower.diff.body
+set ui_diff .vpane.lower.diff.body.t
+text $ui_diff -background white -borderwidth 0 \
+       -width 80 -height 15 -wrap none \
+       -font font_diff \
+       -xscrollcommand {.vpane.lower.diff.body.sbx set} \
+       -yscrollcommand {.vpane.lower.diff.body.sby set} \
+       -state disabled
+scrollbar .vpane.lower.diff.body.sbx -orient horizontal \
+       -command [list $ui_diff xview]
+scrollbar .vpane.lower.diff.body.sby -orient vertical \
+       -command [list $ui_diff yview]
+pack .vpane.lower.diff.body.sbx -side bottom -fill x
+pack .vpane.lower.diff.body.sby -side right -fill y
+pack $ui_diff -side left -fill both -expand 1
+pack .vpane.lower.diff.header -side top -fill x
+pack .vpane.lower.diff.body -side bottom -fill both -expand 1
+
+$ui_diff tag conf d_cr -elide true
+$ui_diff tag conf d_@ -foreground blue -font font_diffbold
+$ui_diff tag conf d_+ -foreground {#00a000}
+$ui_diff tag conf d_- -foreground red
+
+$ui_diff tag conf d_++ -foreground {#00a000}
+$ui_diff tag conf d_-- -foreground red
+$ui_diff tag conf d_+s \
+       -foreground {#00a000} \
+       -background {#e2effa}
+$ui_diff tag conf d_-s \
+       -foreground red \
+       -background {#e2effa}
+$ui_diff tag conf d_s+ \
+       -foreground {#00a000} \
+       -background ivory1
+$ui_diff tag conf d_s- \
+       -foreground red \
+       -background ivory1
+
+$ui_diff tag conf d<<<<<<< \
+       -foreground orange \
+       -font font_diffbold
+$ui_diff tag conf d======= \
+       -foreground orange \
+       -font font_diffbold
+$ui_diff tag conf d>>>>>>> \
+       -foreground orange \
+       -font font_diffbold
+
+$ui_diff tag raise sel
+
+# -- Diff Body Context Menu
+#
+set ctxm .vpane.lower.diff.body.ctxm
+menu $ctxm -tearoff 0
+$ctxm add command \
+       -label {Refresh} \
+       -font font_ui \
+       -command reshow_diff
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add command \
+       -label {Copy} \
+       -font font_ui \
+       -command {tk_textCopy $ui_diff}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add command \
+       -label {Select All} \
+       -font font_ui \
+       -command {focus $ui_diff;$ui_diff tag add sel 0.0 end}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add command \
+       -label {Copy All} \
+       -font font_ui \
+       -command {
+               $ui_diff tag add sel 0.0 end
+               tk_textCopy $ui_diff
+               $ui_diff tag remove sel 0.0 end
+       }
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add separator
+$ctxm add command \
+       -label {Apply/Reverse Hunk} \
+       -font font_ui \
+       -command {apply_hunk $cursorX $cursorY}
+set ui_diff_applyhunk [$ctxm index last]
+lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state]
+$ctxm add separator
+$ctxm add command \
+       -label {Decrease Font Size} \
+       -font font_ui \
+       -command {incr_font_size font_diff -1}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add command \
+       -label {Increase Font Size} \
+       -font font_ui \
+       -command {incr_font_size font_diff 1}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add separator
+$ctxm add command \
+       -label {Show Less Context} \
+       -font font_ui \
+       -command {if {$repo_config(gui.diffcontext) >= 2} {
+               incr repo_config(gui.diffcontext) -1
+               reshow_diff
+       }}
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add command \
+       -label {Show More Context} \
+       -font font_ui \
+       -command {
+               incr repo_config(gui.diffcontext)
+               reshow_diff
+       }
+lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state]
+$ctxm add separator
+$ctxm add command -label {Options...} \
+       -font font_ui \
+       -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}
+       } else {
+               $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit}
+       }
+       tk_popup $ctxm %X %Y
+"
+unset ui_diff_applyhunk
+
+# -- Status Bar
+#
+set ui_status_value {Initializing...}
+label .status -textvariable ui_status_value \
+       -anchor w \
+       -justify left \
+       -borderwidth 1 \
+       -relief sunken \
+       -font font_ui
+pack .status -anchor w -side bottom -fill x
+
+# -- Load geometry
+#
+catch {
+set gm $repo_config(gui.geometry)
+wm geometry . [lindex $gm 0]
+.vpane sash place 0 \
+       [lindex [.vpane sash coord 0] 0] \
+       [lindex $gm 1]
+.vpane.files sash place 0 \
+       [lindex $gm 2] \
+       [lindex [.vpane.files sash coord 0] 1]
+unset gm
+}
+
+# -- Key Bindings
+#
+bind $ui_comm <$M1B-Key-Return> {do_commit;break}
+bind $ui_comm <$M1B-Key-i> {do_add_all;break}
+bind $ui_comm <$M1B-Key-I> {do_add_all;break}
+bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
+bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break}
+bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break}
+bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break}
+bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break}
+bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break}
+bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break}
+bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break}
+
+bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break}
+bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break}
+bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break}
+bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break}
+bind $ui_diff <$M1B-Key-v> {break}
+bind $ui_diff <$M1B-Key-V> {break}
+bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break}
+bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break}
+bind $ui_diff <Key-Up>     {catch {%W yview scroll -1 units};break}
+bind $ui_diff <Key-Down>   {catch {%W yview scroll  1 units};break}
+bind $ui_diff <Key-Left>   {catch {%W xview scroll -1 units};break}
+bind $ui_diff <Key-Right>  {catch {%W xview scroll  1 units};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 all <Key-F5> do_rescan
+bind all <$M1B-Key-r> do_rescan
+bind all <$M1B-Key-R> do_rescan
+bind .   <$M1B-Key-s> do_signoff
+bind .   <$M1B-Key-S> do_signoff
+bind .   <$M1B-Key-i> do_add_all
+bind .   <$M1B-Key-I> do_add_all
+bind .   <$M1B-Key-Return> do_commit
+foreach i [list $ui_index $ui_workdir] {
+       bind $i <Button-1>       "toggle_or_diff         $i %x %y; break"
+       bind $i <$M1B-Button-1>  "add_one_to_selection   $i %x %y; break"
+       bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break"
+}
+unset i
+
+set file_lists($ui_index) [list]
+set file_lists($ui_workdir) [list]
+
+set HEAD {}
+set PARENT {}
+set MERGE_HEAD [list]
+set commit_type {}
+set empty_tree {}
+set current_branch {}
+set current_diff_path {}
+set selected_commit_type new
+
+wm title . "[appname] ([file normalize [file dirname [gitdir]]])"
+focus -force $ui_comm
+
+# -- Warn the user about environmental problems.  Cygwin's Tcl
+#    does *not* pass its env array onto any processes it spawns.
+#    This means that git processes get none of our environment.
+#
+if {[is_Cygwin]} {
+       set ignored_env 0
+       set suggest_user {}
+       set msg "Possible environment issues exist.
+
+The following environment variables are probably
+going to be ignored by any Git subprocess run
+by [appname]:
+
+"
+       foreach name [array names env] {
+               switch -regexp -- $name {
+               {^GIT_INDEX_FILE$} -
+               {^GIT_OBJECT_DIRECTORY$} -
+               {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} -
+               {^GIT_DIFF_OPTS$} -
+               {^GIT_EXTERNAL_DIFF$} -
+               {^GIT_PAGER$} -
+               {^GIT_TRACE$} -
+               {^GIT_CONFIG$} -
+               {^GIT_CONFIG_LOCAL$} -
+               {^GIT_(AUTHOR|COMMITTER)_DATE$} {
+                       append msg " - $name\n"
+                       incr ignored_env
+               }
+               {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} {
+                       append msg " - $name\n"
+                       incr ignored_env
+                       set suggest_user $name
+               }
+               }
+       }
+       if {$ignored_env > 0} {
+               append msg "
+This is due to a known issue with the
+Tcl binary distributed by Cygwin."
+
+               if {$suggest_user ne {}} {
+                       append msg "
+
+A good replacement for $suggest_user
+is placing values for the user.name and
+user.email settings into your personal
+~/.gitconfig file.
+"
+               }
+               warn_popup $msg
+       }
+       unset ignored_env msg suggest_user name
+}
+
+# -- Only initialize complex UI if we are going to stay running.
+#
+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,} [exec git count-objects] _junk objects_current
+       if {$objects_current >= $object_limit} {
+               if {[ask_popup \
+                       "This repository currently has $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
+               }
+       }
+       unset object_limit _junk objects_current
+}
+
+lock_index begin-read
+after 1 do_rescan
index 16cd351f7f0d992be37be83017961c70731d4cc5..cbc7418e3501b37a701a5177157733d97b405376 100755 (executable)
@@ -15,11 +15,11 @@ case "$GIT_DIR" in
        fqgitdir="$PWD/$GIT_DIR" ;;
 esac
 
-local="`git repo-config --bool --get instaweb.local`"
-httpd="`git repo-config --get instaweb.httpd`"
-browser="`git repo-config --get instaweb.browser`"
-port=`git repo-config --get instaweb.port`
-module_path="`git repo-config --get instaweb.modulepath`"
+local="`git config --bool --get instaweb.local`"
+httpd="`git config --get instaweb.httpd`"
+browser="`git config --get instaweb.browser`"
+port=`git config --get instaweb.port`
+module_path="`git config --get instaweb.modulepath`"
 
 conf=$GIT_DIR/gitweb/httpd.conf
 
@@ -53,6 +53,9 @@ start_httpd () {
                                return
                        fi
                done
+               echo "$httpd_only not found. Install $httpd_only or use" \
+                    "--httpd to specify another http daemon."
+               exit 1
        fi
        if test $? != 0; then
                echo "Could not execute http daemon $httpd."
@@ -160,10 +163,20 @@ apache2_conf () {
        test "$local" = true && bind='127.0.0.1:'
        echo 'text/css css' > $fqgitdir/mime.types
        cat > "$conf" <<EOF
+ServerName "git-instaweb"
 ServerRoot "$fqgitdir/gitweb"
 DocumentRoot "$fqgitdir/gitweb"
 PidFile "$fqgitdir/pid"
 Listen $bind$port
+EOF
+
+       for mod in mime dir; do
+               if test -e $module_path/mod_${mod}.so; then
+                       echo "LoadModule ${mod}_module " \
+                            "$module_path/mod_${mod}.so" >> "$conf"
+               fi
+       done
+       cat >> "$conf" <<EOF
 TypesConfig $fqgitdir/mime.types
 DirectoryIndex gitweb.cgi
 EOF
index b928f2ca52c1991ee50838fc47580ed83c58fb64..9360804711d6b81983e53df67c86ddc8082e2ab4 100755 (executable)
@@ -12,7 +12,7 @@ fi
 laf="$GIT_DIR/lost-found"
 rm -fr "$laf" && mkdir -p "$laf/commit" "$laf/other" || exit
 
-git fsck-objects --full |
+git fsck --full |
 while read dangling type sha1
 do
        case "$dangling" in
index 0f88953f299c1fd1243c3d5d6ae1e71d56e842a8..8ea5c5e816c067db88f04bc5a0613ca1dbb13456 100755 (executable)
@@ -23,7 +23,11 @@ do
   -u|--u|--up|--upl|--uploa|--upload|--upload-|--upload-p|--upload-pa|\
   --upload-pac|--upload-pack)
        shift
-       exec="--exec=$1"
+       exec="--upload-pack=$1"
+       shift;;
+  -u=*|--u=*|--up=*|--upl=*|--uplo=*|--uploa=*|--upload=*|\
+  --upload-=*|--upload-p=*|--upload-pa=*|--upload-pac=*|--upload-pack=*)
+       exec=--upload-pack=$(expr "z$1" : 'z-[^=]*=\(.*\)')
        shift;;
   --)
   shift; break ;;
@@ -54,7 +58,7 @@ http://* | https://* | ftp://* )
             curl_extra_args="-k"
         fi
        if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
-               "`git-repo-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" ||
@@ -94,7 +98,7 @@ while read sha1 path
 do
        case "$sha1" in
        failed)
-               die "Failed to find remote refs"
+               exit 1 ;;
        esac
        case "$path" in
        refs/heads/*)
index c49e4c65af606496c7e65b5147e36fee105d9ae7..7d62d7902cb1d7cd2749091b86d532306bea89b3 100755 (executable)
@@ -104,7 +104,7 @@ 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`
-       merge "$src1" "$orig" "$src2"
+       git-merge-file "$src1" "$orig" "$src2"
        ret=$?
 
        # Create the working tree file, using "our tree" version from the
diff --git a/git-merge-recursive-old.py b/git-merge-recursive-old.py
deleted file mode 100755 (executable)
index 4039435..0000000
+++ /dev/null
@@ -1,944 +0,0 @@
-#!/usr/bin/python
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-import sys
-sys.path.append('''@@GIT_PYTHON_PATH@@''')
-
-import math, random, os, re, signal, tempfile, stat, errno, traceback
-from heapq import heappush, heappop
-from sets import Set
-
-from gitMergeCommon import *
-
-outputIndent = 0
-def output(*args):
-    sys.stdout.write('  '*outputIndent)
-    printList(args)
-
-originalIndexFile = os.environ.get('GIT_INDEX_FILE',
-                                   os.environ.get('GIT_DIR', '.git') + '/index')
-temporaryIndexFile = os.environ.get('GIT_DIR', '.git') + \
-                     '/merge-recursive-tmp-index'
-def setupIndex(temporary):
-    try:
-        os.unlink(temporaryIndexFile)
-    except OSError:
-        pass
-    if temporary:
-        newIndex = temporaryIndexFile
-    else:
-        newIndex = originalIndexFile
-    os.environ['GIT_INDEX_FILE'] = newIndex
-
-# This is a global variable which is used in a number of places but
-# only written to in the 'merge' function.
-
-# cacheOnly == True  => Don't leave any non-stage 0 entries in the cache and
-#                       don't update the working directory.
-#              False => Leave unmerged entries in the cache and update
-#                       the working directory.
-
-cacheOnly = False
-
-# The entry point to the merge code
-# ---------------------------------
-
-def merge(h1, h2, branch1Name, branch2Name, graph, callDepth=0, ancestor=None):
-    '''Merge the commits h1 and h2, return the resulting virtual
-    commit object and a flag indicating the cleanness of the merge.'''
-    assert(isinstance(h1, Commit) and isinstance(h2, Commit))
-
-    global outputIndent
-
-    output('Merging:')
-    output(h1)
-    output(h2)
-    sys.stdout.flush()
-
-    if ancestor:
-        ca = [ancestor]
-    else:
-        assert(isinstance(graph, Graph))
-        ca = getCommonAncestors(graph, h1, h2)
-    output('found', len(ca), 'common ancestor(s):')
-    for x in ca:
-        output(x)
-    sys.stdout.flush()
-
-    mergedCA = ca[0]
-    for h in ca[1:]:
-        outputIndent = callDepth+1
-        [mergedCA, dummy] = merge(mergedCA, h,
-                                  'Temporary merge branch 1',
-                                  'Temporary merge branch 2',
-                                  graph, callDepth+1)
-        outputIndent = callDepth
-        assert(isinstance(mergedCA, Commit))
-
-    global cacheOnly
-    if callDepth == 0:
-        setupIndex(False)
-        cacheOnly = False
-    else:
-        setupIndex(True)
-        runProgram(['git-read-tree', h1.tree()])
-        cacheOnly = True
-
-    [shaRes, clean] = mergeTrees(h1.tree(), h2.tree(), mergedCA.tree(),
-                                 branch1Name, branch2Name)
-
-    if graph and (clean or cacheOnly):
-        res = Commit(None, [h1, h2], tree=shaRes)
-        graph.addNode(res)
-    else:
-        res = None
-
-    return [res, clean]
-
-getFilesRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)$', re.S)
-def getFilesAndDirs(tree):
-    files = Set()
-    dirs = Set()
-    out = runProgram(['git-ls-tree', '-r', '-z', '-t', tree])
-    for l in out.split('\0'):
-        m = getFilesRE.match(l)
-        if m:
-            if m.group(2) == 'tree':
-                dirs.add(m.group(4))
-            elif m.group(2) == 'blob':
-                files.add(m.group(4))
-
-    return [files, dirs]
-
-# Those two global variables are used in a number of places but only
-# written to in 'mergeTrees' and 'uniquePath'. They keep track of
-# every file and directory in the two branches that are about to be
-# merged.
-currentFileSet = None
-currentDirectorySet = None
-
-def mergeTrees(head, merge, common, branch1Name, branch2Name):
-    '''Merge the trees 'head' and 'merge' with the common ancestor
-    'common'. The name of the head branch is 'branch1Name' and the name of
-    the merge branch is 'branch2Name'. Return a tuple (tree, cleanMerge)
-    where tree is the resulting tree and cleanMerge is True iff the
-    merge was clean.'''
-    
-    assert(isSha(head) and isSha(merge) and isSha(common))
-
-    if common == merge:
-        output('Already uptodate!')
-        return [head, True]
-
-    if cacheOnly:
-        updateArg = '-i'
-    else:
-        updateArg = '-u'
-
-    [out, code] = runProgram(['git-read-tree', updateArg, '-m',
-                                common, head, merge], returnCode = True)
-    if code != 0:
-        die('git-read-tree:', out)
-
-    [tree, code] = runProgram('git-write-tree', returnCode=True)
-    tree = tree.rstrip()
-    if code != 0:
-        global currentFileSet, currentDirectorySet
-        [currentFileSet, currentDirectorySet] = getFilesAndDirs(head)
-        [filesM, dirsM] = getFilesAndDirs(merge)
-        currentFileSet.union_update(filesM)
-        currentDirectorySet.union_update(dirsM)
-
-        entries = unmergedCacheEntries()
-        renamesHead =  getRenames(head, common, head, merge, entries)
-        renamesMerge = getRenames(merge, common, head, merge, entries)
-
-        cleanMerge = processRenames(renamesHead, renamesMerge,
-                                    branch1Name, branch2Name)
-        for entry in entries:
-            if entry.processed:
-                continue
-            if not processEntry(entry, branch1Name, branch2Name):
-                cleanMerge = False
-                
-        if cleanMerge or cacheOnly:
-            tree = runProgram('git-write-tree').rstrip()
-        else:
-            tree = None
-    else:
-        cleanMerge = True
-
-    return [tree, cleanMerge]
-
-# Low level file merging, update and removal
-# ------------------------------------------
-
-def mergeFile(oPath, oSha, oMode, aPath, aSha, aMode, bPath, bSha, bMode,
-              branch1Name, branch2Name):
-
-    merge = False
-    clean = True
-
-    if stat.S_IFMT(aMode) != stat.S_IFMT(bMode):
-        clean = False
-        if stat.S_ISREG(aMode):
-            mode = aMode
-            sha = aSha
-        else:
-            mode = bMode
-            sha = bSha
-    else:
-        if aSha != oSha and bSha != oSha:
-            merge = True
-
-        if aMode == oMode:
-            mode = bMode
-        else:
-            mode = aMode
-
-        if aSha == oSha:
-            sha = bSha
-        elif bSha == oSha:
-            sha = aSha
-        elif stat.S_ISREG(aMode):
-            assert(stat.S_ISREG(bMode))
-
-            orig = runProgram(['git-unpack-file', oSha]).rstrip()
-            src1 = runProgram(['git-unpack-file', aSha]).rstrip()
-            src2 = runProgram(['git-unpack-file', bSha]).rstrip()
-            try:
-                [out, code] = runProgram(['merge',
-                                          '-L', branch1Name + '/' + aPath,
-                                          '-L', 'orig/' + oPath,
-                                          '-L', branch2Name + '/' + bPath,
-                                          src1, orig, src2], returnCode=True)
-            except ProgramError, e:
-                print >>sys.stderr, e
-                die("Failed to execute 'merge'. merge(1) is used as the "
-                    "file-level merge tool. Is 'merge' in your path?")
-
-            sha = runProgram(['git-hash-object', '-t', 'blob', '-w',
-                              src1]).rstrip()
-
-            os.unlink(orig)
-            os.unlink(src1)
-            os.unlink(src2)
-
-            clean = (code == 0)
-        else:
-            assert(stat.S_ISLNK(aMode) and stat.S_ISLNK(bMode))
-            sha = aSha
-
-            if aSha != bSha:
-                clean = False
-
-    return [sha, mode, clean, merge]
-
-def updateFile(clean, sha, mode, path):
-    updateCache = cacheOnly or clean
-    updateWd = not cacheOnly
-
-    return updateFileExt(sha, mode, path, updateCache, updateWd)
-
-def updateFileExt(sha, mode, path, updateCache, updateWd):
-    if cacheOnly:
-        updateWd = False
-
-    if updateWd:
-        pathComponents = path.split('/')
-        for x in xrange(1, len(pathComponents)):
-            p = '/'.join(pathComponents[0:x])
-
-            try:
-                createDir = not stat.S_ISDIR(os.lstat(p).st_mode)
-            except OSError:
-                createDir = True
-            
-            if createDir:
-                try:
-                    os.mkdir(p)
-                except OSError, e:
-                    die("Couldn't create directory", p, e.strerror)
-
-        prog = ['git-cat-file', 'blob', sha]
-        if stat.S_ISREG(mode):
-            try:
-                os.unlink(path)
-            except OSError:
-                pass
-            if mode & 0100:
-                mode = 0777
-            else:
-                mode = 0666
-            fd = os.open(path, os.O_WRONLY | os.O_TRUNC | os.O_CREAT, mode)
-            proc = subprocess.Popen(prog, stdout=fd)
-            proc.wait()
-            os.close(fd)
-        elif stat.S_ISLNK(mode):
-            linkTarget = runProgram(prog)
-            os.symlink(linkTarget, path)
-        else:
-            assert(False)
-
-    if updateWd and updateCache:
-        runProgram(['git-update-index', '--add', '--', path])
-    elif updateCache:
-        runProgram(['git-update-index', '--add', '--cacheinfo',
-                    '0%o' % mode, sha, path])
-
-def setIndexStages(path,
-                   oSHA1, oMode,
-                   aSHA1, aMode,
-                   bSHA1, bMode,
-                   clear=True):
-    istring = []
-    if clear:
-        istring.append("0 " + ("0" * 40) + "\t" + path + "\0")
-    if oMode:
-        istring.append("%o %s %d\t%s\0" % (oMode, oSHA1, 1, path))
-    if aMode:
-        istring.append("%o %s %d\t%s\0" % (aMode, aSHA1, 2, path))
-    if bMode:
-        istring.append("%o %s %d\t%s\0" % (bMode, bSHA1, 3, path))
-
-    runProgram(['git-update-index', '-z', '--index-info'],
-               input="".join(istring))
-
-def removeFile(clean, path):
-    updateCache = cacheOnly or clean
-    updateWd = not cacheOnly
-
-    if updateCache:
-        runProgram(['git-update-index', '--force-remove', '--', path])
-
-    if updateWd:
-        try:
-            os.unlink(path)
-        except OSError, e:
-            if e.errno != errno.ENOENT and e.errno != errno.EISDIR:
-                raise
-        try:
-            os.removedirs(os.path.dirname(path))
-        except OSError:
-            pass
-
-def uniquePath(path, branch):
-    def fileExists(path):
-        try:
-            os.lstat(path)
-            return True
-        except OSError, e:
-            if e.errno == errno.ENOENT:
-                return False
-            else:
-                raise
-
-    branch = branch.replace('/', '_')
-    newPath = path + '~' + branch
-    suffix = 0
-    while newPath in currentFileSet or \
-          newPath in currentDirectorySet  or \
-          fileExists(newPath):
-        suffix += 1
-        newPath = path + '~' + branch + '_' + str(suffix)
-    currentFileSet.add(newPath)
-    return newPath
-
-# Cache entry management
-# ----------------------
-
-class CacheEntry:
-    def __init__(self, path):
-        class Stage:
-            def __init__(self):
-                self.sha1 = None
-                self.mode = None
-
-            # Used for debugging only
-            def __str__(self):
-                if self.mode != None:
-                    m = '0%o' % self.mode
-                else:
-                    m = 'None'
-
-                if self.sha1:
-                    sha1 = self.sha1
-                else:
-                    sha1 = 'None'
-                return 'sha1: ' + sha1 + ' mode: ' + m
-        
-        self.stages = [Stage(), Stage(), Stage(), Stage()]
-        self.path = path
-        self.processed = False
-
-    def __str__(self):
-        return 'path: ' + self.path + ' stages: ' + repr([str(x) for x in self.stages])
-
-class CacheEntryContainer:
-    def __init__(self):
-        self.entries = {}
-
-    def add(self, entry):
-        self.entries[entry.path] = entry
-
-    def get(self, path):
-        return self.entries.get(path)
-
-    def __iter__(self):
-        return self.entries.itervalues()
-    
-unmergedRE = re.compile(r'^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
-def unmergedCacheEntries():
-    '''Create a dictionary mapping file names to CacheEntry
-    objects. The dictionary contains one entry for every path with a
-    non-zero stage entry.'''
-
-    lines = runProgram(['git-ls-files', '-z', '--unmerged']).split('\0')
-    lines.pop()
-
-    res = CacheEntryContainer()
-    for l in lines:
-        m = unmergedRE.match(l)
-        if m:
-            mode = int(m.group(1), 8)
-            sha1 = m.group(2)
-            stage = int(m.group(3))
-            path = m.group(4)
-
-            e = res.get(path)
-            if not e:
-                e = CacheEntry(path)
-                res.add(e)
-
-            e.stages[stage].mode = mode
-            e.stages[stage].sha1 = sha1
-        else:
-            die('Error: Merge program failed: Unexpected output from',
-                'git-ls-files:', l)
-    return res
-
-lsTreeRE = re.compile(r'^([0-7]+) (\S+) ([0-9a-f]{40})\t(.*)\n$', re.S)
-def getCacheEntry(path, origTree, aTree, bTree):
-    '''Returns a CacheEntry object which doesn't have to correspond to
-    a real cache entry in Git's index.'''
-    
-    def parse(out):
-        if out == '':
-            return [None, None]
-        else:
-            m = lsTreeRE.match(out)
-            if not m:
-                die('Unexpected output from git-ls-tree:', out)
-            elif m.group(2) == 'blob':
-                return [m.group(3), int(m.group(1), 8)]
-            else:
-                return [None, None]
-
-    res = CacheEntry(path)
-
-    [oSha, oMode] = parse(runProgram(['git-ls-tree', origTree, '--', path]))
-    [aSha, aMode] = parse(runProgram(['git-ls-tree', aTree, '--', path]))
-    [bSha, bMode] = parse(runProgram(['git-ls-tree', bTree, '--', path]))
-
-    res.stages[1].sha1 = oSha
-    res.stages[1].mode = oMode
-    res.stages[2].sha1 = aSha
-    res.stages[2].mode = aMode
-    res.stages[3].sha1 = bSha
-    res.stages[3].mode = bMode
-
-    return res
-
-# Rename detection and handling
-# -----------------------------
-
-class RenameEntry:
-    def __init__(self,
-                 src, srcSha, srcMode, srcCacheEntry,
-                 dst, dstSha, dstMode, dstCacheEntry,
-                 score):
-        self.srcName = src
-        self.srcSha = srcSha
-        self.srcMode = srcMode
-        self.srcCacheEntry = srcCacheEntry
-        self.dstName = dst
-        self.dstSha = dstSha
-        self.dstMode = dstMode
-        self.dstCacheEntry = dstCacheEntry
-        self.score = score
-
-        self.processed = False
-
-class RenameEntryContainer:
-    def __init__(self):
-        self.entriesSrc = {}
-        self.entriesDst = {}
-
-    def add(self, entry):
-        self.entriesSrc[entry.srcName] = entry
-        self.entriesDst[entry.dstName] = entry
-
-    def getSrc(self, path):
-        return self.entriesSrc.get(path)
-
-    def getDst(self, path):
-        return self.entriesDst.get(path)
-
-    def __iter__(self):
-        return self.entriesSrc.itervalues()
-
-parseDiffRenamesRE = re.compile('^:([0-7]+) ([0-7]+) ([0-9a-f]{40}) ([0-9a-f]{40}) R([0-9]*)$')
-def getRenames(tree, oTree, aTree, bTree, cacheEntries):
-    '''Get information of all renames which occured between 'oTree' and
-    'tree'. We need the three trees in the merge ('oTree', 'aTree' and
-    'bTree') to be able to associate the correct cache entries with
-    the rename information. 'tree' is always equal to either aTree or bTree.'''
-
-    assert(tree == aTree or tree == bTree)
-    inp = runProgram(['git-diff-tree', '-M', '--diff-filter=R', '-r',
-                      '-z', oTree, tree])
-
-    ret = RenameEntryContainer()
-    try:
-        recs = inp.split("\0")
-        recs.pop() # remove last entry (which is '')
-        it = recs.__iter__()
-        while True:
-            rec = it.next()
-            m = parseDiffRenamesRE.match(rec)
-
-            if not m:
-                die('Unexpected output from git-diff-tree:', rec)
-
-            srcMode = int(m.group(1), 8)
-            dstMode = int(m.group(2), 8)
-            srcSha = m.group(3)
-            dstSha = m.group(4)
-            score = m.group(5)
-            src = it.next()
-            dst = it.next()
-
-            srcCacheEntry = cacheEntries.get(src)
-            if not srcCacheEntry:
-                srcCacheEntry = getCacheEntry(src, oTree, aTree, bTree)
-                cacheEntries.add(srcCacheEntry)
-
-            dstCacheEntry = cacheEntries.get(dst)
-            if not dstCacheEntry:
-                dstCacheEntry = getCacheEntry(dst, oTree, aTree, bTree)
-                cacheEntries.add(dstCacheEntry)
-
-            ret.add(RenameEntry(src, srcSha, srcMode, srcCacheEntry,
-                                dst, dstSha, dstMode, dstCacheEntry,
-                                score))
-    except StopIteration:
-        pass
-    return ret
-
-def fmtRename(src, dst):
-    srcPath = src.split('/')
-    dstPath = dst.split('/')
-    path = []
-    endIndex = min(len(srcPath), len(dstPath)) - 1
-    for x in range(0, endIndex):
-        if srcPath[x] == dstPath[x]:
-            path.append(srcPath[x])
-        else:
-            endIndex = x
-            break
-
-    if len(path) > 0:
-        return '/'.join(path) + \
-               '/{' + '/'.join(srcPath[endIndex:]) + ' => ' + \
-               '/'.join(dstPath[endIndex:]) + '}'
-    else:
-        return src + ' => ' + dst
-
-def processRenames(renamesA, renamesB, branchNameA, branchNameB):
-    srcNames = Set()
-    for x in renamesA:
-        srcNames.add(x.srcName)
-    for x in renamesB:
-        srcNames.add(x.srcName)
-
-    cleanMerge = True
-    for path in srcNames:
-        if renamesA.getSrc(path):
-            renames1 = renamesA
-            renames2 = renamesB
-            branchName1 = branchNameA
-            branchName2 = branchNameB
-        else:
-            renames1 = renamesB
-            renames2 = renamesA
-            branchName1 = branchNameB
-            branchName2 = branchNameA
-        
-        ren1 = renames1.getSrc(path)
-        ren2 = renames2.getSrc(path)
-
-        ren1.dstCacheEntry.processed = True
-        ren1.srcCacheEntry.processed = True
-
-        if ren1.processed:
-            continue
-
-        ren1.processed = True
-
-        if ren2:
-            # Renamed in 1 and renamed in 2
-            assert(ren1.srcName == ren2.srcName)
-            ren2.dstCacheEntry.processed = True
-            ren2.processed = True
-
-            if ren1.dstName != ren2.dstName:
-                output('CONFLICT (rename/rename): Rename',
-                       fmtRename(path, ren1.dstName), 'in branch', branchName1,
-                       'rename', fmtRename(path, ren2.dstName), 'in',
-                       branchName2)
-                cleanMerge = False
-
-                if ren1.dstName in currentDirectorySet:
-                    dstName1 = uniquePath(ren1.dstName, branchName1)
-                    output(ren1.dstName, 'is a directory in', branchName2,
-                           'adding as', dstName1, 'instead.')
-                    removeFile(False, ren1.dstName)
-                else:
-                    dstName1 = ren1.dstName
-
-                if ren2.dstName in currentDirectorySet:
-                    dstName2 = uniquePath(ren2.dstName, branchName2)
-                    output(ren2.dstName, 'is a directory in', branchName1,
-                           'adding as', dstName2, 'instead.')
-                    removeFile(False, ren2.dstName)
-                else:
-                    dstName2 = ren2.dstName
-                setIndexStages(dstName1,
-                               None, None,
-                               ren1.dstSha, ren1.dstMode,
-                              None, None)
-                setIndexStages(dstName2,
-                               None, None,
-                               None, None,
-                               ren2.dstSha, ren2.dstMode)
-
-            else:
-                removeFile(True, ren1.srcName)
-
-                [resSha, resMode, clean, merge] = \
-                         mergeFile(ren1.srcName, ren1.srcSha, ren1.srcMode,
-                                   ren1.dstName, ren1.dstSha, ren1.dstMode,
-                                   ren2.dstName, ren2.dstSha, ren2.dstMode,
-                                   branchName1, branchName2)
-
-                if merge or not clean:
-                    output('Renaming', fmtRename(path, ren1.dstName))
-
-                if merge:
-                    output('Auto-merging', ren1.dstName)
-
-                if not clean:
-                    output('CONFLICT (content): merge conflict in',
-                           ren1.dstName)
-                    cleanMerge = False
-
-                    if not cacheOnly:
-                        setIndexStages(ren1.dstName,
-                                       ren1.srcSha, ren1.srcMode,
-                                       ren1.dstSha, ren1.dstMode,
-                                       ren2.dstSha, ren2.dstMode)
-
-                updateFile(clean, resSha, resMode, ren1.dstName)
-        else:
-            removeFile(True, ren1.srcName)
-
-            # Renamed in 1, maybe changed in 2
-            if renamesA == renames1:
-                stage = 3
-            else:
-                stage = 2
-                
-            srcShaOtherBranch  = ren1.srcCacheEntry.stages[stage].sha1
-            srcModeOtherBranch = ren1.srcCacheEntry.stages[stage].mode
-
-            dstShaOtherBranch  = ren1.dstCacheEntry.stages[stage].sha1
-            dstModeOtherBranch = ren1.dstCacheEntry.stages[stage].mode
-
-            tryMerge = False
-            
-            if ren1.dstName in currentDirectorySet:
-                newPath = uniquePath(ren1.dstName, branchName1)
-                output('CONFLICT (rename/directory): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in', branchName1,
-                       'directory', ren1.dstName, 'added in', branchName2)
-                output('Renaming', ren1.srcName, 'to', newPath, 'instead')
-                cleanMerge = False
-                removeFile(False, ren1.dstName)
-                updateFile(False, ren1.dstSha, ren1.dstMode, newPath)
-            elif srcShaOtherBranch == None:
-                output('CONFLICT (rename/delete): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1, 'and deleted in', branchName2)
-                cleanMerge = False
-                updateFile(False, ren1.dstSha, ren1.dstMode, ren1.dstName)
-            elif dstShaOtherBranch:
-                newPath = uniquePath(ren1.dstName, branchName2)
-                output('CONFLICT (rename/add): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1 + '.', ren1.dstName, 'added in', branchName2)
-                output('Adding as', newPath, 'instead')
-                updateFile(False, dstShaOtherBranch, dstModeOtherBranch, newPath)
-                cleanMerge = False
-                tryMerge = True
-            elif renames2.getDst(ren1.dstName):
-                dst2 = renames2.getDst(ren1.dstName)
-                newPath1 = uniquePath(ren1.dstName, branchName1)
-                newPath2 = uniquePath(dst2.dstName, branchName2)
-                output('CONFLICT (rename/rename): Rename',
-                       fmtRename(ren1.srcName, ren1.dstName), 'in',
-                       branchName1+'. Rename',
-                       fmtRename(dst2.srcName, dst2.dstName), 'in', branchName2)
-                output('Renaming', ren1.srcName, 'to', newPath1, 'and',
-                       dst2.srcName, 'to', newPath2, 'instead')
-                removeFile(False, ren1.dstName)
-                updateFile(False, ren1.dstSha, ren1.dstMode, newPath1)
-                updateFile(False, dst2.dstSha, dst2.dstMode, newPath2)
-                dst2.processed = True
-                cleanMerge = False
-            else:
-                tryMerge = True
-
-            if tryMerge:
-
-                oName, oSHA1, oMode = ren1.srcName, ren1.srcSha, ren1.srcMode
-                aName, bName = ren1.dstName, ren1.srcName
-                aSHA1, bSHA1 = ren1.dstSha, srcShaOtherBranch
-                aMode, bMode = ren1.dstMode, srcModeOtherBranch
-                aBranch, bBranch = branchName1, branchName2
-
-                if renamesA != renames1:
-                    aName, bName = bName, aName
-                    aSHA1, bSHA1 = bSHA1, aSHA1
-                    aMode, bMode = bMode, aMode
-                    aBranch, bBranch = bBranch, aBranch
-
-                [resSha, resMode, clean, merge] = \
-                         mergeFile(oName, oSHA1, oMode,
-                                   aName, aSHA1, aMode,
-                                   bName, bSHA1, bMode,
-                                   aBranch, bBranch);
-
-                if merge or not clean:
-                    output('Renaming', fmtRename(ren1.srcName, ren1.dstName))
-
-                if merge:
-                    output('Auto-merging', ren1.dstName)
-
-                if not clean:
-                    output('CONFLICT (rename/modify): Merge conflict in',
-                           ren1.dstName)
-                    cleanMerge = False
-
-                    if not cacheOnly:
-                        setIndexStages(ren1.dstName,
-                                       oSHA1, oMode,
-                                       aSHA1, aMode,
-                                       bSHA1, bMode)
-
-                updateFile(clean, resSha, resMode, ren1.dstName)
-
-    return cleanMerge
-
-# Per entry merge function
-# ------------------------
-
-def processEntry(entry, branch1Name, branch2Name):
-    '''Merge one cache entry.'''
-
-    debug('processing', entry.path, 'clean cache:', cacheOnly)
-
-    cleanMerge = True
-
-    path = entry.path
-    oSha = entry.stages[1].sha1
-    oMode = entry.stages[1].mode
-    aSha = entry.stages[2].sha1
-    aMode = entry.stages[2].mode
-    bSha = entry.stages[3].sha1
-    bMode = entry.stages[3].mode
-
-    assert(oSha == None or isSha(oSha))
-    assert(aSha == None or isSha(aSha))
-    assert(bSha == None or isSha(bSha))
-
-    assert(oMode == None or type(oMode) is int)
-    assert(aMode == None or type(aMode) is int)
-    assert(bMode == None or type(bMode) is int)
-
-    if (oSha and (not aSha or not bSha)):
-    #
-    # Case A: Deleted in one
-    #
-        if (not aSha     and not bSha) or \
-           (aSha == oSha and not bSha) or \
-           (not aSha     and bSha == oSha):
-    # Deleted in both or deleted in one and unchanged in the other
-            if aSha:
-                output('Removing', path)
-            removeFile(True, path)
-        else:
-    # Deleted in one and changed in the other
-            cleanMerge = False
-            if not aSha:
-                output('CONFLICT (delete/modify):', path, 'deleted in',
-                       branch1Name, 'and modified in', branch2Name + '.',
-                       'Version', branch2Name, 'of', path, 'left in tree.')
-                mode = bMode
-                sha = bSha
-            else:
-                output('CONFLICT (modify/delete):', path, 'deleted in',
-                       branch2Name, 'and modified in', branch1Name + '.',
-                       'Version', branch1Name, 'of', path, 'left in tree.')
-                mode = aMode
-                sha = aSha
-
-            updateFile(False, sha, mode, path)
-
-    elif (not oSha and aSha     and not bSha) or \
-         (not oSha and not aSha and bSha):
-    #
-    # Case B: Added in one.
-    #
-        if aSha:
-            addBranch = branch1Name
-            otherBranch = branch2Name
-            mode = aMode
-            sha = aSha
-            conf = 'file/directory'
-        else:
-            addBranch = branch2Name
-            otherBranch = branch1Name
-            mode = bMode
-            sha = bSha
-            conf = 'directory/file'
-    
-        if path in currentDirectorySet:
-            cleanMerge = False
-            newPath = uniquePath(path, addBranch)
-            output('CONFLICT (' + conf + '):',
-                   'There is a directory with name', path, 'in',
-                   otherBranch + '. Adding', path, 'as', newPath)
-
-            removeFile(False, path)
-            updateFile(False, sha, mode, newPath)
-        else:
-            output('Adding', path)
-            updateFile(True, sha, mode, path)
-    
-    elif not oSha and aSha and bSha:
-    #
-    # Case C: Added in both (check for same permissions).
-    #
-        if aSha == bSha:
-            if aMode != bMode:
-                cleanMerge = False
-                output('CONFLICT: File', path,
-                       'added identically in both branches, but permissions',
-                       'conflict', '0%o' % aMode, '->', '0%o' % bMode)
-                output('CONFLICT: adding with permission:', '0%o' % aMode)
-
-                updateFile(False, aSha, aMode, path)
-            else:
-                # This case is handled by git-read-tree
-                assert(False)
-        else:
-            cleanMerge = False
-            newPath1 = uniquePath(path, branch1Name)
-            newPath2 = uniquePath(path, branch2Name)
-            output('CONFLICT (add/add): File', path,
-                   'added non-identically in both branches. Adding as',
-                   newPath1, 'and', newPath2, 'instead.')
-            removeFile(False, path)
-            updateFile(False, aSha, aMode, newPath1)
-            updateFile(False, bSha, bMode, newPath2)
-
-    elif oSha and aSha and bSha:
-    #
-    # case D: Modified in both, but differently.
-    #
-        output('Auto-merging', path)
-        [sha, mode, clean, dummy] = \
-              mergeFile(path, oSha, oMode,
-                        path, aSha, aMode,
-                        path, bSha, bMode,
-                        branch1Name, branch2Name)
-        if clean:
-            updateFile(True, sha, mode, path)
-        else:
-            cleanMerge = False
-            output('CONFLICT (content): Merge conflict in', path)
-
-            if cacheOnly:
-                updateFile(False, sha, mode, path)
-            else:
-                updateFileExt(sha, mode, path, updateCache=False, updateWd=True)
-    else:
-        die("ERROR: Fatal merge failure, shouldn't happen.")
-
-    return cleanMerge
-
-def usage():
-    die('Usage:', sys.argv[0], ' <base>... -- <head> <remote>..')
-
-# main entry point as merge strategy module
-# The first parameters up to -- are merge bases, and the rest are heads.
-
-if len(sys.argv) < 4:
-    usage()
-
-bases = []
-for nextArg in xrange(1, len(sys.argv)):
-    if sys.argv[nextArg] == '--':
-        if len(sys.argv) != nextArg + 3:
-            die('Not handling anything other than two heads merge.')
-        try:
-            h1 = firstBranch = sys.argv[nextArg + 1]
-            h2 = secondBranch = sys.argv[nextArg + 2]
-        except IndexError:
-            usage()
-        break
-    else:
-        bases.append(sys.argv[nextArg])
-
-print 'Merging', h1, 'with', h2
-
-try:
-    h1 = runProgram(['git-rev-parse', '--verify', h1 + '^0']).rstrip()
-    h2 = runProgram(['git-rev-parse', '--verify', h2 + '^0']).rstrip()
-
-    if len(bases) == 1:
-        base = runProgram(['git-rev-parse', '--verify',
-                           bases[0] + '^0']).rstrip()
-        ancestor = Commit(base, None)
-        [dummy, clean] = merge(Commit(h1, None), Commit(h2, None),
-                               firstBranch, secondBranch, None, 0,
-                               ancestor)
-    else:
-        graph = buildGraph([h1, h2])
-        [dummy, clean] = merge(graph.shaMap[h1], graph.shaMap[h2],
-                               firstBranch, secondBranch, graph)
-
-    print ''
-except:
-    if isinstance(sys.exc_info()[1], SystemExit):
-        raise
-    else:
-        traceback.print_exc(None, sys.stderr)
-        sys.exit(2)
-
-if clean:
-    sys.exit(0)
-else:
-    sys.exit(1)
index 0a8ef216cb8a45df7f900a0c9698bec3e4bb47bb..75e1de49ac9adb443e6065c93ac691c27f0f5518 100755 (executable)
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Linus Torvalds
 # Copyright (c) 2005 Junio C Hamano
 #
-# Resolve two trees, using enhancd multi-base read-tree.
+# Resolve two trees, using enhanced multi-base read-tree.
 
 # The first parameters up to -- are merge bases; the rest are heads.
 bases= head= remotes= sep_seen=
index cb094388bb5c830d37a790f5e7bbf13e8c61352a..04a5eb0f297a691fa0520d1729ea20bd66a53bd9 100755 (executable)
@@ -3,22 +3,26 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-USAGE='[-n] [--no-commit] [--squash] [-s <strategy>]... <merge-message> <head> <remote>+'
+USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
+
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
+require_work_tree
+cd_to_toplevel
+
+test -z "$(git ls-files -u)" ||
+       die "You are in the middle of a conflicted merge."
 
 LF='
 '
 
-all_strategies='recur recursive recursive-old octopus resolve stupid ours'
+all_strategies='recur recursive octopus resolve stupid ours'
 default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
 no_trivial_merge_strategies='ours'
 use_strategies=
 
 index_merge=t
-if test "@@NO_PYTHON@@"; then
-       all_strategies='recur recursive resolve octopus stupid ours'
-fi
 
 dropsave() {
        rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
@@ -34,7 +38,7 @@ savestate() {
 restorestate() {
         if test -f "$GIT_DIR/MERGE_SAVE"
        then
-               git reset --hard $head
+               git reset --hard $head >/dev/null
                cpio -iuv <"$GIT_DIR/MERGE_SAVE"
                git-update-index --refresh >/dev/null
        fi
@@ -59,10 +63,10 @@ squash_message () {
 finish () {
        if test '' = "$2"
        then
-               rlogm="$rloga"
+               rlogm="$GIT_REFLOG_ACTION"
        else
                echo "$2"
-               rlogm="$rloga: $2"
+               rlogm="$GIT_REFLOG_ACTION: $2"
        fi
        case "$squash" in
        t)
@@ -93,9 +97,25 @@ finish () {
        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)
+       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
+       then
+               echo "$rh               branch '$truname' (early part) of ."
+       else
+               echo "$rh               commit '$remote'"
+       fi
+}
+
 case "$#" in 0) usage ;; esac
 
-rloga=
+have_message=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
@@ -125,8 +145,17 @@ do
                        die "available strategies are: $all_strategies" ;;
                esac
                ;;
-       --reflog-action=*)
-               rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+       -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+               merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+               have_message=t
+               ;;
+       -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+               shift
+               case "$#" in
+               1)      usage ;;
+               esac
+               merge_msg="$1"
+               have_message=t
                ;;
        -*)     usage ;;
        *)      break ;;
@@ -134,22 +163,69 @@ do
        shift
 done
 
-merge_msg="$1"
-shift
-head_arg="$1"
-head=$(git-rev-parse --verify "$1"^0) || usage
-shift
+# 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
+# is the same as HEAD there instead.  Traditional format never would
+# 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) &&
+       test "$second_token" = "$head_commit"
+then
+       merge_msg="$1"
+       shift
+       head_arg="$1"
+       shift
+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
+       # the same for "git pull".
+       if test 1 -ne $#
+       then
+               echo >&2 "Can merge only exactly one commit into empty head"
+               exit 1
+       fi
+
+       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
+       exit
+
+else
+       # We are invoked directly as the first-class UI.
+       head_arg=HEAD
+
+       # All the rest are the commits being merged; prepare
+       # the standard merge summary message to be appended to
+       # the given message.  If remote is invalid we will die
+       # later in the common codepath so we discard the error
+       # in this loop.
+       merge_name=$(for remote
+               do
+                       merge_name "$remote"
+               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
 
 # All the rest are remote heads
 test "$#" = 0 && usage ;# we need at least one remote head.
-test "$rloga" = '' && rloga="merge: $@"
+set_reflog_action "merge $*"
 
 remoteheads=
 for remote
 do
-       remotehead=$(git-rev-parse --verify "$remote"^0) ||
+       remotehead=$(git-rev-parse --verify "$remote"^0 2>/dev/null) ||
            die "$remote - not something we can merge"
        remoteheads="${remoteheads}$remotehead "
+       eval GITHEAD_$remotehead='"$remote"'
+       export GITHEAD_$remotehead
 done
 set x $remoteheads ; shift
 
@@ -157,9 +233,21 @@ case "$use_strategies" in
 '')
        case "$#" in
        1)
-               use_strategies="$default_twohead_strategies" ;;
+               var="`git-config --get pull.twohead`"
+               if test -n "$var"
+               then
+                       use_strategies="$var"
+               else
+                       use_strategies="$default_twohead_strategies"
+               fi ;;
        *)
-               use_strategies="$default_octopus_strategies" ;;
+               var="`git-config --get pull.octopus`"
+               if test -n "$var"
+               then
+                       use_strategies="$var"
+               else
+                       use_strategies="$default_octopus_strategies"
+               fi ;;
        esac
        ;;
 esac
@@ -202,7 +290,7 @@ f,*)
        echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
        git-update-index --refresh 2>/dev/null
        new_head=$(git-rev-parse --verify "$1^0") &&
-       git-read-tree -u -v -m $head "$new_head" &&
+       git-read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" &&
        finish "$new_head" "Fast forward"
        dropsave
        exit 0
@@ -213,24 +301,30 @@ f,*)
        ;;
 ?,1,*,)
        # We are not doing octopus, not fast forward, and have only
-       # one common.  See if it is really trivial.
-       git var GIT_COMMITTER_IDENT >/dev/null || exit
-
-       echo "Trying really trivial in-index merge..."
+       # one common.
        git-update-index --refresh 2>/dev/null
-       if git-read-tree --trivial -m -u -v $common $head "$1" &&
-          result_tree=$(git-write-tree)
-       then
-           echo "Wonderful."
-           result_commit=$(
-               echo "$merge_msg" |
-               git-commit-tree $result_tree -p HEAD -p "$1"
-           ) || exit
-           finish "$result_commit" "In-index merge"
-           dropsave
-           exit 0
-       fi
-       echo "Nope."
+       case " $use_strategies " in
+       *' recursive '*|*' recur '*)
+               : run merge later
+               ;;
+       *)
+               # 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)
+               then
+                       echo "Wonderful."
+                       result_commit=$(
+                               echo "$merge_msg" |
+                               git-commit-tree $result_tree -p HEAD -p "$1"
+                       ) || exit
+                       finish "$result_commit" "In-index merge"
+                       dropsave
+                       exit 0
+               fi
+               echo "Nope."
+       esac
        ;;
 *)
        # An octopus.  If we can reach all the remote we are up to date.
@@ -337,7 +431,14 @@ fi
 case "$best_strategy" in
 '')
        restorestate
-       echo >&2 "No merge strategy handled the merge."
+       case "$use_strategies" in
+       ?*' '?*)
+               echo >&2 "No merge strategy handled the merge."
+               ;;
+       *)
+               echo >&2 "Merge with strategy $use_strategies failed."
+               ;;
+       esac
        exit 2
        ;;
 "$wt_strategy")
index 908941dd77007e71f5aa036bb190a14d628050c8..60a758bfe3bf535733612d4a0843fb42055f4c80 100644 (file)
@@ -163,7 +163,7 @@ class git_command:
             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-db\" ?")
+            die("Not a git repository... did you forget to \"git init\" ?")
         try:
             self.cdup = self.get_single("rev-parse --show-cdup")
             if self.cdup != "":
@@ -193,13 +193,13 @@ class git_command:
 
     def get_config(self, variable):
         try:
-            return self.git("repo-config --get %s" % variable)[0].rstrip()
+            return self.git("config --get %s" % variable)[0].rstrip()
         except:
             return None
 
     def set_config(self, variable, value):
         try:
-            self.git("repo-config %s %s"%(variable, value) )
+            self.git("config %s %s"%(variable, value) )
         except:
             die("Could not set %s to " % variable, value)
 
index c325ef761e4c558ab5c7c560da942e127e1be040..5208ee6ce0bb07dd22ad5ca63ee469cb03011164 100755 (executable)
@@ -7,21 +7,10 @@ GIT_DIR=$(git-rev-parse --git-dir 2>/dev/null) || :;
 get_data_source () {
        case "$1" in
        */*)
-               # Not so fast.  This could be the partial URL shorthand...
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               if test "$(git-repo-config --get "remote.$token.url")"
-               then
-                       echo config-partial
-               elif test -f "$GIT_DIR/branches/$token"
-               then
-                       echo branches-partial
-               else
-                       echo ''
-               fi
+               echo ''
                ;;
        *)
-               if test "$(git-repo-config --get "remote.$1.url")"
+               if test "$(git-config --get "remote.$1.url")"
                then
                        echo config
                elif test -f "$GIT_DIR/remotes/$1"
@@ -40,28 +29,19 @@ get_remote_url () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
        '')
-               echo "$1" ;;
-       config-partial)
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               url=$(git-repo-config --get "remote.$token.url")
-               echo "$url/$remainder"
+               echo "$1"
                ;;
        config)
-               git-repo-config --get "remote.$1.url"
+               git-config --get "remote.$1.url"
                ;;
        remotes)
                sed -ne '/^URL: */{
                        s///p
                        q
-               }' "$GIT_DIR/remotes/$1" ;;
+               }' "$GIT_DIR/remotes/$1"
+               ;;
        branches)
-               sed -e 's/#.*//' "$GIT_DIR/branches/$1" ;;
-       branches-partial)
-               token=$(expr "z$1" : 'z\([^/]*\)/')
-               remainder=$(expr "z$1" : 'z[^/]*/\(.*\)')
-               url=$(sed -e 's/#.*//' "$GIT_DIR/branches/$token")
-               echo "$url/$remainder"
+               sed -e 's/#.*//' "$GIT_DIR/branches/$1"
                ;;
        *)
                die "internal error: get-remote-url $1" ;;
@@ -69,18 +49,18 @@ get_remote_url () {
 }
 
 get_default_remote () {
-       curr_branch=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
-       origin=$(git-repo-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}
 }
 
 get_remote_default_refs_for_push () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | config-partial | branches | branches-partial)
+       '' | branches)
                ;; # no default push mapping, just send matching refs.
        config)
-               git-repo-config --get-all "remote.$1.push" ;;
+               git-config --get-all "remote.$1.push" ;;
        remotes)
                sed -ne '/^Push: */{
                        s///p
@@ -90,6 +70,64 @@ get_remote_default_refs_for_push () {
        esac
 }
 
+# Called from canon_refs_list_for_fetch -d "$remote", which
+# is called from get_remote_default_refs_for_fetch to grok
+# refspecs that are retrieved from the configuration, but not
+# from get_remote_refs_for_fetch when it deals with refspecs
+# supplied on the command line.  $ls_remote_result has the list
+# of refs available at remote.
+#
+# The first token returned is either "explicit" or "glob"; this
+# is to help prevent randomly "globbed" ref from being chosen as
+# a merge candidate
+expand_refs_wildcard () {
+       remote="$1"
+       shift
+       first_one=yes
+       if test "$#" = 0
+       then
+               echo empty
+               echo >&2 "Nothing specified for fetching with remote.$remote.fetch"
+       fi
+       for ref
+       do
+               lref=${ref#'+'}
+               # a non glob pattern is given back as-is.
+               expr "z$lref" : 'zrefs/.*/\*:refs/.*/\*$' >/dev/null || {
+                       if test -n "$first_one"
+                       then
+                               echo "explicit"
+                               first_one=
+                       fi
+                       echo "$ref"
+                       continue
+               }
+
+               # glob
+               if test -n "$first_one"
+               then
+                       echo "glob"
+                       first_one=
+               fi
+               from=`expr "z$lref" : 'z\(refs/.*/\)\*:refs/.*/\*$'`
+               to=`expr "z$lref" : 'zrefs/.*/\*:\(refs/.*/\)\*$'`
+               local_force=
+               test "z$lref" = "z$ref" || local_force='+'
+               echo "$ls_remote_result" |
+               sed -e '/\^{}$/d' |
+               (
+                       IFS='   '
+                       while read sha1 name
+                       do
+                               # ignore the ones that do not start with $from
+                               mapped=${name#"$from"}
+                               test "z$name" = "z$mapped" && continue
+                               echo "${local_force}${name}:${to}${mapped}"
+                       done
+               )
+       done
+}
+
 # Subroutine to canonicalize remote:local notation.
 canon_refs_list_for_fetch () {
        # If called from get_remote_default_refs_for_fetch
@@ -97,16 +135,24 @@ canon_refs_list_for_fetch () {
        # or the first one otherwise; add prefix . to the rest
        # to prevent the secondary branches to be merged by default.
        merge_branches=
+       curr_branch=
        if test "$1" = "-d"
        then
                shift ; remote="$1" ; shift
+               set $(expand_refs_wildcard "$remote" "$@")
+               is_explicit="$1"
+               shift
                if test "$remote" = "$(get_default_remote)"
                then
-                       curr_branch=$(git-symbolic-ref HEAD | \
+                       curr_branch=$(git-symbolic-ref -q HEAD | \
                            sed -e 's|^refs/heads/||')
-                       merge_branches=$(git-repo-config \
+                       merge_branches=$(git-config \
                            --get-all "branch.${curr_branch}.merge")
                fi
+               if test -z "$merge_branches" && test $is_explicit != explicit
+               then
+                       merge_branches=..this.will.never.match.any.ref..
+               fi
        fi
        for ref
        do
@@ -133,7 +179,7 @@ canon_refs_list_for_fetch () {
                        done
                fi
                case "$remote" in
-               '') remote=HEAD ;;
+               '' | HEAD ) remote=HEAD ;;
                refs/heads/* | refs/tags/* | refs/remotes/*) ;;
                heads/* | tags/* | remotes/* ) remote="refs/$remote" ;;
                *) remote="refs/heads/$remote" ;;
@@ -158,11 +204,11 @@ canon_refs_list_for_fetch () {
 get_remote_default_refs_for_fetch () {
        data_source=$(get_data_source "$1")
        case "$data_source" in
-       '' | config-partial | branches-partial)
+       '')
                echo "HEAD:" ;;
        config)
                canon_refs_list_for_fetch -d "$1" \
-                       $(git-repo-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
@@ -236,3 +282,16 @@ resolve_alternates () {
                esac
        done
 }
+
+get_uploadpack () {
+       data_source=$(get_data_source "$1")
+       case "$data_source" in
+       config)
+               uplp=$(git-config --get "remote.$1.uploadpack")
+               echo ${uplp:-git-upload-pack}
+               ;;
+       *)
+               echo "git-upload-pack"
+               ;;
+       esac
+}
index e23beb685d15447ac114c58d1795ed15bef88c9d..a3665d7751cc836dc669390df7b972c29575037b 100755 (executable)
@@ -6,7 +6,14 @@
 
 USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [<fetch-options>] <repo> <head>...'
 LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.'
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
+set_reflog_action "pull $*"
+require_work_tree
+cd_to_toplevel
+
+test -z "$(git ls-files -u)" ||
+       die "You are in the middle of a conflicted merge."
 
 strategy_args= no_summary= no_commit= squash=
 while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac
@@ -45,7 +52,7 @@ do
 done
 
 orig_head=$(git-rev-parse --verify HEAD 2>/dev/null)
-git-fetch --update-head-ok --reflog-action=pull "$@" || exit 1
+git-fetch --update-head-ok "$@" || exit 1
 
 curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
 if test "$curr_head" != "$orig_head"
@@ -76,6 +83,19 @@ merge_head=$(sed -e '/       not-for-merge   /d' \
 
 case "$merge_head" in
 '')
+       curr_branch=$(git-symbolic-ref -q HEAD)
+       case $? in
+         0) ;;
+         1) echo >&2 "You are not currently on a branch; you must explicitly"
+            echo >&2 "specify which branch you wish to merge:"
+            echo >&2 "  git pull <remote> <branch>"
+            exit 1;;
+         *) exit $?;;
+       esac
+       curr_branch=${curr_branch#refs/heads/}
+
+       echo >&2 "Warning: No merge candidate found because value of config option
+         \"branch.${curr_branch}.merge\" does not match any remote branch fetched."
        echo >&2 "No changes."
        exit 0
        ;;
@@ -85,18 +105,6 @@ case "$merge_head" in
                echo >&2 "Cannot merge multiple branches into empty head"
                exit 1
        fi
-       var=`git-repo-config --get pull.octopus`
-       if test -n "$var"
-       then
-               strategy_default_args="-s $var"
-       fi
-       ;;
-*)
-       var=`git-repo-config --get pull.twohead`
-       if test -n "$var"
-        then
-               strategy_default_args="-s $var"
-       fi
        ;;
 esac
 
@@ -107,13 +115,6 @@ then
        exit
 fi
 
-case "$strategy_args" in
-'')
-       strategy_args=$strategy_default_args
-       ;;
-esac
-
 merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
-git-merge "--reflog-action=pull $*" \
-       $no_summary $no_commit $squash $strategy_args \
+exec git-merge $no_summary $no_commit $squash $strategy_args \
        "$merge_name" HEAD $merge_head
index 10135da3ac04e3175fa45ca26b5ea31605a47284..671a5ff865b6ab891da4251ee3dd90892b13cbad 100755 (executable)
@@ -59,7 +59,7 @@ if ! [ -d "$QUILT_PATCHES" ] ; then
        exit 1
 fi
 
-# Temporay directories
+# Temporary directories
 tmp_dir=.dotest
 tmp_msg="$tmp_dir/msg"
 tmp_patch="$tmp_dir/patch"
@@ -89,7 +89,7 @@ for patch_name in $(cat "$QUILT_PATCHES/series" | grep -v '^#'); do
                        echo "No author found in $patch_name" >&2;
                        echo "---"
                        cat $tmp_msg
-                       echo -n "Author: ";
+                       printf "Author: ";
                        read patch_author
 
                        echo "$patch_author"
index 546fa446fc3c6c63488b9ef38cb5bdc960953cb1..b51d19d12e6d5a108f13c4ec60c5f5a6e560ef47 100755 (executable)
@@ -27,7 +27,12 @@ Example:       git-rebase master~1 topic
        /                   -->           /
   D---E---F---G master          D---E---F---G master
 '
+
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
+set_reflog_action rebase
+require_work_tree
+cd_to_toplevel
 
 RESOLVEMSG="
 When you have resolved this problem run \"git rebase --continue\".
@@ -40,6 +45,7 @@ do_merge=
 dotest=$GIT_DIR/.dotest-merge
 prec=4
 verbose=
+git_am_opt=
 
 continue_merge () {
        test -n "$prev_head" || die "prev_head must be defined"
@@ -80,10 +86,18 @@ continue_merge () {
 call_merge () {
        cmt="$(cat $dotest/cmt.$1)"
        echo "$cmt" > "$dotest/current"
-       git-merge-$strategy "$cmt^" -- HEAD "$cmt"
+       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))"'
+       eval GITHEAD_$hd='"$(cat $dotest/onto_name)"'
+       export GITHEAD_$cmt GITHEAD_$hd
+       git-merge-$strategy "$cmt^" -- "$hd" "$cmt"
        rv=$?
        case "$rv" in
        0)
+               unset GITHEAD_$cmt GITHEAD_$hd
                return
                ;;
        1)
@@ -132,13 +146,16 @@ do
                        finish_rb_merge
                        exit
                fi
-               git am --resolved --3way --resolvemsg="$RESOLVEMSG" \
-                       --reflog-action=rebase
+               git am --resolved --3way --resolvemsg="$RESOLVEMSG"
                exit
                ;;
        --skip)
                if test -d "$dotest"
                then
+                       if test -d "$GIT_DIR/rr-cache"
+                       then
+                               git-rerere clear
+                       fi
                        prev_head="`cat $dotest/prev_head`"
                        end="`cat $dotest/end`"
                        msgnum="`cat $dotest/msgnum`"
@@ -152,11 +169,14 @@ do
                        finish_rb_merge
                        exit
                fi
-               git am -3 --skip --resolvemsg="$RESOLVEMSG" \
-                       --reflog-action=rebase
+               git am -3 --skip --resolvemsg="$RESOLVEMSG"
                exit
                ;;
        --abort)
+               if test -d "$GIT_DIR/rr-cache"
+               then
+                       git-rerere clear
+               fi
                if test -d "$dotest"
                then
                        rm -r "$dotest"
@@ -194,6 +214,10 @@ do
        -v|--verbose)
                verbose=t
                ;;
+       -C*)
+               git_am_opt=$1
+               shift
+               ;;
        -*)
                usage
                ;;
@@ -230,7 +254,8 @@ fi
 git-update-index --refresh || exit
 diff=$(git-diff-index --cached --name-status -r HEAD)
 case "$diff" in
-?*)    echo "$diff"
+?*)    echo "cannot rebase: your index is not up-to-date"
+       echo "$diff"
        exit 1
        ;;
 esac
@@ -256,8 +281,12 @@ case "$#" in
        git-checkout "$2" || usage
        ;;
 *)
-       branch_name=`git symbolic-ref HEAD` || die "No current branch"
-       branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'`
+       if branch_name=`git symbolic-ref -q HEAD`
+       then
+               branch_name=`expr "z$branch_name" : 'zrefs/heads/\(.*\)'`
+       else
+               branch_name=HEAD ;# detached
+       fi
        ;;
 esac
 branch=$(git-rev-parse --verify "${branch_name}^0") || exit
@@ -284,6 +313,7 @@ then
 fi
 
 # Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
+echo "First, rewinding head to replay your work on top of it..."
 git-reset --hard "$onto"
 
 # If the $onto is a proper descendant of the tip of the branch, then
@@ -297,25 +327,16 @@ fi
 if test -z "$do_merge"
 then
        git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
-       git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \
-               --reflog-action=rebase
+       git am $git_am_opt --binary -3 -k --resolvemsg="$RESOLVEMSG"
        exit $?
 fi
 
-if test "@@NO_PYTHON@@" && test "$strategy" = "recursive-old"
-then
-       die 'The recursive-old merge strategy is written in Python,
-which this installation of git was not configured with.  Please consider
-a different merge strategy (e.g. recursive, resolve, or stupid)
-or install Python and git with Python support.'
-
-fi
-
 # start doing a rebase with git-merge
 # this is rename-aware if the recursive (default) strategy is used
 
 mkdir -p "$dotest"
 echo "$onto" > "$dotest/onto"
+echo "$onto_name" > "$dotest/onto_name"
 prev_head=`git-rev-parse HEAD^0`
 echo "$prev_head" > "$dotest/prev_head"
 
diff --git a/git-remote.perl b/git-remote.perl
new file mode 100755 (executable)
index 0000000..c56c5a8
--- /dev/null
@@ -0,0 +1,364 @@
+#!/usr/bin/perl -w
+
+use Git;
+my $git = Git->repository();
+
+sub add_remote_config {
+       my ($hash, $name, $what, $value) = @_;
+       if ($what eq 'url') {
+               if (exists $hash->{$name}{'URL'}) {
+                       print STDERR "Warning: more than one remote.$name.url\n";
+               }
+               $hash->{$name}{'URL'} = $value;
+       }
+       elsif ($what eq 'fetch') {
+               $hash->{$name}{'FETCH'} ||= [];
+               push @{$hash->{$name}{'FETCH'}}, $value;
+       }
+       if (!exists $hash->{$name}{'SOURCE'}) {
+               $hash->{$name}{'SOURCE'} = 'config';
+       }
+}
+
+sub add_remote_remotes {
+       my ($hash, $file, $name) = @_;
+
+       if (exists $hash->{$name}) {
+               $hash->{$name}{'WARNING'} = 'ignored due to config';
+               return;
+       }
+
+       my $fh;
+       if (!open($fh, '<', $file)) {
+               print STDERR "Warning: cannot open $file\n";
+               return;
+       }
+       my $it = { 'SOURCE' => 'remotes' };
+       $hash->{$name} = $it;
+       while (<$fh>) {
+               chomp;
+               if (/^URL:\s*(.*)$/) {
+                       # Having more than one is Ok -- it is used for push.
+                       if (! exists $it->{'URL'}) {
+                               $it->{'URL'} = $1;
+                       }
+               }
+               elsif (/^Push:\s*(.*)$/) {
+                       ; # later
+               }
+               elsif (/^Pull:\s*(.*)$/) {
+                       $it->{'FETCH'} ||= [];
+                       push @{$it->{'FETCH'}}, $1;
+               }
+               elsif (/^\#/) {
+                       ; # ignore
+               }
+               else {
+                       print STDERR "Warning: funny line in $file: $_\n";
+               }
+       }
+       close($fh);
+}
+
+sub list_remote {
+       my ($git) = @_;
+       my %seen = ();
+       my @remotes = eval {
+               $git->command(qw(config --get-regexp), '^remote\.');
+       };
+       for (@remotes) {
+               if (/^remote\.([^.]*)\.(\S*)\s+(.*)$/) {
+                       add_remote_config(\%seen, $1, $2, $3);
+               }
+       }
+
+       my $dir = $git->repo_path() . "/remotes";
+       if (opendir(my $dh, $dir)) {
+               local $_;
+               while ($_ = readdir($dh)) {
+                       chomp;
+                       next if (! -f "$dir/$_" || ! -r _);
+                       add_remote_remotes(\%seen, "$dir/$_", $_);
+               }
+       }
+
+       return \%seen;
+}
+
+sub add_branch_config {
+       my ($hash, $name, $what, $value) = @_;
+       if ($what eq 'remote') {
+               if (exists $hash->{$name}{'REMOTE'}) {
+                       print STDERR "Warning: more than one branch.$name.remote\n";
+               }
+               $hash->{$name}{'REMOTE'} = $value;
+       }
+       elsif ($what eq 'merge') {
+               $hash->{$name}{'MERGE'} ||= [];
+               push @{$hash->{$name}{'MERGE'}}, $value;
+       }
+}
+
+sub list_branch {
+       my ($git) = @_;
+       my %seen = ();
+       my @branches = eval {
+               $git->command(qw(config --get-regexp), '^branch\.');
+       };
+       for (@branches) {
+               if (/^branch\.([^.]*)\.(\S*)\s+(.*)$/) {
+                       add_branch_config(\%seen, $1, $2, $3);
+               }
+       }
+
+       return \%seen;
+}
+
+my $remote = list_remote($git);
+my $branch = list_branch($git);
+
+sub update_ls_remote {
+       my ($harder, $info) = @_;
+
+       return if (($harder == 0) ||
+                  (($harder == 1) && exists $info->{'LS_REMOTE'}));
+
+       my @ref = map {
+               s|^[0-9a-f]{40}\s+refs/heads/||;
+               $_;
+       } $git->command(qw(ls-remote --heads), $info->{'URL'});
+       $info->{'LS_REMOTE'} = \@ref;
+}
+
+sub list_wildcard_mapping {
+       my ($forced, $ours, $ls) = @_;
+       my %refs;
+       for (@$ls) {
+               $refs{$_} = 01; # bit #0 to say "they have"
+       }
+       for ($git->command('for-each-ref', "refs/remotes/$ours")) {
+               chomp;
+               next unless (s|^[0-9a-f]{40}\s[a-z]+\srefs/remotes/$ours/||);
+               next if ($_ eq 'HEAD');
+               $refs{$_} ||= 0;
+               $refs{$_} |= 02; # bit #1 to say "we have"
+       }
+       my (@new, @stale, @tracked);
+       for (sort keys %refs) {
+               my $have = $refs{$_};
+               if ($have == 1) {
+                       push @new, $_;
+               }
+               elsif ($have == 2) {
+                       push @stale, $_;
+               }
+               elsif ($have == 3) {
+                       push @tracked, $_;
+               }
+       }
+       return \@new, \@stale, \@tracked;
+}
+
+sub list_mapping {
+       my ($name, $info) = @_;
+       my $fetch = $info->{'FETCH'};
+       my $ls = $info->{'LS_REMOTE'};
+       my (@new, @stale, @tracked);
+
+       for (@$fetch) {
+               next unless (/(\+)?([^:]+):(.*)/);
+               my ($forced, $theirs, $ours) = ($1, $2, $3);
+               if ($theirs eq 'refs/heads/*' &&
+                   $ours =~ /^refs\/remotes\/(.*)\/\*$/) {
+                       # wildcard mapping
+                       my ($w_new, $w_stale, $w_tracked)
+                               = list_wildcard_mapping($forced, $1, $ls);
+                       push @new, @$w_new;
+                       push @stale, @$w_stale;
+                       push @tracked, @$w_tracked;
+               }
+               elsif ($theirs =~ /\*/ || $ours =~ /\*/) {
+                       print STDERR "Warning: unrecognized mapping in remotes.$name.fetch: $_\n";
+               }
+               elsif ($theirs =~ s|^refs/heads/||) {
+                       if (!grep { $_ eq $theirs } @$ls) {
+                               push @stale, $theirs;
+                       }
+                       elsif ($ours ne '') {
+                               push @tracked, $theirs;
+                       }
+               }
+       }
+       return \@new, \@stale, \@tracked;
+}
+
+sub show_mapping {
+       my ($name, $info) = @_;
+       my ($new, $stale, $tracked) = list_mapping($name, $info);
+       if (@$new) {
+               print "  New remote branches (next fetch will store in remotes/$name)\n";
+               print "    @$new\n";
+       }
+       if (@$stale) {
+               print "  Stale tracking branches in remotes/$name (use 'git remote prune')\n";
+               print "    @$stale\n";
+       }
+       if (@$tracked) {
+               print "  Tracked remote branches\n";
+               print "    @$tracked\n";
+       }
+}
+
+sub prune_remote {
+       my ($name, $ls_remote) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return;
+       }
+       my $info = $remote->{$name};
+       update_ls_remote($ls_remote, $info);
+
+       my ($new, $stale, $tracked) = list_mapping($name, $info);
+       my $prefix = "refs/remotes/$name";
+       foreach my $to_prune (@$stale) {
+               my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
+               $git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
+       }
+}
+
+sub show_remote {
+       my ($name, $ls_remote) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return;
+       }
+       my $info = $remote->{$name};
+       update_ls_remote($ls_remote, $info);
+
+       print "* remote $name\n";
+       print "  URL: $info->{'URL'}\n";
+       for my $branchname (sort keys %$branch) {
+               next if ($branch->{$branchname}{'REMOTE'} ne $name);
+               my @merged = map {
+                       s|^refs/heads/||;
+                       $_;
+               } split(' ',"@{$branch->{$branchname}{'MERGE'}}");
+               next unless (@merged);
+               print "  Remote branch(es) merged with 'git pull' while on branch $branchname\n";
+               print "    @merged\n";
+       }
+       if ($info->{'LS_REMOTE'}) {
+               show_mapping($name, $info);
+       }
+}
+
+sub add_remote {
+       my ($name, $url, $opts) = @_;
+       if (exists $remote->{$name}) {
+               print STDERR "remote $name already exists.\n";
+               exit(1);
+       }
+       $git->command('config', "remote.$name.url", $url);
+       my $track = $opts->{'track'} || ["*"];
+
+       for (@$track) {
+               $git->command('config', '--add', "remote.$name.fetch",
+                             "+refs/heads/$_:refs/remotes/$name/$_");
+       }
+       if ($opts->{'fetch'}) {
+               $git->command('fetch', $name);
+       }
+       if (exists $opts->{'master'}) {
+               $git->command('symbolic-ref', "refs/remotes/$name/HEAD",
+                             "refs/remotes/$name/$opts->{'master'}");
+       }
+}
+
+sub add_usage {
+       print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
+       exit(1);
+}
+
+if (!@ARGV) {
+       for (sort keys %$remote) {
+               print "$_\n";
+       }
+}
+elsif ($ARGV[0] eq 'show') {
+       my $ls_remote = 1;
+       my $i;
+       for ($i = 1; $i < @ARGV; $i++) {
+               if ($ARGV[$i] eq '-n') {
+                       $ls_remote = 0;
+               }
+               else {
+                       last;
+               }
+       }
+       if ($i >= @ARGV) {
+               print STDERR "Usage: git remote show <remote>\n";
+               exit(1);
+       }
+       for (; $i < @ARGV; $i++) {
+               show_remote($ARGV[$i], $ls_remote);
+       }
+}
+elsif ($ARGV[0] eq 'prune') {
+       my $ls_remote = 1;
+       my $i;
+       for ($i = 1; $i < @ARGV; $i++) {
+               if ($ARGV[$i] eq '-n') {
+                       $ls_remote = 0;
+               }
+               else {
+                       last;
+               }
+       }
+       if ($i >= @ARGV) {
+               print STDERR "Usage: git remote prune <remote>\n";
+               exit(1);
+       }
+       for (; $i < @ARGV; $i++) {
+               prune_remote($ARGV[$i], $ls_remote);
+       }
+}
+elsif ($ARGV[0] eq 'add') {
+       my %opts = ();
+       while (1 < @ARGV && $ARGV[1] =~ /^-/) {
+               my $opt = $ARGV[1];
+               shift @ARGV;
+               if ($opt eq '-f' || $opt eq '--fetch') {
+                       $opts{'fetch'} = 1;
+                       next;
+               }
+               if ($opt eq '-t' || $opt eq '--track') {
+                       if (@ARGV < 1) {
+                               add_usage();
+                       }
+                       $opts{'track'} ||= [];
+                       push @{$opts{'track'}}, $ARGV[1];
+                       shift @ARGV;
+                       next;
+               }
+               if ($opt eq '-m' || $opt eq '--master') {
+                       if ((@ARGV < 1) || exists $opts{'master'}) {
+                               add_usage();
+                       }
+                       $opts{'master'} = $ARGV[1];
+                       shift @ARGV;
+                       next;
+               }
+               add_usage();
+       }
+       if (@ARGV != 3) {
+               add_usage();
+       }
+       add_remote($ARGV[1], $ARGV[2], \%opts);
+}
+else {
+       print STDERR "Usage: git remote\n";
+       print STDERR "       git remote add <name> <url>\n";
+       print STDERR "       git remote show <name>\n";
+       print STDERR "       git remote prune <name>\n";
+       exit(1);
+}
index f150a558ca7a7a659f4eb88be3a275e1a4e87337..ddfa8b44a17f1ea7ff039bca1d6132ba07e5cf12 100755 (executable)
@@ -28,7 +28,7 @@ done
 # Later we will default repack.UseDeltaBaseOffset to true
 default_dbo=false
 
-case "`git repo-config --bool repack.usedeltabaseoffset ||
+case "`git config --bool repack.usedeltabaseoffset ||
        echo $default_dbo`" in
 true)
        extra="$extra --delta-base-offset" ;;
@@ -62,11 +62,13 @@ case ",$all_into_one," in
 esac
 
 args="$args $local $quiet $no_reuse_delta$extra"
-name=$(git-pack-objects --non-empty --all $args </dev/null "$PACKTMP") ||
+name=$(git-pack-objects --non-empty --all --reflog $args </dev/null "$PACKTMP") ||
        exit 1
 if [ -z "$name" ]; then
        echo Nothing new to pack.
 else
+       chmod a-w "$PACKTMP-$name.pack"
+       chmod a-w "$PACKTMP-$name.idx"
        if test "$quiet" != '-q'; then
            echo "Pack pack-$name created."
        fi
@@ -108,7 +110,7 @@ then
                  done
                )
        fi
-       git-prune-packed
+       git-prune-packed $quiet
 fi
 
 case "$no_update_info" in
index 4319e35c628efdd86745c560d2dfe306b180fb1b..4eacc3a059ca3d90305cd1f28b91460b56e07167 100755 (executable)
@@ -30,4 +30,4 @@ echo "  $url"
 echo
 
 git log  $baserev..$headrev | git-shortlog ;
-git diff --stat --summary $baserev..$headrev
+git diff -M --stat --summary $baserev..$headrev
diff --git a/git-rerere.perl b/git-rerere.perl
deleted file mode 100755 (executable)
index d3664ff..0000000
+++ /dev/null
@@ -1,227 +0,0 @@
-#!/usr/bin/perl
-#
-# REuse REcorded REsolve.  This tool records a conflicted automerge
-# result and its hand resolution, and helps to resolve future
-# automerge that results in the same conflict.
-#
-# To enable this feature, create a directory 'rr-cache' under your
-# .git/ directory.
-
-use Digest;
-use File::Path;
-use File::Copy;
-
-my $git_dir = $::ENV{GIT_DIR} || ".git";
-my $rr_dir = "$git_dir/rr-cache";
-my $merge_rr = "$git_dir/rr-cache/MERGE_RR";
-
-my %merge_rr = ();
-
-sub read_rr {
-       if (!-f $merge_rr) {
-               %merge_rr = ();
-               return;
-       }
-       my $in;
-       local $/ = "\0";
-       open $in, "<$merge_rr" or die "$!: $merge_rr";
-       while (<$in>) {
-               chomp;
-               my ($name, $path) = /^([0-9a-f]{40})\t(.*)$/s;
-               $merge_rr{$path} = $name;
-       }
-       close $in;
-}
-
-sub write_rr {
-       my $out;
-       open $out, ">$merge_rr" or die "$!: $merge_rr";
-       for my $path (sort keys %merge_rr) {
-               my $name = $merge_rr{$path};
-               print $out "$name\t$path\0";
-       }
-       close $out;
-}
-
-sub compute_conflict_name {
-       my ($path) = @_;
-       my @side = ();
-       my $in;
-       open $in, "<$path"  or die "$!: $path";
-
-       my $sha1 = Digest->new("SHA-1");
-       my $hunk = 0;
-       while (<$in>) {
-               if (/^<<<<<<< .*/) {
-                       $hunk++;
-                       @side = ([], undef);
-               }
-               elsif (/^=======$/) {
-                       $side[1] = [];
-               }
-               elsif (/^>>>>>>> .*/) {
-                       my ($one, $two);
-                       $one = join('', @{$side[0]});
-                       $two = join('', @{$side[1]});
-                       if ($two le $one) {
-                               ($one, $two) = ($two, $one);
-                       }
-                       $sha1->add($one);
-                       $sha1->add("\0");
-                       $sha1->add($two);
-                       $sha1->add("\0");
-                       @side = ();
-               }
-               elsif (@side == 0) {
-                       next;
-               }
-               elsif (defined $side[1]) {
-                       push @{$side[1]}, $_;
-               }
-               else {
-                       push @{$side[0]}, $_;
-               }
-       }
-       close $in;
-       return ($sha1->hexdigest, $hunk);
-}
-
-sub record_preimage {
-       my ($path, $name) = @_;
-       my @side = ();
-       my ($in, $out);
-       open $in, "<$path"  or die "$!: $path";
-       open $out, ">$name" or die "$!: $name";
-
-       while (<$in>) {
-               if (/^<<<<<<< .*/) {
-                       @side = ([], undef);
-               }
-               elsif (/^=======$/) {
-                       $side[1] = [];
-               }
-               elsif (/^>>>>>>> .*/) {
-                       my ($one, $two);
-                       $one = join('', @{$side[0]});
-                       $two = join('', @{$side[1]});
-                       if ($two le $one) {
-                               ($one, $two) = ($two, $one);
-                       }
-                       print $out "<<<<<<<\n";
-                       print $out $one;
-                       print $out "=======\n";
-                       print $out $two;
-                       print $out ">>>>>>>\n";
-                       @side = ();
-               }
-               elsif (@side == 0) {
-                       print $out $_;
-               }
-               elsif (defined $side[1]) {
-                       push @{$side[1]}, $_;
-               }
-               else {
-                       push @{$side[0]}, $_;
-               }
-       }
-       close $out;
-       close $in;
-}
-
-sub find_conflict {
-       my $in;
-       local $/ = "\0";
-       my $pid = open($in, '-|');
-       die "$!" unless defined $pid;
-       if (!$pid) {
-               exec(qw(git ls-files -z -u)) or die "$!: ls-files";
-       }
-       my %path = ();
-       my @path = ();
-       while (<$in>) {
-               chomp;
-               my ($mode, $sha1, $stage, $path) =
-                   /^([0-7]+) ([0-9a-f]{40}) ([123])\t(.*)$/s;
-               $path{$path} |= (1 << $stage);
-       }
-       close $in;
-       while (my ($path, $status) = each %path) {
-               if ($status == 14) { push @path, $path; }
-       }
-       return @path;
-}
-
-sub merge {
-       my ($name, $path) = @_;
-       record_preimage($path, "$rr_dir/$name/thisimage");
-       unless (system('merge', map { "$rr_dir/$name/${_}image" }
-                      qw(this pre post))) {
-               my $in;
-               open $in, "<$rr_dir/$name/thisimage" or
-                   die "$!: $name/thisimage";
-               my $out;
-               open $out, ">$path" or die "$!: $path";
-               while (<$in>) { print $out $_; }
-               close $in;
-               close $out;
-               return 1;
-       }
-       return 0;
-}
-
--d "$rr_dir" || exit(0);
-
-read_rr();
-my %conflict = map { $_ => 1 } find_conflict();
-
-# MERGE_RR records paths with conflicts immediately after merge
-# failed.  Some of the conflicted paths might have been hand resolved
-# in the working tree since then, but the initial run would catch all
-# and register their preimages.
-
-for my $path (keys %conflict) {
-       # This path has conflict.  If it is not recorded yet,
-       # record the pre-image.
-       if (!exists $merge_rr{$path}) {
-               my ($name, $hunk) = compute_conflict_name($path);
-               next unless ($hunk);
-               $merge_rr{$path} = $name;
-               if (! -d "$rr_dir/$name") {
-                       mkpath("$rr_dir/$name", 0, 0777);
-                       print STDERR "Recorded preimage for '$path'\n";
-                       record_preimage($path, "$rr_dir/$name/preimage");
-               }
-       }
-}
-
-# Now some of the paths that had conflicts earlier might have been
-# hand resolved.  Others may be similar to a conflict already that
-# was resolved before.
-
-for my $path (keys %merge_rr) {
-       my $name = $merge_rr{$path};
-
-       # We could resolve this automatically if we have images.
-       if (-f "$rr_dir/$name/preimage" &&
-           -f "$rr_dir/$name/postimage") {
-               if (merge($name, $path)) {
-                       print STDERR "Resolved '$path' using previous resolution.\n";
-                       # Then we do not have to worry about this path
-                       # anymore.
-                       delete $merge_rr{$path};
-                       next;
-               }
-       }
-
-       # Let's see if we have resolved it.
-       (undef, my $hunk) = compute_conflict_name($path);
-       next if ($hunk);
-
-       print STDERR "Recorded resolution for '$path'.\n";
-       copy($path, "$rr_dir/$name/postimage");
-       # And we do not have to worry about this path anymore.
-       delete $merge_rr{$path};
-}
-
-# Write out the rest.
-write_rr();
index 3133b5bd25ca7ad3c2a5451ee9c2e0f4514cd980..fee6d98d9cba708828c36a2d2a29dbec8662c36b 100755 (executable)
@@ -1,28 +1,60 @@
 #!/bin/sh
-
-USAGE='[--mixed | --soft | --hard]  [<commit-ish>]'
+#
+# Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
+#
+USAGE='[--mixed | --soft | --hard]  [<commit-ish>] [ [--] <paths>...]'
+SUBDIRECTORY_OK=Yes
 . git-sh-setup
+set_reflog_action "reset $*"
+require_work_tree
+
+update= reset_type=--mixed
+unset rev
 
-update=
-reset_type=--mixed
-case "$1" in
---mixed | --soft | --hard)
-       reset_type="$1"
+while case $# in 0) break ;; esac
+do
+       case "$1" in
+       --mixed | --soft | --hard)
+               reset_type="$1"
+               ;;
+       --)
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               rev=$(git-rev-parse --verify "$1") || exit
+               shift
+               break
+               ;;
+       esac
        shift
-       ;;
--*)
-        usage ;;
-esac
+done
 
-case $# in
-0) rev=HEAD ;;
-1) rev=$(git-rev-parse --verify "$1") || exit ;;
-*) usage ;;
-esac
+: ${rev=HEAD}
 rev=$(git-rev-parse --verify $rev^0) || exit
 
-# We need to remember the set of paths that _could_ be left
-# behind before a hard reset, so that we can remove them.
+# Skip -- in "git reset HEAD -- foo" and "git reset -- foo".
+case "$1" in --) shift ;; esac
+
+# git reset --mixed tree [--] paths... can be used to
+# load chosen paths from the tree into the index without
+# affecting the working tree nor HEAD.
+if test $# != 0
+then
+       test "$reset_type" = "--mixed" ||
+               die "Cannot do partial $reset_type reset."
+
+       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
+       exit
+fi
+
+cd_to_toplevel
+
 if test "$reset_type" = "--hard"
 then
        update=-u
@@ -49,12 +81,17 @@ then
 else
        rm -f "$GIT_DIR/ORIG_HEAD"
 fi
-git-update-ref -m "reset $reset_type $*" HEAD "$rev"
+git-update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
 update_ref_status=$?
 
 case "$reset_type" in
 --hard )
-       ;; # Nothing else to do
+       test $update_ref_status = 0 && {
+               printf "HEAD is now at "
+               GIT_PAGER= git log --max-count=1 --pretty=oneline \
+                       --abbrev-commit HEAD
+       }
+       ;;
 --soft )
        ;; # Nothing else to do
 --mixed )
@@ -63,6 +100,7 @@ case "$reset_type" in
        ;;
 esac
 
-rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" "$GIT_DIR/SQUASH_MSG"
+rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" \
+       "$GIT_DIR/SQUASH_MSG" "$GIT_DIR/MERGE_MSG"
 
 exit $update_ref_status
index 6eab3c72df0db0994a352836018a7fc35ba1eae3..49f00321b28833c24ebb78ea2104f34091d43017 100755 (executable)
@@ -16,9 +16,14 @@ case "$0" in
        me=cherry-pick
        USAGE='[--edit] [-n] [-r] [-x] <commit-ish>'  ;;
 * )
-       die "What are you talking about?" ;;
+       echo >&2 "What are you talking about?"
+       exit 1 ;;
 esac
+
+SUBDIRECTORY_OK=Yes ;# we will cd up
 . git-sh-setup
+require_work_tree
+cd_to_toplevel
 
 no_commit=
 while case "$#" in 0) break ;; esac
@@ -49,6 +54,8 @@ do
        shift
 done
 
+set_reflog_action "$me"
+
 test "$me,$replay" = "revert,t" && usage
 
 case "$no_commit" in
@@ -76,6 +83,8 @@ prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
 git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
        die "Cannot run $me a multi-parent commit."
 
+encoding=$(git config i18n.commitencoding || echo UTF-8)
+
 # "commit" is an existing commit.  We would want to apply
 # the difference it introduces since its first parent "prev"
 # on top of the current HEAD if we are cherry-pick.  Or the
@@ -83,10 +92,11 @@ git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
 
 case "$me" in
 revert)
-       git-rev-list --pretty=oneline --max-count=1 $commit |
+       git show -s --pretty=oneline --encoding="$encoding" $commit |
        sed -e '
                s/^[^ ]* /Revert "/
-               s/$/"/'
+               s/$/"/
+       '
        echo
        echo "This reverts commit $commit."
        test "$rev" = "$commit" ||
@@ -115,14 +125,17 @@ cherry-pick)
 
                q
        }'
-       set_author_env=`git-cat-file commit "$commit" |
+
+       logmsg=`git show -s --pretty=raw --encoding="$encoding" "$commit"`
+       set_author_env=`echo "$logmsg" |
        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
 
-       git-cat-file commit $commit | sed -e '1,/^$/d'
+       echo "$logmsg" |
+       sed -e '1,/^$/d' -e 's/^    //'
        case "$replay" in
        '')
                echo "(cherry picked from commit $commit)"
@@ -135,37 +148,38 @@ cherry-pick)
 
 esac >.msg
 
+eval GITHEAD_$head=HEAD
+eval GITHEAD_$next='`git show -s \
+       --pretty=oneline --encoding="$encoding" "$commit" |
+       sed -e "s/^[^ ]* //"`'
+export GITHEAD_$head GITHEAD_$next
+
 # This three way merge is an interesting one.  We are at
 # $head, and would want to apply the change between $commit
 # and $prev on top of us (when reverting), or the change between
 # $prev and $commit on top of us (when cherry-picking or replaying).
 
-echo >&2 "First trying simple merge strategy to $me."
-git-read-tree -m -u --aggressive $base $head $next &&
+git-merge-recursive $base -- $head $next &&
 result=$(git-write-tree 2>/dev/null) || {
-    echo >&2 "Simple $me fails; trying Automatic $me."
-    git-merge-index -o git-merge-one-file -a || {
-           mv -f .msg "$GIT_DIR/MERGE_MSG"
-           {
-               echo '
+       mv -f .msg "$GIT_DIR/MERGE_MSG"
+       {
+           echo '
 Conflicts:
 '
                git ls-files --unmerged |
                sed -e 's/^[^   ]*      /       /' |
                uniq
-           } >>"$GIT_DIR/MERGE_MSG"
-           echo >&2 "Automatic $me failed.  After resolving the conflicts,"
-           echo >&2 "mark the corrected paths with 'git-update-index <paths>'"
-           echo >&2 "and commit the result."
-           case "$me" in
-           cherry-pick)
+       } >>"$GIT_DIR/MERGE_MSG"
+       echo >&2 "Automatic $me failed.  After resolving the conflicts,"
+       echo >&2 "mark the corrected paths with 'git-add <paths>'"
+       echo >&2 "and commit the result."
+       case "$me" in
+       cherry-pick)
                echo >&2 "You may choose to use the following when making"
                echo >&2 "the commit:"
                echo >&2 "$set_author_env"
-           esac
-           exit 1
-    }
-    result=$(git-write-tree) || exit
+       esac
+       exit 1
 }
 echo >&2 "Finished one $me."
 
index 4c87c20c158fe3edfd0b770f1855e67e20038e3b..6a285bfd214132f792f7493433a52019ba80b5ab 100755 (executable)
@@ -178,11 +178,10 @@ my $prompting = 0;
 if (!defined $from) {
        $from = $author || $committer;
        do {
-               $_ = $term->readline("Who should the emails appear to be from? ",
-                       $from);
+               $_ = $term->readline("Who should the emails appear to be from? [$from] ");
        } while (!defined $_);
 
-       $from = $_;
+       $from = $_ if ($_);
        print "Emails will be sent from: ", $from, "\n";
        $prompting++;
 }
@@ -403,6 +402,15 @@ sub make_message_id
 $cc = "";
 $time = time - scalar $#files;
 
+sub unquote_rfc2047 {
+       local ($_) = @_;
+       if (s/=\?utf-8\?q\?(.*)\?=/$1/g) {
+               s/_/ /g;
+               s/=([0-9A-F]{2})/chr(hex($1))/eg;
+       }
+       return "$_";
+}
+
 sub send_message
 {
        my @recipients = unique_email_list(@to);
@@ -556,6 +564,7 @@ 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";
        }
 
index 42f9b1c125578a7158392b53ec8792d6c6bbb273..f24c7f2d23c13e9874308a019f3c0f93225de3c0 100755 (executable)
@@ -20,6 +20,39 @@ usage() {
        die "Usage: $0 $USAGE"
 }
 
+set_reflog_action() {
+       if [ -z "${GIT_REFLOG_ACTION:+set}" ]
+       then
+               GIT_REFLOG_ACTION="$*"
+               export GIT_REFLOG_ACTION
+       fi
+}
+
+is_bare_repository () {
+       git-config --bool --get core.bare ||
+       case "$GIT_DIR" in
+       .git | */.git) echo false ;;
+       *) echo true ;;
+       esac
+}
+
+cd_to_toplevel () {
+       cdup=$(git-rev-parse --show-cdup)
+       if test ! -z "$cdup"
+       then
+               cd "$cdup" || {
+                       echo >&2 "Cannot chdir to $cdup, the toplevel of the working tree"
+                       exit 1
+               }
+       fi
+}
+
+require_work_tree () {
+       test $(is_bare_repository) = false &&
+       test $(git-rev-parse --is-inside-git-dir) = false ||
+       die "fatal: $0 cannot be used without a working tree."
+}
+
 if [ -z "$LONG_USAGE" ]
 then
        LONG_USAGE="Usage: $0 $USAGE"
@@ -39,7 +72,11 @@ esac
 if [ -z "$SUBDIRECTORY_OK" ]
 then
        : ${GIT_DIR=.git}
-       GIT_DIR=$(GIT_DIR="$GIT_DIR" git-rev-parse --git-dir) || exit
+       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
 fi
diff --git a/git-shortlog.perl b/git-shortlog.perl
deleted file mode 100755 (executable)
index 334fec7..0000000
+++ /dev/null
@@ -1,234 +0,0 @@
-#!/usr/bin/perl -w
-
-use strict;
-use Getopt::Std;
-use File::Basename qw(basename dirname);
-
-our ($opt_h, $opt_n, $opt_s);
-getopts('hns');
-
-$opt_h && usage();
-
-sub usage {
-       print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < <log_data>\n";
-        exit(1);
-}
-
-my (%mailmap);
-my (%email);
-my (%map);
-my $pstate = 1;
-my $n_records = 0;
-my $n_output = 0;
-
-sub shortlog_entry($$) {
-       my ($name, $desc) = @_;
-       my $key = $name;
-
-       $desc =~ s#/pub/scm/linux/kernel/git/#/.../#g;
-       $desc =~ s#\[PATCH\] ##g;
-
-       # store description in array, in email->{desc list} map
-       if (exists $map{$key}) {
-               # grab ref
-               my $obj = $map{$key};
-
-               # add desc to array
-               push(@$obj, $desc);
-       } else {
-               # create new array, containing 1 item
-               my @arr = ($desc);
-
-               # store ref to array
-               $map{$key} = \@arr;
-       }
-}
-
-# sort comparison function
-sub by_name($$) {
-       my ($a, $b) = @_;
-
-       uc($a) cmp uc($b);
-}
-sub by_nbentries($$) {
-       my ($a, $b) = @_;
-       my $a_entries = $map{$a};
-       my $b_entries = $map{$b};
-
-       @$b_entries - @$a_entries || by_name $a, $b;
-}
-
-my $sort_method = $opt_n ? \&by_nbentries : \&by_name;
-
-sub summary_output {
-       my ($obj, $num, $key);
-
-       foreach $key (sort $sort_method keys %map) {
-               $obj = $map{$key};
-               $num = @$obj;
-               printf "%s: %u\n", $key, $num;
-               $n_output += $num;
-       }
-}
-
-sub shortlog_output {
-       my ($obj, $num, $key, $desc);
-
-       foreach $key (sort $sort_method keys %map) {
-               $obj = $map{$key};
-               $num = @$obj;
-
-               # output author
-               printf "%s (%u):\n", $key, $num;
-
-               # output author's 1-line summaries
-               foreach $desc (reverse @$obj) {
-                       print "  $desc\n";
-                       $n_output++;
-               }
-
-               # blank line separating author from next author
-               print "\n";
-       }
-}
-
-sub changelog_input {
-       my ($author, $desc);
-
-       while (<>) {
-               # get author and email
-               if ($pstate == 1) {
-                       my ($email);
-
-                       next unless /^[Aa]uthor:?\s*(.*?)\s*<(.*)>/;
-
-                       $n_records++;
-
-                       $author = $1;
-                       $email = $2;
-                       $desc = undef;
-
-                       # cset author fixups
-                       if (exists $mailmap{$email}) {
-                               $author = $mailmap{$email};
-                       } elsif (exists $mailmap{$author}) {
-                               $author = $mailmap{$author};
-                       } elsif (!$author) {
-                               $author = $email;
-                       }
-                       $email{$author}{$email}++;
-                       $pstate++;
-               }
-
-               # skip to blank line
-               elsif ($pstate == 2) {
-                       next unless /^\s*$/;
-                       $pstate++;
-               }
-
-               # skip to non-blank line
-               elsif ($pstate == 3) {
-                       next unless /^\s*?(.*)/;
-
-                       # skip lines that are obviously not
-                       # a 1-line cset description
-                       next if /^\s*From: /;
-
-                       chomp;
-                       $desc = $1;
-
-                       &shortlog_entry($author, $desc);
-
-                       $pstate = 1;
-               }
-       
-               else {
-                       die "invalid parse state $pstate";
-               }
-       }
-}
-
-sub read_mailmap {
-       my ($fh, $mailmap) = @_;
-       while (<$fh>) {
-               chomp;
-               if (/^([^#].*?)\s*<(.*)>/) {
-                       $mailmap->{$2} = $1;
-               }
-       }
-}
-
-sub setup_mailmap {
-       read_mailmap(\*DATA, \%mailmap);
-       if (-f '.mailmap') {
-               my $fh = undef;
-               open $fh, '<', '.mailmap';
-               read_mailmap($fh, \%mailmap);
-               close $fh;
-       }
-}
-
-sub finalize {
-       #print "\n$n_records records parsed.\n";
-
-       if ($n_records != $n_output) {
-               die "parse error: input records != output records\n";
-       }
-       if (0) {
-               for my $author (sort keys %email) {
-                       my $e = $email{$author};
-                       for my $email (sort keys %$e) {
-                               print STDERR "$author <$email>\n";
-                       }
-               }
-       }
-}
-
-&setup_mailmap;
-&changelog_input;
-$opt_s ? &summary_output : &shortlog_output;
-&finalize;
-exit(0);
-
-
-__DATA__
-#
-# Even with git, we don't always have name translations.
-# So have an email->real name table to translate the
-# (hopefully few) missing names
-#
-Adrian Bunk <bunk@stusta.de>
-Andreas Herrmann <aherrman@de.ibm.com>
-Andrew Morton <akpm@osdl.org>
-Andrew Vasquez <andrew.vasquez@qlogic.com>
-Christoph Hellwig <hch@lst.de>
-Corey Minyard <minyard@acm.org>
-David Woodhouse <dwmw2@shinybook.infradead.org>
-Domen Puncer <domen@coderock.org>
-Douglas Gilbert <dougg@torque.net>
-Ed L Cashin <ecashin@coraid.com>
-Evgeniy Polyakov <johnpol@2ka.mipt.ru>
-Felix Moeller <felix@derklecks.de>
-Frank Zago <fzago@systemfabricworks.com>
-Greg Kroah-Hartman <gregkh@suse.de>
-James Bottomley <jejb@mulgrave.(none)>
-James Bottomley <jejb@titanic.il.steeleye.com>
-Jeff Garzik <jgarzik@pretzel.yyz.us>
-Jens Axboe <axboe@suse.de>
-Kay Sievers <kay.sievers@vrfy.org>
-Mitesh shah <mshah@teja.com>
-Morten Welinder <terra@gnome.org>
-Morten Welinder <welinder@anemone.rentec.com>
-Morten Welinder <welinder@darter.rentec.com>
-Morten Welinder <welinder@troll.com>
-Nguyen Anh Quynh <aquynh@gmail.com>
-Paolo 'Blaisorblade' Giarrusso <blaisorblade@yahoo.it>
-Peter A Jonsson <pj@ludd.ltu.se>
-Ralf Wildenhues <Ralf.Wildenhues@gmx.de>
-Rudolf Marek <R.Marek@sh.cvut.cz>
-Rui Saraiva <rmps@joel.ist.utl.pt>
-Sachin P Sant <ssant@in.ibm.com>
-Santtu Hyrkk\e,Av\e(B <santtu.hyrkko@gmail.com>
-Simon Kelley <simon@thekelleys.org.uk>
-Tejun Heo <htejun@gmail.com>
-Tony Luck <tony.luck@intel.com>
index 80b7b87f0f4f1933e551439ca4fb2cd68981a029..d792a62d7ca400145d57189b5ca8af97978fef4d 100755 (executable)
@@ -21,44 +21,46 @@ $ENV{TZ} = 'UTC';
 $ENV{LC_ALL} = 'C';
 $| = 1; # unbuffer STDOUT
 
-# If SVN:: library support is added, please make the dependencies
-# optional and preserve the capability to use the command-line client.
-# use eval { require SVN::... } to make it lazy load
-# We don't use any modules not in the standard Perl distribution:
+# properties that we do not log:
+my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1,
+             'svn:special' => 1,
+             'svn:executable' => 1,
+             'svn:entry:committed-rev' => 1,
+             'svn:entry:last-author' => 1,
+             'svn:entry:uuid' => 1,
+             'svn:entry:committed-date' => 1,
+);
+
+sub fatal (@) { print STDERR @_; exit 1 }
+require SVN::Core; # use()-ing this causes segfaults for me... *shrug*
+require SVN::Ra;
+require SVN::Delta;
+if ($SVN::Core::VERSION lt '1.1.0') {
+       fatal "Need SVN::Core 1.1.0 or better (got $SVN::Core::VERSION)\n";
+}
+push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
+push @SVN::Git::Fetcher::ISA, 'SVN::Delta::Editor';
+*SVN::Git::Fetcher::process_rm = *process_rm;
 use Carp qw/croak/;
 use IO::File qw//;
 use File::Basename qw/dirname basename/;
 use File::Path qw/mkpath/;
 use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev pass_through/;
-use File::Spec qw//;
-use File::Copy qw/copy/;
 use POSIX qw/strftime/;
 use IPC::Open3;
 use Memoize;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
 memoize('revisions_eq');
 memoize('cmt_metadata');
 memoize('get_commit_time');
 
-my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
-
-sub nag_lib {
-       print STDERR <<EOF;
-! Please consider installing the SVN Perl libraries (version 1.1.0 or
-! newer).  You will generally get better performance and fewer bugs,
-! especially if you:
-! 1) have a case-insensitive filesystem
-! 2) replace symlinks with files (and vice-versa) in commits
-
-EOF
-}
-
-$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
-libsvn_load();
-nag_lib() unless $_use_lib;
+my ($SVN);
 
 my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
 my $sha1 = qr/[a-f\d]{40}/;
 my $sha1_short = qr/[a-f\d]{4,40}/;
+my $_esc_color = qr/(?:\033\[(?:(?:\d+;)*\d*)?m)*/;
 my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
        $_find_copies_harder, $_l, $_cp_similarity, $_cp_remote,
        $_repack, $_repack_nr, $_repack_flags, $_q,
@@ -66,9 +68,11 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
        $_template, $_shared, $_no_default_regex, $_no_graft_copy,
        $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
        $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
-       $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive);
+       $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive,
+       $_username, $_config_dir, $_no_auth_cache,
+       $_pager, $_color, $_prefix);
 my (@_branch_from, %tree_map, %users, %rusers, %equiv);
-my ($_svn_co_url_revs, $_svn_pg_peg_revs);
+my ($_svn_can_do_switch);
 my @repo_path_split_cache;
 
 my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
@@ -79,6 +83,9 @@ my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
                'repack:i' => \$_repack,
                'no-metadata' => \$_no_metadata,
                'quiet|q' => \$_q,
+               'username=s' => \$_username,
+               'config-dir=s' => \$_config_dir,
+               'no-auth-cache' => \$_no_auth_cache,
                'ignore-nodate' => \$_ignore_nodate,
                'repack-flags|repack-args|repack-opts=s' => \$_repack_flags);
 
@@ -95,12 +102,17 @@ my %cmt_opts = ( 'edit|e' => \$_edit,
 );
 
 my %cmd = (
-       fetch => [ \&fetch, "Download new revisions from SVN",
+       fetch => [ \&cmd_fetch, "Download new revisions from SVN",
                        { 'revision|r=s' => \$_revision, %fc_opts } ],
        init => [ \&init, "Initialize a repo for tracking" .
                          " (requires URL argument)",
                          \%init_opts ],
-       commit => [ \&commit, "Commit git revisions to SVN",
+       dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
+                       { 'merge|m|M' => \$_merge,
+                         'strategy|s=s' => \$_strategy,
+                         'dry-run|n' => \$_dry_run,
+                       %cmt_opts, %fc_opts } ],
+       'set-tree' => [ \&commit, "Set an SVN repository to a git tree-ish",
                        {       'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'show-ignore' => [ \&show_ignore, "Show svn:ignore listings",
                        { 'revision|r=i' => \$_revision } ],
@@ -117,7 +129,13 @@ my %cmd = (
                          'no-graft-copy' => \$_no_graft_copy } ],
        'multi-init' => [ \&multi_init,
                        'Initialize multiple trees (like git-svnimport)',
-                       { %multi_opts, %fc_opts } ],
+                       { %multi_opts, %init_opts,
+                        'revision|r=i' => \$_revision,
+                        'username=s' => \$_username,
+                        'config-dir=s' => \$_config_dir,
+                        'no-auth-cache' => \$_no_auth_cache,
+                        'prefix=s' => \$_prefix,
+                       } ],
        'multi-fetch' => [ \&multi_fetch,
                        'Fetch multiple trees (like git-svnimport)',
                        \%fc_opts ],
@@ -130,17 +148,14 @@ my %cmd = (
                          'show-commit' => \$_show_commit,
                          'non-recursive' => \$_non_recursive,
                          'authors-file|A=s' => \$_authors,
+                         'color' => \$_color,
+                         'pager=s' => \$_pager,
                        } ],
        'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
                        { 'message|m=s' => \$_message,
                          'file|F=s' => \$_file,
                          'revision|r=s' => \$_revision,
                        %cmt_opts } ],
-       dcommit => [ \&dcommit, 'Commit several diffs to merge with upstream',
-                       { 'merge|m|M' => \$_merge,
-                         'strategy|s=s' => \$_strategy,
-                         'dry-run|n' => \$_dry_run,
-                       %cmt_opts } ],
 );
 
 my $cmd;
@@ -167,7 +182,6 @@ usage(1) unless defined $cmd;
 init_vars();
 load_authors() if $_authors;
 load_all_refs() if $_branch_all_refs;
-svn_compat_check() unless $_use_lib;
 migration_check() unless $cmd =~ /^(?:init|rebuild|multi-init|commit-diff)$/;
 $cmd{$cmd}->[0]->(@ARGV);
 exit 0;
@@ -203,36 +217,35 @@ information.
 }
 
 sub version {
-       print "git-svn version $VERSION\n";
+       print "git-svn version $VERSION (svn $SVN::Core::VERSION)\n";
        exit 0;
 }
 
 sub rebuild {
-       if (quiet_run(qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0")) {
+       if (!verify_ref("refs/remotes/$GIT_SVN^0")) {
                copy_remote_ref();
        }
        $SVN_URL = shift or undef;
        my $newest_rev = 0;
        if ($_upgrade) {
-               sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
+               command_noisy('update-ref',"refs/remotes/$GIT_SVN","
+                             $GIT_SVN-HEAD");
        } else {
                check_upgrade_needed();
        }
 
-       my $pid = open(my $rev_list,'-|');
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
-       }
+       my ($rev_list, $ctx) = command_output_pipe("rev-list",
+                                                  "refs/remotes/$GIT_SVN");
        my $latest;
        while (<$rev_list>) {
                chomp;
                my $c = $_;
                croak "Non-SHA1: $c\n" unless $c =~ /^$sha1$/o;
-               my @commit = grep(/^git-svn-id: /,`git-cat-file commit $c`);
+               my @commit = grep(/^git-svn-id: /,
+                                 command(qw/cat-file commit/, $c));
                next if (!@commit); # skip merges
                my ($url, $rev, $uuid) = extract_metadata($commit[$#commit]);
-               if (!$rev || !$uuid) {
+               if (!defined $rev || !$uuid) {
                        croak "Unable to extract revision or UUID from ",
                                "$c, $commit[$#commit]\n";
                }
@@ -255,33 +268,7 @@ sub rebuild {
                print "r$rev = $c\n";
                $newest_rev = $rev if ($rev > $newest_rev);
        }
-       close $rev_list or croak $?;
-
-       goto out if $_use_lib;
-       if (!chdir $SVN_WC) {
-               svn_cmd_checkout($SVN_URL, $latest, $SVN_WC);
-               chdir $SVN_WC or croak $!;
-       }
-
-       $pid = fork;
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               my @svn_up = qw(svn up);
-               push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-               sys(@svn_up,"-r$newest_rev");
-               $ENV{GIT_INDEX_FILE} = $GIT_SVN_INDEX;
-               index_changes();
-               exec('git-write-tree') or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-out:
-       if ($_upgrade) {
-               print STDERR <<"";
-Keeping deprecated refs/head/$GIT_SVN-HEAD for now.  Please remove it
-when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
-
-       }
+       command_close_pipe($rev_list, $ctx);
 }
 
 sub init {
@@ -299,88 +286,32 @@ sub init {
 
        $SVN_URL = $url;
        unless (-d $GIT_DIR) {
-               my @init_db = ('git-init-db');
+               my @init_db = ('init');
                push @init_db, "--template=$_template" if defined $_template;
                push @init_db, "--shared" if defined $_shared;
-               sys(@init_db);
+               command_noisy(@init_db);
        }
        setup_git_svn();
 }
 
+sub cmd_fetch {
+       fetch_child_id($GIT_SVN, @_);
+}
+
 sub fetch {
        check_upgrade_needed();
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       my $ret = $_use_lib ? fetch_lib(@_) : fetch_cmd(@_);
-       if ($ret->{commit} && quiet_run(qw(git-rev-parse --verify
-                                               refs/heads/master^0))) {
-               sys(qw(git-update-ref refs/heads/master),$ret->{commit});
+       my $ret = fetch_lib(@_);
+       if ($ret->{commit} && !verify_ref('refs/heads/master^0')) {
+               command_noisy(qw(update-ref refs/heads/master),$ret->{commit});
        }
        return $ret;
 }
 
-sub fetch_cmd {
-       my (@parents) = @_;
-       my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
-       unless ($_revision) {
-               $_revision = -d $SVN_WC ? 'BASE:HEAD' : '0:HEAD';
-       }
-       push @log_args, "-r$_revision";
-       push @log_args, '--stop-on-copy' unless $_no_stop_copy;
-
-       my $svn_log = svn_log_raw(@log_args);
-
-       my $base = next_log_entry($svn_log) or croak "No base revision!\n";
-       # don't need last_revision from grab_base_rev() because
-       # user could've specified a different revision to skip (they
-       # didn't want to import certain revisions into git for whatever
-       # reason, so trust $base->{revision} instead.
-       my (undef, $last_commit) = svn_grab_base_rev();
-       unless (-d $SVN_WC) {
-               svn_cmd_checkout($SVN_URL,$base->{revision},$SVN_WC);
-               chdir $SVN_WC or croak $!;
-               read_uuid();
-               $last_commit = git_commit($base, @parents);
-               assert_tree($last_commit);
-       } else {
-               chdir $SVN_WC or croak $!;
-               read_uuid();
-               # looks like a user manually cp'd and svn switch'ed
-               unless ($last_commit) {
-                       sys(qw/svn revert -R ./);
-                       assert_svn_wc_clean($base->{revision});
-                       $last_commit = git_commit($base, @parents);
-                       assert_tree($last_commit);
-               }
-       }
-       my @svn_up = qw(svn up);
-       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-       my $last = $base;
-       while (my $log_msg = next_log_entry($svn_log)) {
-               if ($last->{revision} >= $log_msg->{revision}) {
-                       croak "Out of order: last >= current: ",
-                               "$last->{revision} >= $log_msg->{revision}\n";
-               }
-               # Revert is needed for cases like:
-               # https://svn.musicpd.org/Jamming/trunk (r166:167), but
-               # I can't seem to reproduce something like that on a test...
-               sys(qw/svn revert -R ./);
-               assert_svn_wc_clean($last->{revision});
-               sys(@svn_up,"-r$log_msg->{revision}");
-               $last_commit = git_commit($log_msg, $last_commit, @parents);
-               $last = $log_msg;
-       }
-       close $svn_log->{fh};
-       $last->{commit} = $last_commit;
-       return $last;
-}
-
 sub fetch_lib {
        my (@parents) = @_;
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
-       $SVN_LOG ||= libsvn_connect($repo);
-       $SVN ||= libsvn_connect($repo);
+       $SVN ||= libsvn_connect($SVN_URL);
        my ($last_rev, $last_commit) = svn_grab_base_rev();
        my ($base, $head) = libsvn_parse_revision($last_rev);
        if ($base > $head) {
@@ -395,16 +326,16 @@ sub fetch_lib {
        read_uuid();
        if (defined $last_commit) {
                unless (-e $GIT_SVN_INDEX) {
-                       sys(qw/git-read-tree/, $last_commit);
+                       command_noisy('read-tree', $last_commit);
                }
-               chomp (my $x = `git-write-tree`);
-               my ($y) = (`git-cat-file commit $last_commit`
+               my $x = command_oneline('write-tree');
+               my ($y) = (command(qw/cat-file commit/, $last_commit)
                                                        =~ /^tree ($sha1)/m);
                if ($y ne $x) {
                        unlink $GIT_SVN_INDEX or croak $!;
-                       sys(qw/git-read-tree/, $last_commit);
+                       command_noisy('read-tree', $last_commit);
                }
-               chomp ($x = `git-write-tree`);
+               $x = command_oneline('write-tree');
                if ($y ne $x) {
                        print STDERR "trees ($last_commit) $y != $x\n",
                                 "Something is seriously wrong...\n";
@@ -422,7 +353,7 @@ sub fetch_lib {
                        # performance sucks with it enabled, so it's much
                        # faster to fetch revision ranges instead of relying
                        # on the limiter.
-                       libsvn_get_log($SVN_LOG, '/'.$SVN_PATH,
+                       libsvn_get_log(libsvn_dup_ra($SVN), [''],
                                        $min, $max, 0, 1, 1,
                                sub {
                                        my $log_msg;
@@ -448,6 +379,7 @@ sub fetch_lib {
                $min = $max + 1;
                $max += $inc;
                $max = $head if ($max > $head);
+               $SVN = libsvn_connect($SVN_URL);
        }
        restore_index($index);
        return { revision => $last_rev, commit => $last_commit };
@@ -467,45 +399,19 @@ sub commit {
        }
        my @revs;
        foreach my $c (@commits) {
-               chomp(my @tmp = safe_qx('git-rev-parse',$c));
+               my @tmp = command('rev-parse',$c);
                if (scalar @tmp == 1) {
                        push @revs, $tmp[0];
                } elsif (scalar @tmp > 1) {
-                       push @revs, reverse (safe_qx('git-rev-list',@tmp));
+                       push @revs, reverse(command('rev-list',@tmp));
                } else {
                        die "Failed to rev-parse $c\n";
                }
        }
-       chomp @revs;
-       $_use_lib ? commit_lib(@revs) : commit_cmd(@revs);
+       commit_lib(@revs);
        print "Done committing ",scalar @revs," revisions to SVN\n";
 }
 
-sub commit_cmd {
-       my (@revs) = @_;
-
-       chdir $SVN_WC or croak "Unable to chdir $SVN_WC: $!\n";
-       my $info = svn_info('.');
-       my $fetched = fetch();
-       if ($info->{Revision} != $fetched->{revision}) {
-               print STDERR "There are new revisions that were fetched ",
-                               "and need to be merged (or acknowledged) ",
-                               "before committing.\n";
-               exit 1;
-       }
-       $info = svn_info('.');
-       read_uuid($info);
-       my $last = $fetched;
-       foreach my $c (@revs) {
-               my $mods = svn_checkout_tree($last, $c);
-               if (scalar @$mods == 0) {
-                       print "Skipping, no changes detected\n";
-                       next;
-               }
-               $last = svn_commit_tree($last, $c);
-       }
-}
-
 sub commit_lib {
        my (@revs) = @_;
        my ($r_last, $cmt_last) = svn_grab_base_rev();
@@ -524,7 +430,6 @@ sub commit_lib {
        my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
 
        my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
        set_svn_commit_env();
        foreach my $c (@revs) {
                my $log_msg = get_commit_message($c, $commit_msg);
@@ -533,13 +438,11 @@ sub commit_lib {
                # can't track down... (it's probably in the SVN code)
                defined(my $pid = open my $fh, '-|') or croak $!;
                if (!$pid) {
-                       $SVN_LOG = libsvn_connect($repo);
-                       $SVN = libsvn_connect($repo);
                        my $ed = SVN::Git::Editor->new(
                                        {       r => $r_last,
-                                               ra => $SVN_LOG,
+                                               ra => libsvn_dup_ra($SVN),
                                                c => $c,
-                                               svn_path => $SVN_PATH
+                                               svn_path => $SVN->{svn_path},
                                        },
                                        $SVN->get_commit_editor(
                                                $log_msg->{msg},
@@ -571,7 +474,7 @@ sub commit_lib {
                                $no = 1;
                        }
                }
-               close $fh or croak $?;
+               close $fh or exit 1;
                if (! defined $r_new && ! defined $cmt_new) {
                        unless ($no) {
                                die "Failed to parse revision information\n";
@@ -585,10 +488,18 @@ sub commit_lib {
 }
 
 sub dcommit {
+       my $head = shift || 'HEAD';
        my $gs = "refs/remotes/$GIT_SVN";
-       chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD"));
+       my @refs = command(qw/rev-list --no-merges/, "$gs..$head");
        my $last_rev;
        foreach my $d (reverse @refs) {
+               if (!verify_ref("$d~1")) {
+                       die "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";
+               }
                unless (defined $last_rev) {
                        (undef, $last_rev, undef) = cmt_metadata("$d~1");
                        unless (defined $last_rev) {
@@ -606,7 +517,7 @@ sub dcommit {
        }
        return if $_dry_run;
        fetch();
-       my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs);
+       my @diff = command('diff-tree', 'HEAD', $gs, '--');
        my @finish;
        if (@diff) {
                @finish = qw/rebase/;
@@ -615,45 +526,18 @@ sub dcommit {
                print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff;
        } else {
                print "No changes between current HEAD and $gs\n",
-                     "Hard resetting to the latest $gs\n";
-               @finish = qw/reset --hard/;
+                     "Resetting to the latest $gs\n";
+               @finish = qw/reset --mixed/;
        }
-       sys('git', @finish, $gs);
+       command_noisy(@finish, $gs);
 }
 
 sub show_ignore {
        $SVN_URL ||= file_to_s("$GIT_SVN_DIR/info/url");
-       $_use_lib ? show_ignore_lib() : show_ignore_cmd();
-}
-
-sub show_ignore_cmd {
-       require File::Find or die $!;
-       if (defined $_revision) {
-               die "-r/--revision option doesn't work unless the Perl SVN ",
-                       "libraries are used\n";
-       }
-       chdir $SVN_WC or croak $!;
-       my %ign;
-       File::Find::find({wanted=>sub{if(lstat $_ && -d _ && -d "$_/.svn"){
-               s#^\./##;
-               @{$ign{$_}} = svn_propget_base('svn:ignore', $_);
-               }}, no_chdir=>1},'.');
-
-       print "\n# /\n";
-       foreach (@{$ign{'.'}}) { print '/',$_ if /\S/ }
-       delete $ign{'.'};
-       foreach my $i (sort keys %ign) {
-               print "\n# ",$i,"\n";
-               foreach (@{$ign{$i}}) { print '/',$i,'/',$_ if /\S/ }
-       }
-}
-
-sub show_ignore_lib {
        my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
-       $SVN ||= libsvn_connect($repo);
+       $SVN ||= libsvn_connect($SVN_URL);
        my $r = defined $_revision ? $_revision : $SVN->get_latest_revnum;
-       libsvn_traverse_ignore(\*STDOUT, $SVN_PATH, $r);
+       libsvn_traverse_ignore(\*STDOUT, '', $r);
 }
 
 sub graft_branches {
@@ -663,7 +547,7 @@ sub graft_branches {
 
        if (%$grafts) {
                # temporarily disable our grafts file to make this idempotent
-               chomp($gr_sha1 = safe_qx(qw/git-hash-object -w/,$gr_file));
+               chomp($gr_sha1 = command(qw/hash-object -w/,$gr_file));
                rename $gr_file, "$gr_file~$gr_sha1" or croak $!;
        }
 
@@ -681,11 +565,7 @@ sub graft_branches {
                        }
                }
                unless ($_no_graft_copy) {
-                       if ($_use_lib) {
-                               graft_file_copy_lib($grafts,$l_map,$u);
-                       } else {
-                               graft_file_copy_cmd($grafts,$l_map,$u);
-                       }
+                       graft_file_copy_lib($grafts,$l_map,$u);
                }
        }
        graft_tree_joins($grafts);
@@ -696,31 +576,29 @@ sub graft_branches {
 
 sub multi_init {
        my $url = shift;
-       $_trunk ||= 'trunk';
-       $_trunk =~ s#/+$##;
-       $url =~ s#/+$## if $url;
-       if ($_trunk !~ m#^[a-z\+]+://#) {
-               $_trunk = '/' . $_trunk if ($_trunk !~ m#^/#);
-               unless ($url) {
-                       print STDERR "E: '$_trunk' is not a complete URL ",
-                               "and a separate URL is not specified\n";
-                       exit 1;
-               }
-               $_trunk = $url . $_trunk;
+       unless (defined $_trunk || defined $_branches || defined $_tags) {
+               usage(1);
        }
-       my $ch_id;
-       if ($GIT_SVN eq 'git-svn') {
-               $ch_id = 1;
-               $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
-       }
-       init_vars();
-       unless (-d $GIT_SVN_DIR) {
-               print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id;
-               init($_trunk);
-               sys('git-repo-config', 'svn.trunk', $_trunk);
+       if (defined $_trunk) {
+               my $trunk_url = complete_svn_url($url, $_trunk);
+               my $ch_id;
+               if ($GIT_SVN eq 'git-svn') {
+                       $ch_id = 1;
+                       $GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
+               }
+               init_vars();
+               unless (-d $GIT_SVN_DIR) {
+                       if ($ch_id) {
+                               print "GIT_SVN_ID set to 'trunk' for ",
+                                     "$trunk_url ($_trunk)\n";
+                       }
+                       init($trunk_url);
+                       command_noisy('config', 'svn.trunk', $trunk_url);
+               }
        }
-       complete_url_ls_init($url, $_branches, '--branches/-b', '');
-       complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
+       $_prefix = '' unless defined $_prefix;
+       complete_url_ls_init($url, $_branches, '--branches/-b', $_prefix);
+       complete_url_ls_init($url, $_tags, '--tags/-t', $_prefix . 'tags/');
 }
 
 sub multi_fetch {
@@ -754,16 +632,14 @@ sub show_log {
                }
        }
 
-       my $pid = open(my $log,'-|');
-       defined $pid or croak $!;
-       if (!$pid) {
-               exec(git_svn_log_cmd($r_min,$r_max), @args) or croak $!;
-       }
-       setup_pager();
+       config_pager();
+       @args = (git_svn_log_cmd($r_min, $r_max), @args);
+       my $log = command_output_pipe(@args);
+       run_pager();
        my (@k, $c, $d);
 
        while (<$log>) {
-               if (/^commit ($sha1_short)/o) {
+               if (/^${_esc_color}commit ($sha1_short)/o) {
                        my $cmt = $1;
                        if ($c && cmt_showable($c) && $c->{r} != $r_last) {
                                $r_last = $c->{r};
@@ -772,25 +648,25 @@ sub show_log {
                        }
                        $d = undef;
                        $c = { c => $cmt };
-               } elsif (/^author (.+) (\d+) ([\-\+]?\d+)$/) {
+               } elsif (/^${_esc_color}author (.+) (\d+) ([\-\+]?\d+)$/) {
                        get_author_info($c, $1, $2, $3);
-               } elsif (/^(?:tree|parent|committer) /) {
+               } elsif (/^${_esc_color}(?:tree|parent|committer) /) {
                        # ignore
-               } elsif (/^:\d{6} \d{6} $sha1_short/o) {
+               } elsif (/^${_esc_color}:\d{6} \d{6} $sha1_short/o) {
                        push @{$c->{raw}}, $_;
-               } elsif (/^[ACRMDT]\t/) {
-                       # we could add $SVN_PATH here, but that requires
+               } elsif (/^${_esc_color}[ACRMDT]\t/) {
+                       # we could add $SVN->{svn_path} here, but that requires
                        # remote access at the moment (repo_path_split)...
-                       s#^([ACRMDT])\t#   $1 #;
+                       s#^(${_esc_color})([ACRMDT])\t#$1   $2 #;
                        push @{$c->{changed}}, $_;
-               } elsif (/^diff /) {
+               } elsif (/^${_esc_color}diff /) {
                        $d = 1;
                        push @{$c->{diff}}, $_;
                } elsif ($d) {
                        push @{$c->{diff}}, $_;
-               } elsif (/^    (git-svn-id:.+)$/) {
+               } elsif (/^${_esc_color}    (git-svn-id:.+)$/) {
                        ($c->{url}, $c->{r}, undef) = extract_metadata($1);
-               } elsif (s/^    //) {
+               } elsif (s/^${_esc_color}    //) {
                        push @{$c->{l}}, $_;
                }
        }
@@ -815,18 +691,20 @@ sub commit_diff_usage {
 }
 
 sub commit_diff {
-       if (!$_use_lib) {
-               print STDERR "commit-diff must be used with SVN libraries\n";
-               exit 1;
-       }
        my $ta = shift or commit_diff_usage();
        my $tb = shift or commit_diff_usage();
        if (!eval { $SVN_URL = shift || file_to_s("$GIT_SVN_DIR/info/url") }) {
                print STDERR "Needed URL or usable git-svn id command-line\n";
                commit_diff_usage();
        }
-       my $r = shift || $_revision;
-       die "-r|--revision is a required argument\n" unless (defined $r);
+       my $r = shift;
+       unless (defined $r) {
+               if (defined $_revision) {
+                       $r = $_revision
+               } else {
+                       die "-r|--revision is a required argument\n";
+               }
+       }
        if (defined $_message && defined $_file) {
                print STDERR "Both --message/-m and --file/-F specified ",
                                "for the commit message.\n",
@@ -839,10 +717,7 @@ sub commit_diff {
                $_message ||= get_commit_message($tb,
                                        "$GIT_DIR/.svn-commit.tmp.$$")->{msg};
        }
-       my $repo;
-       ($repo, $SVN_PATH) = repo_path_split($SVN_URL);
-       $SVN_LOG ||= libsvn_connect($repo);
-       $SVN ||= libsvn_connect($repo);
+       $SVN ||= libsvn_connect($SVN_URL);
        if ($r eq 'HEAD') {
                $r = $SVN->get_latest_revnum;
        } elsif ($r !~ /^\d+$/) {
@@ -851,8 +726,9 @@ sub commit_diff {
        my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
        my $rev_committed;
        my $ed = SVN::Git::Editor->new({        r => $r,
-                                               ra => $SVN_LOG, c => $tb,
-                                               svn_path => $SVN_PATH
+                                               ra => libsvn_dup_ra($SVN),
+                                               c => $tb,
+                                               svn_path => $SVN->{svn_path}
                                        },
                                $SVN->get_commit_editor($_message,
                                        sub {
@@ -860,13 +736,16 @@ sub commit_diff {
                                                print "Committed $_[0]\n";
                                        }, @lock)
                                );
-       my $mods = libsvn_checkout_tree($ta, $tb, $ed);
-       if (@$mods == 0) {
-               print "No changes\n$ta == $tb\n";
-               $ed->abort_edit;
-       } else {
-               $ed->close_edit;
-       }
+       eval {
+               my $mods = libsvn_checkout_tree($ta, $tb, $ed);
+               if (@$mods == 0) {
+                       print "No changes\n$ta == $tb\n";
+                       $ed->abort_edit;
+               } else {
+                       $ed->close_edit;
+               }
+       };
+       fatal "$@\n" if $@;
        $_message = $_file = undef;
        return $rev_committed;
 }
@@ -878,7 +757,7 @@ sub cmt_showable {
        return 1 if defined $c->{r};
        if ($c->{l} && $c->{l}->[-1] eq "...\n" &&
                                $c->{a_raw} =~ /\@([a-f\d\-]+)>$/) {
-               my @msg = safe_qx(qw/git-cat-file commit/, $c->{c});
+               my @msg = command(qw/cat-file commit/, $c->{c});
                shift @msg while ($msg[0] ne "\n");
                shift @msg;
                @{$c->{l}} = grep !/^git-svn-id: /, @msg;
@@ -889,12 +768,49 @@ sub cmt_showable {
        return defined $c->{r};
 }
 
+sub log_use_color {
+       return 1 if $_color;
+       my ($dc, $dcvar);
+       $dcvar = 'color.diff';
+       $dc = `git-config --get $dcvar`;
+       if ($dc eq '') {
+               # nothing at all; fallback to "diff.color"
+               $dcvar = 'diff.color';
+               $dc = `git-config --get $dcvar`;
+       }
+       chomp($dc);
+       if ($dc eq 'auto') {
+               my $pc;
+               $pc = `git-config --get color.pager`;
+               if ($pc eq '') {
+                       # does not have it -- fallback to pager.color
+                       $pc = `git-config --bool --get pager.color`;
+               }
+               else {
+                       $pc = `git-config --bool --get color.pager`;
+                       if ($?) {
+                               $pc = 'false';
+                       }
+               }
+               chomp($pc);
+               if (-t *STDOUT || (defined $_pager && $pc eq 'true')) {
+                       return ($ENV{TERM} && $ENV{TERM} ne 'dumb');
+               }
+               return 0;
+       }
+       return 0 if $dc eq 'never';
+       return 1 if $dc eq 'always';
+       chomp($dc = `git-config --bool --get $dcvar`);
+       return ($dc eq 'true');
+}
+
 sub git_svn_log_cmd {
        my ($r_min, $r_max) = @_;
-       my @cmd = (qw/git-log --abbrev-commit --pretty=raw
+       my @cmd = (qw/log --abbrev-commit --pretty=raw
                        --default/, "refs/remotes/$GIT_SVN");
        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, '--max-count=1';
@@ -926,7 +842,6 @@ sub fetch_child_id {
        my $ref = "$GIT_DIR/refs/remotes/$id";
        defined(my $pid = open my $fh, '-|') or croak $!;
        if (!$pid) {
-               $_repack = undef;
                $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
                init_vars();
                fetch(@_);
@@ -934,7 +849,7 @@ sub fetch_child_id {
        }
        while (<$fh>) {
                print $_;
-               check_repack() if (/^r\d+ = $sha1/);
+               check_repack() if (/^r\d+ = $sha1/o);
        }
        close $fh or croak $?;
 }
@@ -959,30 +874,34 @@ sub rec_fetch {
        }
 }
 
+sub complete_svn_url {
+       my ($url, $path) = @_;
+       $path =~ s#/+$##;
+       $url =~ s#/+$## if $url;
+       if ($path !~ m#^[a-z\+]+://#) {
+               $path = '/' . $path if ($path !~ m#^/#);
+               if (!defined $url || $url !~ m#^[a-z\+]+://#) {
+                       fatal("E: '$path' is not a complete URL ",
+                             "and a separate URL is not specified\n");
+               }
+               $path = $url . $path;
+       }
+       return $path;
+}
+
 sub complete_url_ls_init {
-       my ($url, $var, $switch, $pfx) = @_;
-       unless ($var) {
+       my ($url, $path, $switch, $pfx) = @_;
+       unless ($path) {
                print STDERR "W: $switch not specified\n";
                return;
        }
-       $var =~ s#/+$##;
-       if ($var !~ m#^[a-z\+]+://#) {
-               $var = '/' . $var if ($var !~ m#^/#);
-               unless ($url) {
-                       print STDERR "E: '$var' is not a complete URL ",
-                               "and a separate URL is not specified\n";
-                       exit 1;
-               }
-               $var = $url . $var;
-       }
-       chomp(my @ls = $_use_lib ? libsvn_ls_fullurl($var)
-                               : safe_qx(qw/svn ls --non-interactive/, $var));
-       my $old = $GIT_SVN;
+       my $full_url = complete_svn_url($url, $path);
+       my @ls = libsvn_ls_fullurl($full_url);
        defined(my $pid = fork) or croak $!;
        if (!$pid) {
-               foreach my $u (map { "$var/$_" } (grep m!/$!, @ls)) {
+               foreach my $u (map { "$full_url/$_" } (grep m!/$!, @ls)) {
                        $u =~ s#/+$##;
-                       if ($u !~ m!\Q$var\E/(.+)$!) {
+                       if ($u !~ m!\Q$full_url\E/(.+)$!) {
                                print STDERR "W: Unrecognized URL: $u\n";
                                die "This should never happen\n";
                        }
@@ -1000,7 +919,7 @@ sub complete_url_ls_init {
        waitpid $pid, 0;
        croak $? if $?;
        my ($n) = ($switch =~ /^--(\w+)/);
-       sys('git-repo-config', "svn.$n", $var);
+       command_noisy('config', "svn.$n", $full_url);
 }
 
 sub common_prefix {
@@ -1032,11 +951,8 @@ sub graft_tree_joins {
 
        git_svn_each(sub {
                my $i = shift;
-               defined(my $pid = open my $fh, '-|') or croak $!;
-               if (!$pid) {
-                       exec qw/git-rev-list --pretty=raw/,
-                                       "refs/remotes/$i" or croak $!;
-               }
+               my @args = (qw/rev-list --pretty=raw/, "refs/remotes/$i");
+               my ($fh, $ctx) = command_output_pipe(@args);
                while (<$fh>) {
                        next unless /^commit ($sha1)$/o;
                        my $c = $1;
@@ -1059,9 +975,7 @@ sub graft_tree_joins {
 
                        foreach my $p (@{$tree_map{$t}}) {
                                next if $p eq $c;
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $c, $p)
-                               };
+                               my $mb = eval { command('merge-base', $c, $p) };
                                next unless ($@ || $?);
                                if (defined $r_a) {
                                        # see if SVN says it's a relative
@@ -1090,48 +1004,16 @@ sub graft_tree_joins {
                                # what should we do when $ct == $s ?
                        }
                }
-               close $fh or croak $?;
+               command_close_pipe($fh, $ctx);
        });
 }
 
-# this isn't funky-filename safe, but good enough for now...
-sub graft_file_copy_cmd {
-       my ($grafts, $l_map, $u) = @_;
-       my $paths = $l_map->{$u};
-       my $pfx = common_prefix([keys %$paths]);
-       $SVN_URL ||= $u.$pfx;
-       my $pid = open my $fh, '-|';
-       defined $pid or croak $!;
-       unless ($pid) {
-               my @exec = qw/svn log -v/;
-               push @exec, "-r$_revision" if defined $_revision;
-               exec @exec, $u.$pfx or croak $!;
-       }
-       my ($r, $mp) = (undef, undef);
-       while (<$fh>) {
-               chomp;
-               if (/^\-{72}$/) {
-                       $mp = $r = undef;
-               } elsif (/^r(\d+) \| /) {
-                       $r = $1 unless defined $r;
-               } elsif (/^Changed paths:/) {
-                       $mp = 1;
-               } elsif ($mp && m#^   [AR] /(\S.*?) \(from /(\S+?):(\d+)\)$#) {
-                       my ($p1, $p0, $r0) = ($1, $2, $3);
-                       my $c = find_graft_path_commit($paths, $p1, $r);
-                       next unless $c;
-                       find_graft_path_parents($grafts, $paths, $c, $p0, $r0);
-               }
-       }
-}
-
 sub graft_file_copy_lib {
        my ($grafts, $l_map, $u) = @_;
        my $tree_paths = $l_map->{$u};
        my $pfx = common_prefix([keys %$tree_paths]);
        my ($repo, $path) = repo_path_split($u.$pfx);
-       $SVN_LOG ||= libsvn_connect($repo);
-       $SVN ||= libsvn_connect($repo);
+       $SVN = libsvn_connect($repo);
 
        my ($base, $head) = libsvn_parse_revision();
        my $inc = 1000;
@@ -1140,7 +1022,8 @@ sub graft_file_copy_lib {
        $SVN::Error::handler = \&libsvn_skip_unknown_revs;
        while (1) {
                my $pool = SVN::Pool->new;
-               libsvn_get_log($SVN_LOG, "/$path", $min, $max, 0, 1, 1,
+               libsvn_get_log(libsvn_dup_ra($SVN), [$path],
+                              $min, $max, 0, 2, 1,
                        sub {
                                libsvn_graft_file_copies($grafts, $tree_paths,
                                                        $path, @_);
@@ -1203,7 +1086,7 @@ sub graft_merge_msg {
        my ($grafts, $l_map, $u, $p, @re) = @_;
 
        my $x = $l_map->{$u}->{$p};
-       my $rl = rev_list_raw($x);
+       my $rl = rev_list_raw("refs/remotes/$x");
        while (my $c = next_rev_list_entry($rl)) {
                foreach my $re (@re) {
                        my (@br) = ($c->{m} =~ /$re/g);
@@ -1215,28 +1098,15 @@ sub graft_merge_msg {
 
 sub read_uuid {
        return if $SVN_UUID;
-       if ($_use_lib) {
-               my $pool = SVN::Pool->new;
-               $SVN_UUID = $SVN->get_uuid($pool);
-               $pool->clear;
-       } else {
-               my $info = shift || svn_info('.');
-               $SVN_UUID = $info->{'Repository UUID'} or
-                                       croak "Repository UUID unreadable\n";
-       }
+       my $pool = SVN::Pool->new;
+       $SVN_UUID = $SVN->get_uuid($pool);
+       $pool->clear;
 }
 
-sub quiet_run {
-       my $pid = fork;
-       defined $pid or croak $!;
-       if (!$pid) {
-               open my $null, '>', '/dev/null' or croak $!;
-               open STDERR, '>&', $null or croak $!;
-               open STDOUT, '>&', $null or croak $!;
-               exec @_ or croak $!;
-       }
-       waitpid $pid, 0;
-       return $?;
+sub verify_ref {
+       my ($ref) = @_;
+       eval { command_oneline([ 'rev-parse', '--verify', $ref ],
+                              { STDERR => 0 }); };
 }
 
 sub repo_path_split {
@@ -1250,25 +1120,8 @@ sub repo_path_split {
                        return ($u, $full_url);
                }
        }
-
-       if ($_use_lib) {
-               my $tmp = libsvn_connect($full_url);
-               my $url = $tmp->get_repos_root;
-               $full_url =~ s#^\Q$url\E/*##;
-               push @repo_path_split_cache, qr/^(\Q$url\E)/;
-               return ($url, $full_url);
-       } else {
-               my ($url, $path) = ($full_url =~ m!^([a-z\+]+://[^/]*)(.*)$!i);
-               $path =~ s#^/+##;
-               my @paths = split(m#/+#, $path);
-               while (quiet_run(qw/svn ls --non-interactive/, $url)) {
-                       my $n = shift @paths || last;
-                       $url .= "/$n";
-               }
-               push @repo_path_split_cache, qr/^(\Q$url\E)/;
-               $path = join('/',@paths);
-               return ($url, $path);
-       }
+       my $tmp = libsvn_connect($full_url);
+       return ($tmp->{repos_root}, $tmp->{svn_path});
 }
 
 sub setup_git_svn {
@@ -1284,41 +1137,17 @@ sub setup_git_svn {
 
 }
 
-sub assert_svn_wc_clean {
-       return if $_use_lib;
-       my ($svn_rev) = @_;
-       croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
-       my $lcr = svn_info('.')->{'Last Changed Rev'};
-       if ($svn_rev != $lcr) {
-               print STDERR "Checking for copy-tree ... ";
-               my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
-                                               "-r$lcr:$svn_rev")));
-               if (@diff) {
-                       croak "Nope!  Expected r$svn_rev, got r$lcr\n";
-               } else {
-                       print STDERR "OK!\n";
-               }
-       }
-       my @status = grep(!/^Performing status on external/,(`svn status`));
-       @status = grep(!/^\s*$/,@status);
-       @status = grep(!/^X/,@status) if $_no_ignore_ext;
-       if (scalar @status) {
-               print STDERR "Tree ($SVN_WC) is not clean:\n";
-               print STDERR $_ foreach @status;
-               croak;
-       }
-}
-
 sub get_tree_from_treeish {
        my ($treeish) = @_;
        croak "Not a sha1: $treeish\n" unless $treeish =~ /^$sha1$/o;
-       chomp(my $type = `git-cat-file -t $treeish`);
+       my $type = command_oneline(qw/cat-file -t/, $treeish);
        my $expected;
        while ($type eq 'tag') {
-               chomp(($treeish, $type) = `git-cat-file tag $treeish`);
+               ($treeish, $type) = command(qw/cat-file tag/, $treeish);
        }
        if ($type eq 'commit') {
-               $expected = (grep /^tree /,`git-cat-file commit $treeish`)[0];
+               $expected = (grep /^tree /, command(qw/cat-file commit/,
+                                                   $treeish))[0];
                ($expected) = ($expected =~ /^tree ($sha1)$/);
                die "Unable to get tree from $treeish\n" unless $expected;
        } elsif ($type eq 'tree') {
@@ -1329,27 +1158,19 @@ sub get_tree_from_treeish {
        return $expected;
 }
 
-sub assert_tree {
-       return if $_use_lib;
-       my ($treeish) = @_;
-       my $expected = get_tree_from_treeish($treeish);
-
-       my $tmpindex = $GIT_SVN_INDEX.'.assert-tmp';
-       if (-e $tmpindex) {
-               unlink $tmpindex or croak $!;
-       }
-       my $old_index = set_index($tmpindex);
-       index_changes(1);
-       chomp(my $tree = `git-write-tree`);
-       restore_index($old_index);
-       if ($tree ne $expected) {
-               croak "Tree mismatch, Got: $tree, Expected: $expected\n";
+sub get_diff {
+       my ($from, $treeish) = @_;
+       print "diff-tree $from $treeish\n";
+       my @diff_tree = qw(diff-tree -z -r);
+       if ($_cp_similarity) {
+               push @diff_tree, "-C$_cp_similarity";
+       } else {
+               push @diff_tree, '-C';
        }
-       unlink $tmpindex;
-}
-
-sub parse_diff_tree {
-       my $diff_fh = shift;
+       push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
+       push @diff_tree, "-l$_l" if defined $_l;
+       push @diff_tree, $from, $treeish;
+       my ($diff_fh, $ctx) = command_output_pipe(@diff_tree);
        local $/ = "\0";
        my $state = 'meta';
        my @mods;
@@ -1385,170 +1206,10 @@ sub parse_diff_tree {
                        croak "Error parsing $_\n";
                }
        }
-       close $diff_fh or croak $?;
-
+       command_close_pipe($diff_fh, $ctx);
        return \@mods;
 }
 
-sub svn_check_prop_executable {
-       my $m = shift;
-       return if -l $m->{file_b};
-       if ($m->{mode_b} =~ /755$/) {
-               chmod((0755 &~ umask),$m->{file_b}) or croak $!;
-               if ($m->{mode_a} !~ /755$/) {
-                       sys(qw(svn propset svn:executable 1), $m->{file_b});
-               }
-               -x $m->{file_b} or croak "$m->{file_b} is not executable!\n";
-       } elsif ($m->{mode_b} !~ /755$/ && $m->{mode_a} =~ /755$/) {
-               sys(qw(svn propdel svn:executable), $m->{file_b});
-               chmod((0644 &~ umask),$m->{file_b}) or croak $!;
-               -x $m->{file_b} and croak "$m->{file_b} is executable!\n";
-       }
-}
-
-sub svn_ensure_parent_path {
-       my $dir_b = dirname(shift);
-       svn_ensure_parent_path($dir_b) if ($dir_b ne File::Spec->curdir);
-       mkpath([$dir_b]) unless (-d $dir_b);
-       sys(qw(svn add -N), $dir_b) unless (-d "$dir_b/.svn");
-}
-
-sub precommit_check {
-       my $mods = shift;
-       my (%rm_file, %rmdir_check, %added_check);
-
-       my %o = ( D => 0, R => 1, C => 2, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               if ($m->{chg} eq 'R') {
-                       if (-d $m->{file_b}) {
-                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
-                       }
-                       # dir/$file => dir/file/$file
-                       my $dirname = dirname($m->{file_b});
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($dirname ne $m->{file_a}) {
-                                       $dirname = dirname($dirname);
-                                       next;
-                               }
-                               err_file_to_dir("$m->{file_a} => $m->{file_b}");
-                       }
-                       # baz/zzz => baz (baz is a file)
-                       $dirname = dirname($m->{file_a});
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($dirname ne $m->{file_b}) {
-                                       $dirname = dirname($dirname);
-                                       next;
-                               }
-                               err_dir_to_file("$m->{file_a} => $m->{file_b}");
-                       }
-               }
-               if ($m->{chg} =~ /^(D|R)$/) {
-                       my $t = $1 eq 'D' ? 'file_b' : 'file_a';
-                       $rm_file{ $m->{$t} } = 1;
-                       my $dirname = dirname( $m->{$t} );
-                       my $basename = basename( $m->{$t} );
-                       $rmdir_check{$dirname}->{$basename} = 1;
-               } elsif ($m->{chg} =~ /^(?:A|C)$/) {
-                       if (-d $m->{file_b}) {
-                               err_dir_to_file($m->{file_b});
-                       }
-                       my $dirname = dirname( $m->{file_b} );
-                       my $basename = basename( $m->{file_b} );
-                       $added_check{$dirname}->{$basename} = 1;
-                       while ($dirname ne File::Spec->curdir) {
-                               if ($rm_file{$dirname}) {
-                                       err_file_to_dir($m->{file_b});
-                               }
-                               $dirname = dirname $dirname;
-                       }
-               }
-       }
-       return (\%rmdir_check, \%added_check);
-
-       sub err_dir_to_file {
-               my $file = shift;
-               print STDERR "Node change from directory to file ",
-                               "is not supported by Subversion: ",$file,"\n";
-               exit 1;
-       }
-       sub err_file_to_dir {
-               my $file = shift;
-               print STDERR "Node change from file to directory ",
-                               "is not supported by Subversion: ",$file,"\n";
-               exit 1;
-       }
-}
-
-
-sub get_diff {
-       my ($from, $treeish) = @_;
-       assert_tree($from);
-       print "diff-tree $from $treeish\n";
-       my $pid = open my $diff_fh, '-|';
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               my @diff_tree = qw(git-diff-tree -z -r);
-               if ($_cp_similarity) {
-                       push @diff_tree, "-C$_cp_similarity";
-               } else {
-                       push @diff_tree, '-C';
-               }
-               push @diff_tree, '--find-copies-harder' if $_find_copies_harder;
-               push @diff_tree, "-l$_l" if defined $_l;
-               exec(@diff_tree, $from, $treeish) or croak $!;
-       }
-       return parse_diff_tree($diff_fh);
-}
-
-sub svn_checkout_tree {
-       my ($from, $treeish) = @_;
-       my $mods = get_diff($from->{commit}, $treeish);
-       return $mods unless (scalar @$mods);
-       my ($rm, $add) = precommit_check($mods);
-
-       my %o = ( D => 1, R => 0, C => -1, A => 3, M => 3, T => 3 );
-       foreach my $m (sort { $o{$a->{chg}} <=> $o{$b->{chg}} } @$mods) {
-               if ($m->{chg} eq 'C') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       sys(qw(svn cp),         $m->{file_a}, $m->{file_b});
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'D') {
-                       sys(qw(svn rm --force), $m->{file_b});
-               } elsif ($m->{chg} eq 'R') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       sys(qw(svn mv --force), $m->{file_a}, $m->{file_b});
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'M') {
-                       apply_mod_line_blob($m);
-                       svn_check_prop_executable($m);
-               } elsif ($m->{chg} eq 'T') {
-                       svn_check_prop_executable($m);
-                       apply_mod_line_blob($m);
-                       if ($m->{mode_a} =~ /^120/ && $m->{mode_b} !~ /^120/) {
-                               sys(qw(svn propdel svn:special), $m->{file_b});
-                       } else {
-                               sys(qw(svn propset svn:special *),$m->{file_b});
-                       }
-               } elsif ($m->{chg} eq 'A') {
-                       svn_ensure_parent_path( $m->{file_b} );
-                       apply_mod_line_blob($m);
-                       sys(qw(svn add), $m->{file_b});
-                       svn_check_prop_executable($m);
-               } else {
-                       croak "Invalid chg: $m->{chg}\n";
-               }
-       }
-
-       assert_tree($treeish);
-       if ($_rmdir) { # remove empty directories
-               handle_rmdir($rm, $add);
-       }
-       assert_tree($treeish);
-       return $mods;
-}
-
 sub libsvn_checkout_tree {
        my ($from, $treeish, $ed) = @_;
        my $mods = get_diff($from, $treeish);
@@ -1566,57 +1227,15 @@ sub libsvn_checkout_tree {
        return $mods;
 }
 
-# svn ls doesn't work with respect to the current working tree, but what's
-# in the repository.  There's not even an option for it... *sigh*
-# (added files don't show up and removed files remain in the ls listing)
-sub svn_ls_current {
-       my ($dir, $rm, $add) = @_;
-       chomp(my @ls = safe_qx('svn','ls',$dir));
-       my @ret = ();
-       foreach (@ls) {
-               s#/$##; # trailing slashes are evil
-               push @ret, $_ unless $rm->{$dir}->{$_};
-       }
-       if (exists $add->{$dir}) {
-               push @ret, keys %{$add->{$dir}};
-       }
-       return \@ret;
-}
-
-sub handle_rmdir {
-       my ($rm, $add) = @_;
-
-       foreach my $dir (sort {length $b <=> length $a} keys %$rm) {
-               my $ls = svn_ls_current($dir, $rm, $add);
-               next if (scalar @$ls);
-               sys(qw(svn rm --force),$dir);
-
-               my $dn = dirname $dir;
-               $rm->{ $dn }->{ basename $dir } = 1;
-               $ls = svn_ls_current($dn, $rm, $add);
-               while (scalar @$ls == 0 && $dn ne File::Spec->curdir) {
-                       sys(qw(svn rm --force),$dn);
-                       $dir = basename $dn;
-                       $dn = dirname $dn;
-                       $rm->{ $dn }->{ $dir } = 1;
-                       $ls = svn_ls_current($dn, $rm, $add);
-               }
-       }
-}
-
 sub get_commit_message {
        my ($commit, $commit_msg) = (@_);
        my %log_msg = ( msg => '' );
        open my $msg, '>', $commit_msg or croak $!;
 
-       chomp(my $type = `git-cat-file -t $commit`);
+       my $type = command_oneline(qw/cat-file -t/, $commit);
        if ($type eq 'commit' || $type eq 'tag') {
-               my $pid = open my $msg_fh, '-|';
-               defined $pid or croak $!;
-
-               if ($pid == 0) {
-                       exec('git-cat-file', $type, $commit) or croak $!;
-               }
+               my ($msg_fh, $ctx) = command_output_pipe('cat-file',
+                                                        $type, $commit);
                my $in_msg = 0;
                while (<$msg_fh>) {
                        if (!$in_msg) {
@@ -1628,7 +1247,7 @@ sub get_commit_message {
                                print $msg $_ or croak $!;
                        }
                }
-               close $msg_fh or croak $?;
+               command_close_pipe($msg_fh, $ctx);
        }
        close $msg or croak $!;
 
@@ -1653,64 +1272,9 @@ sub set_svn_commit_env {
        }
 }
 
-sub svn_commit_tree {
-       my ($last, $commit) = @_;
-       my $commit_msg = "$GIT_SVN_DIR/.svn-commit.tmp.$$";
-       my $log_msg = get_commit_message($commit, $commit_msg);
-       my ($oneline) = ($log_msg->{msg} =~ /([^\n\r]+)/);
-       print "Committing $commit: $oneline\n";
-
-       set_svn_commit_env();
-       my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
-       $ENV{LC_ALL} = 'C';
-       unlink $commit_msg;
-       my ($committed) = ($ci_output[$#ci_output] =~ /(\d+)/);
-       if (!defined $committed) {
-               my $out = join("\n",@ci_output);
-               print STDERR "W: Trouble parsing \`svn commit' output:\n\n",
-                               $out, "\n\nAssuming English locale...";
-               ($committed) = ($out =~ /^Committed revision \d+\./sm);
-               defined $committed or die " FAILED!\n",
-                       "Commit output failed to parse committed revision!\n",
-               print STDERR " OK\n";
-       }
-
-       my @svn_up = qw(svn up);
-       push @svn_up, '--ignore-externals' unless $_no_ignore_ext;
-       if ($_optimize_commits && ($committed == ($last->{revision} + 1))) {
-               push @svn_up, "-r$committed";
-               sys(@svn_up);
-               my $info = svn_info('.');
-               my $date = $info->{'Last Changed Date'} or die "Missing date\n";
-               if ($info->{'Last Changed Rev'} != $committed) {
-                       croak "$info->{'Last Changed Rev'} != $committed\n"
-               }
-               my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
-                                       /(\d{4})\-(\d\d)\-(\d\d)\s
-                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
-                                        or croak "Failed to parse date: $date\n";
-               $log_msg->{date} = "$tz $Y-$m-$d $H:$M:$S";
-               $log_msg->{author} = $info->{'Last Changed Author'};
-               $log_msg->{revision} = $committed;
-               $log_msg->{msg} .= "\n";
-               $log_msg->{parents} = [ $last->{commit} ];
-               $log_msg->{commit} = git_commit($log_msg, $commit);
-               return $log_msg;
-       }
-       # resync immediately
-       push @svn_up, "-r$last->{revision}";
-       sys(@svn_up);
-       return fetch("$committed=$commit");
-}
-
 sub rev_list_raw {
-       my (@args) = @_;
-       my $pid = open my $fh, '-|';
-       defined $pid or croak $!;
-       if (!$pid) {
-               exec(qw/git-rev-list --pretty=raw/, @args) or croak $!;
-       }
-       return { fh => $fh, t => { } };
+       my ($fh, $c) = command_output_pipe(qw/rev-list --pretty=raw/, @_);
+       return { fh => $fh, ctx => $c, t => { } };
 }
 
 sub next_rev_list_entry {
@@ -1732,184 +1296,10 @@ sub next_rev_list_entry {
                        $x->{m} .= $_;
                }
        }
+       command_close_pipe($fh, $rl->{ctx});
        return ($x != $rl->{t}) ? $x : undef;
 }
 
-# read the entire log into a temporary file (which is removed ASAP)
-# and store the file handle + parser state
-sub svn_log_raw {
-       my (@log_args) = @_;
-       my $log_fh = IO::File->new_tmpfile or croak $!;
-       my $pid = fork;
-       defined $pid or croak $!;
-       if (!$pid) {
-               open STDOUT, '>&', $log_fh or croak $!;
-               exec (qw(svn log), @log_args) or croak $!
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-       seek $log_fh, 0, 0 or croak $!;
-       return { state => 'sep', fh => $log_fh };
-}
-
-sub next_log_entry {
-       my $log = shift; # retval of svn_log_raw()
-       my $ret = undef;
-       my $fh = $log->{fh};
-
-       while (<$fh>) {
-               chomp;
-               if (/^\-{72}$/) {
-                       if ($log->{state} eq 'msg') {
-                               if ($ret->{lines}) {
-                                       $ret->{msg} .= $_."\n";
-                                       unless(--$ret->{lines}) {
-                                               $log->{state} = 'sep';
-                                       }
-                               } else {
-                                       croak "Log parse error at: $_\n",
-                                               $ret->{revision},
-                                               "\n";
-                               }
-                               next;
-                       }
-                       if ($log->{state} ne 'sep') {
-                               croak "Log parse error at: $_\n",
-                                       "state: $log->{state}\n",
-                                       $ret->{revision},
-                                       "\n";
-                       }
-                       $log->{state} = 'rev';
-
-                       # if we have an empty log message, put something there:
-                       if ($ret) {
-                               $ret->{msg} ||= "\n";
-                               delete $ret->{lines};
-                               return $ret;
-                       }
-                       next;
-               }
-               if ($log->{state} eq 'rev' && s/^r(\d+)\s*\|\s*//) {
-                       my $rev = $1;
-                       my ($author, $date, $lines) = split(/\s*\|\s*/, $_, 3);
-                       ($lines) = ($lines =~ /(\d+)/);
-                       $date = '1970-01-01 00:00:00 +0000'
-                               if ($_ignore_nodate && $date eq '(no date)');
-                       my ($Y,$m,$d,$H,$M,$S,$tz) = ($date =~
-                                       /(\d{4})\-(\d\d)\-(\d\d)\s
-                                        (\d\d)\:(\d\d)\:(\d\d)\s([\-\+]\d+)/x)
-                                        or croak "Failed to parse date: $date\n";
-                       $ret = {        revision => $rev,
-                                       date => "$tz $Y-$m-$d $H:$M:$S",
-                                       author => $author,
-                                       lines => $lines,
-                                       msg => '' };
-                       if (defined $_authors && ! defined $users{$author}) {
-                               die "Author: $author not defined in ",
-                                               "$_authors file\n";
-                       }
-                       $log->{state} = 'msg_start';
-                       next;
-               }
-               # skip the first blank line of the message:
-               if ($log->{state} eq 'msg_start' && /^$/) {
-                       $log->{state} = 'msg';
-               } elsif ($log->{state} eq 'msg') {
-                       if ($ret->{lines}) {
-                               $ret->{msg} .= $_."\n";
-                               unless (--$ret->{lines}) {
-                                       $log->{state} = 'sep';
-                               }
-                       } else {
-                               croak "Log parse error at: $_\n",
-                                       $ret->{revision},"\n";
-                       }
-               }
-       }
-       return $ret;
-}
-
-sub svn_info {
-       my $url = shift || $SVN_URL;
-
-       my $pid = open my $info_fh, '-|';
-       defined $pid or croak $!;
-
-       if ($pid == 0) {
-               exec(qw(svn info),$url) or croak $!;
-       }
-
-       my $ret = {};
-       # only single-lines seem to exist in svn info output
-       while (<$info_fh>) {
-               chomp $_;
-               if (m#^([^:]+)\s*:\s*(\S.*)$#) {
-                       $ret->{$1} = $2;
-                       push @{$ret->{-order}}, $1;
-               }
-       }
-       close $info_fh or croak $?;
-       return $ret;
-}
-
-sub sys { system(@_) == 0 or croak $? }
-
-sub do_update_index {
-       my ($z_cmd, $cmd, $no_text_base) = @_;
-
-       my $z = open my $p, '-|';
-       defined $z or croak $!;
-       unless ($z) { exec @$z_cmd or croak $! }
-
-       my $pid = open my $ui, '|-';
-       defined $pid or croak $!;
-       unless ($pid) {
-               exec('git-update-index',"--$cmd",'-z','--stdin') or croak $!;
-       }
-       local $/ = "\0";
-       while (my $x = <$p>) {
-               chomp $x;
-               if (!$no_text_base && lstat $x && ! -l _ &&
-                               svn_propget_base('svn:keywords', $x)) {
-                       my $mode = -x _ ? 0755 : 0644;
-                       my ($v,$d,$f) = File::Spec->splitpath($x);
-                       my $tb = File::Spec->catfile($d, '.svn', 'tmp',
-                                               'text-base',"$f.svn-base");
-                       $tb =~ s#^/##;
-                       unless (-f $tb) {
-                               $tb = File::Spec->catfile($d, '.svn',
-                                               'text-base',"$f.svn-base");
-                               $tb =~ s#^/##;
-                       }
-                       my @s = stat($x);
-                       unlink $x or croak $!;
-                       copy($tb, $x);
-                       chmod(($mode &~ umask), $x) or croak $!;
-                       utime $s[8], $s[9], $x;
-               }
-               print $ui $x,"\0";
-       }
-       close $ui or croak $?;
-}
-
-sub index_changes {
-       return if $_use_lib;
-
-       if (!-f "$GIT_SVN_DIR/info/exclude") {
-               open my $fd, '>>', "$GIT_SVN_DIR/info/exclude" or croak $!;
-               print $fd '.svn',"\n";
-               close $fd or croak $!;
-       }
-       my $no_text_base = shift;
-       do_update_index([qw/git-diff-files --name-only -z/],
-                       'remove',
-                       $no_text_base);
-       do_update_index([qw/git-ls-files -z --others/,
-                               "--exclude-from=$GIT_SVN_DIR/info/exclude"],
-                       'add',
-                       $no_text_base);
-}
-
 sub s_to_file {
        my ($str, $file, $mode) = @_;
        open my $fd,'>',$file or croak $!;
@@ -1935,18 +1325,6 @@ sub assert_revision_unknown {
        }
 }
 
-sub trees_eq {
-       my ($x, $y) = @_;
-       my @x = safe_qx('git-cat-file','commit',$x);
-       my @y = safe_qx('git-cat-file','commit',$y);
-       if (($y[0] ne $x[0]) || $x[0] !~ /^tree $sha1\n$/
-                               || $y[0] !~ /^tree $sha1\n$/) {
-               print STDERR "Trees not equal: $y[0] != $x[0]\n";
-               return 0
-       }
-       return 1;
-}
-
 sub git_commit {
        my ($log_msg, @parents) = @_;
        assert_revision_unknown($log_msg->{revision});
@@ -1971,15 +1349,14 @@ sub git_commit {
        my $tree = $log_msg->{tree};
        if (!defined $tree) {
                my $index = set_index($GIT_SVN_INDEX);
-               index_changes();
-               chomp($tree = `git-write-tree`);
+               $tree = command_oneline('write-tree');
                croak $? if $?;
                restore_index($index);
        }
-
        # just in case we clobber the existing ref, we still want that ref
        # as our parent:
-       if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) {
+       if (my $cur = verify_ref("refs/remotes/$GIT_SVN^0")) {
+               chomp $cur;
                push @tmp_parents, $cur;
        }
 
@@ -1988,9 +1365,7 @@ sub git_commit {
                        my $skip;
                        foreach (@tmp_parents) {
                                # see if a common parent is found
-                               my $mb = eval {
-                                       safe_qx('git-merge-base', $_, $p)
-                               };
+                               my $mb = eval { command('merge-base', $_, $p) };
                                next if ($@ || $?);
                                $skip = 1;
                                last;
@@ -2032,19 +1407,19 @@ sub git_commit {
        if ($commit !~ /^$sha1$/o) {
                die "Failed to commit, invalid sha1: $commit\n";
        }
-       sys('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
+       command_noisy('update-ref',"refs/remotes/$GIT_SVN",$commit);
        revdb_set($REVDB, $log_msg->{revision}, $commit);
 
        # this output is read via pipe, do not change:
        print "r$log_msg->{revision} = $commit\n";
-       check_repack();
        return $commit;
 }
 
 sub check_repack {
        if ($_repack && (--$_repack_nr == 0)) {
                $_repack_nr = $_repack;
-               sys("git repack $_repack_flags");
+               # repack doesn't use any arguments with spaces in them, does it?
+               command_noisy('repack', split(/\s+/, $_repack_flags));
        }
 }
 
@@ -2061,122 +1436,17 @@ sub set_commit_env {
        $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
 }
 
-sub apply_mod_line_blob {
-       my $m = shift;
-       if ($m->{mode_b} =~ /^120/) {
-               blob_to_symlink($m->{sha1_b}, $m->{file_b});
-       } else {
-               blob_to_file($m->{sha1_b}, $m->{file_b});
-       }
-}
-
-sub blob_to_symlink {
-       my ($blob, $link) = @_;
-       defined $link or croak "\$link not defined!\n";
-       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
-       if (-l $link || -f _) {
-               unlink $link or croak $!;
-       }
-
-       my $dest = `git-cat-file blob $blob`; # no newline, so no chomp
-       symlink $dest, $link or croak $!;
-}
-
-sub blob_to_file {
-       my ($blob, $file) = @_;
-       defined $file or croak "\$file not defined!\n";
-       croak "Not a sha1: $blob\n" unless $blob =~ /^$sha1$/o;
-       if (-l $file || -f _) {
-               unlink $file or croak $!;
-       }
-
-       open my $blob_fh, '>', $file or croak "$!: $file\n";
-       my $pid = fork;
-       defined $pid or croak $!;
-
-       if ($pid == 0) {
-               open STDOUT, '>&', $blob_fh or croak $!;
-               exec('git-cat-file','blob',$blob) or croak $!;
-       }
-       waitpid $pid, 0;
-       croak $? if $?;
-
-       close $blob_fh or croak $!;
-}
-
-sub safe_qx {
-       my $pid = open my $child, '-|';
-       defined $pid or croak $!;
-       if ($pid == 0) {
-               exec(@_) or croak $!;
-       }
-       my @ret = (<$child>);
-       close $child or croak $?;
-       die $? if $?; # just in case close didn't error out
-       return wantarray ? @ret : join('',@ret);
-}
-
-sub svn_compat_check {
-       if ($_follow_parent) {
-               print STDERR 'E: --follow-parent functionality is only ',
-                               "available when SVN libraries are used\n";
-               exit 1;
-       }
-       my @co_help = safe_qx(qw(svn co -h));
-       unless (grep /ignore-externals/,@co_help) {
-               print STDERR "W: Installed svn version does not support ",
-                               "--ignore-externals\n";
-               $_no_ignore_ext = 1;
-       }
-       if (grep /usage: checkout URL\[\@REV\]/,@co_help) {
-               $_svn_co_url_revs = 1;
-       }
-       if (grep /\[TARGET\[\@REV\]\.\.\.\]/, `svn propget -h`) {
-               $_svn_pg_peg_revs = 1;
-       }
-
-       # I really, really hope nobody hits this...
-       unless (grep /stop-on-copy/, (safe_qx(qw(svn log -h)))) {
-               print STDERR <<'';
-W: The installed svn version does not support the --stop-on-copy flag in
-   the log command.
-   Lets hope the directory you're tracking is not a branch or tag
-   and was never moved within the repository...
-
-               $_no_stop_copy = 1;
-       }
-}
-
-# *sigh*, new versions of svn won't honor -r<rev> without URL@<rev>,
-# (and they won't honor URL@<rev> without -r<rev>, too!)
-sub svn_cmd_checkout {
-       my ($url, $rev, $dir) = @_;
-       my @cmd = ('svn','co', "-r$rev");
-       push @cmd, '--ignore-externals' unless $_no_ignore_ext;
-       $url .= "\@$rev" if $_svn_co_url_revs;
-       sys(@cmd, $url, $dir);
-}
-
 sub check_upgrade_needed {
        if (!-r $REVDB) {
                -d $GIT_SVN_DIR or mkpath([$GIT_SVN_DIR]);
                open my $fh, '>>',$REVDB or croak $!;
                close $fh;
        }
-       my $old = eval {
-               my $pid = open my $child, '-|';
-               defined $pid or croak $!;
-               if ($pid == 0) {
-                       close STDERR;
-                       exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $!;
-               }
-               my @ret = (<$child>);
-               close $child or croak $?;
-               die $? if $?; # just in case close didn't error out
-               return wantarray ? @ret : join('',@ret);
+       return unless eval {
+               command([qw/rev-parse --verify/,"$GIT_SVN-HEAD^0"],
+                       {STDERR => 0});
        };
-       return unless $old;
-       my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+       my $head = eval { command('rev-parse',"refs/remotes/$GIT_SVN") };
        if ($@ || !$head) {
                print STDERR "Please run: $0 rebuild --upgrade\n";
                exit 1;
@@ -2188,12 +1458,8 @@ sub check_upgrade_needed {
 sub map_tree_joins {
        my %seen;
        foreach my $br (@_branch_from) {
-               my $pid = open my $pipe, '-|';
-               defined $pid or croak $!;
-               if ($pid == 0) {
-                       exec(qw(git-rev-list --topo-order --pretty=raw), $br)
-                                                               or croak $!;
-               }
+               my $pipe = command_output_pipe(qw/rev-list
+                                           --topo-order --pretty=raw/, $br);
                while (<$pipe>) {
                        if (/^commit ($sha1)$/o) {
                                my $commit = $1;
@@ -2209,7 +1475,7 @@ sub map_tree_joins {
                                $seen{$commit} = 1;
                        }
                }
-               close $pipe; # we could be breaking the pipe early
+               close $pipe;
        }
 }
 
@@ -2221,7 +1487,7 @@ sub load_all_refs {
 
        # don't worry about rev-list on non-commit objects/tags,
        # it shouldn't blow up if a ref is a blob or tree...
-       chomp(@_branch_from = `git-rev-parse --symbolic --all`);
+       @_branch_from = command(qw/rev-parse --symbolic --all/);
 }
 
 # '<svn username> = real-name <email address>' mapping based on git-svnimport:
@@ -2247,15 +1513,9 @@ sub rload_authors {
        close $authors or croak $!;
 }
 
-sub svn_propget_base {
-       my ($p, $f) = @_;
-       $f .= '@BASE' if $_svn_pg_peg_revs;
-       return safe_qx(qw/svn propget/, $p, $f);
-}
-
 sub git_svn_each {
        my $sub = shift;
-       foreach (`git-rev-parse --symbolic --all`) {
+       foreach (command(qw/rev-parse --symbolic --all/)) {
                next unless s#^refs/remotes/##;
                chomp $_;
                next unless -f "$GIT_DIR/svn/$_/info/url";
@@ -2296,7 +1556,7 @@ sub migration_check {
                                "$GIT_SVN_DIR\n\t(required for this version ",
                                "($VERSION) of git-svn) does not.\n";
 
-       foreach my $x (`git-rev-parse --symbolic --all`) {
+       foreach my $x (command(qw/rev-parse --symbolic --all/)) {
                next unless $x =~ s#^refs/remotes/##;
                chomp $x;
                next unless -f "$GIT_DIR/$x/info/url";
@@ -2334,7 +1594,7 @@ sub init_vars {
        %tree_map = ();
 }
 
-# convert GetOpt::Long specs for use by git-repo-config
+# convert GetOpt::Long specs for use by git-config
 sub read_repo_config {
        return unless -d $GIT_DIR;
        my $opts = shift;
@@ -2342,7 +1602,7 @@ sub read_repo_config {
                my $v = $opts->{$o};
                my ($key) = ($o =~ /^([a-z\-]+)/);
                $key =~ s/-//g;
-               my $arg = 'git-repo-config';
+               my $arg = 'git-config';
                $arg .= ' --int' if ($o =~ /[:=]i$/);
                $arg .= ' --bool' if ($o !~ /[:=][sfi]$/);
                if (ref $v eq 'ARRAY') {
@@ -2350,7 +1610,7 @@ sub read_repo_config {
                        @$v = @tmp if @tmp;
                } else {
                        chomp(my $tmp = `$arg --get svn.$key`);
-                       if ($tmp && !($arg =~ / --bool / && $tmp eq 'false')) {
+                       if ($tmp && !($arg =~ / --bool/ && $tmp eq 'false')) {
                                $$v = $tmp;
                        }
                }
@@ -2401,11 +1661,7 @@ sub write_grafts {
                my $p = $grafts->{$c};
                my %x; # real parents
                delete $p->{$c}; # commits are not self-reproducing...
-               my $pid = open my $ch, '-|';
-               defined $pid or croak $!;
-               if (!$pid) {
-                       exec(qw/git-cat-file commit/, $c) or croak $!;
-               }
+               my $ch = command_output_pipe(qw/cat-file commit/, $c);
                while (<$ch>) {
                        if (/^parent ($sha1)/) {
                                $x{$1} = $p->{$1} = 1;
@@ -2425,7 +1681,7 @@ sub write_grafts {
                        next if $del{$i} || $p->{$i} == 2;
                        foreach my $j (@jp) {
                                next if $i eq $j || $del{$j} || $p->{$j} == 2;
-                               $mb = eval { safe_qx('git-merge-base',$i,$j) };
+                               $mb = eval { command('merge-base', $i, $j) };
                                next unless $mb;
                                chomp $mb;
                                next if $x{$mb};
@@ -2486,7 +1742,7 @@ sub extract_metadata {
        my $id = shift or return (undef, undef, undef);
        my ($url, $rev, $uuid) = ($id =~ /^git-svn-id:\s(\S+?)\@(\d+)
                                                        \s([a-f\d\-]+)$/x);
-       if (!$rev || !$uuid || !$url) {
+       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\-]+)/);
@@ -2496,15 +1752,12 @@ sub extract_metadata {
 
 sub cmt_metadata {
        return extract_metadata((grep(/^git-svn-id: /,
-               safe_qx(qw/git-cat-file commit/, shift)))[-1]);
+               command(qw/cat-file commit/, shift)))[-1]);
 }
 
 sub get_commit_time {
        my $cmt = shift;
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               exec qw/git-rev-list --pretty=raw -n1/, $cmt or croak $!;
-       }
+       my $fh = command_output_pipe(qw/rev-list --pretty=raw -n1/, $cmt);
        while (<$fh>) {
                /^committer\s(?:.+) (\d+) ([\-\+]?\d+)$/ or next;
                my ($s, $tz) = ($1, $2);
@@ -2525,14 +1778,18 @@ sub tz_to_s_offset {
        return ($1 * 60) + ($tz * 3600);
 }
 
-sub setup_pager { # translated to Perl from pager.c
-       return unless (-t *STDOUT);
-       my $pager = $ENV{PAGER};
-       if (!defined $pager) {
-               $pager = 'less';
-       } elsif (length $pager == 0 || $pager eq 'cat') {
-               return;
+# adapted from pager.c
+sub config_pager {
+       $_pager ||= $ENV{GIT_PAGER} || $ENV{PAGER};
+       if (!defined $_pager) {
+               $_pager = 'less';
+       } elsif (length $_pager == 0 || $_pager eq 'cat') {
+               $_pager = undef;
        }
+}
+
+sub run_pager {
+       return unless -t *STDOUT;
        pipe my $rfd, my $wfd or return;
        defined(my $pid = fork) or croak $!;
        if (!$pid) {
@@ -2540,8 +1797,8 @@ sub setup_pager { # translated to Perl from pager.c
                return;
        }
        open STDIN, '<&', $rfd or croak $!;
-       $ENV{LESS} ||= '-S';
-       exec $pager or croak "Can't run pager: $!\n";;
+       $ENV{LESS} ||= 'FRSX';
+       exec $_pager or croak "Can't run pager: $! ($_pager)\n";
 }
 
 sub get_author_info {
@@ -2655,160 +1912,309 @@ sub show_commit_normal {
        }
 }
 
-sub libsvn_load {
-       return unless $_use_lib;
-       $_use_lib = eval {
-               require SVN::Core;
-               if ($SVN::Core::VERSION lt '1.1.0') {
-                       die "Need SVN::Core 1.1.0 or better ",
-                                       "(got $SVN::Core::VERSION) ",
-                                       "Falling back to command-line svn\n";
+sub _simple_prompt {
+       my ($cred, $realm, $default_username, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $default_username = $_username if defined $_username;
+       if (defined $default_username && length $default_username) {
+               if (defined $realm && length $realm) {
+                       print STDERR "Authentication realm: $realm\n";
+                       STDERR->flush;
                }
-               require SVN::Ra;
-               require SVN::Delta;
-               push @SVN::Git::Editor::ISA, 'SVN::Delta::Editor';
-               my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
-                                       $SVN::Node::dir.$SVN::Node::unknown.
-                                       $SVN::Node::none.$SVN::Node::file.
-                                       $SVN::Node::dir.$SVN::Node::unknown;
-               1;
-       };
+               $cred->username($default_username);
+       } else {
+               _username_prompt($cred, $realm, $may_save, $pool);
+       }
+       $cred->password(_read_password("Password for '" .
+                                      $cred->username . "': ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _ssl_server_trust_prompt {
+       my ($cred, $realm, $failures, $cert_info, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print STDERR "Error validating server certificate for '$realm':\n";
+       if ($failures & $SVN::Auth::SSL::UNKNOWNCA) {
+               print STDERR " - The certificate is not issued by a trusted ",
+                     "authority. Use the\n",
+                     "   fingerprint to validate the certificate manually!\n";
+       }
+       if ($failures & $SVN::Auth::SSL::CNMISMATCH) {
+               print STDERR " - The certificate hostname does not match.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::NOTYETVALID) {
+               print STDERR " - The certificate is not yet valid.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::EXPIRED) {
+               print STDERR " - The certificate has expired.\n";
+       }
+       if ($failures & $SVN::Auth::SSL::OTHER) {
+               print STDERR " - The certificate has an unknown error.\n";
+       }
+       printf STDERR
+               "Certificate information:\n".
+               " - Hostname: %s\n".
+               " - Valid: from %s until %s\n".
+               " - Issuer: %s\n".
+               " - Fingerprint: %s\n",
+               map $cert_info->$_, qw(hostname valid_from valid_until
+                                      issuer_dname fingerprint);
+       my $choice;
+prompt:
+       print STDERR $may_save ?
+             "(R)eject, accept (t)emporarily or accept (p)ermanently? " :
+             "(R)eject or accept (t)emporarily? ";
+       STDERR->flush;
+       $choice = lc(substr(<STDIN> || 'R', 0, 1));
+       if ($choice =~ /^t$/i) {
+               $cred->may_save(undef);
+       } elsif ($choice =~ /^r$/i) {
+               return -1;
+       } elsif ($may_save && $choice =~ /^p$/i) {
+               $cred->may_save($may_save);
+       } else {
+               goto prompt;
+       }
+       $cred->accepted_failures($failures);
+       $SVN::_Core::SVN_NO_ERROR;
 }
 
-sub libsvn_connect {
-       my ($url) = @_;
-       my $auth = SVN::Core::auth_open([SVN::Client::get_simple_provider(),
-                         SVN::Client::get_ssl_server_trust_file_provider(),
-                         SVN::Client::get_username_provider()]);
-       my $s = eval { SVN::Ra->new(url => $url, auth => $auth) };
-       return $s;
+sub _ssl_client_cert_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       print STDERR "Client certificate filename: ";
+       STDERR->flush;
+       chomp(my $filename = <STDIN>);
+       $cred->cert_file($filename);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
 }
 
-sub libsvn_get_file {
-       my ($gui, $f, $rev, $chg) = @_;
-       my $p = $f;
-       if (length $SVN_PATH > 0) {
-               return unless ($p =~ s#^\Q$SVN_PATH\E/##);
+sub _ssl_client_cert_pw_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       $cred->password(_read_password("Password: ", $realm));
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
+
+sub _username_prompt {
+       my ($cred, $realm, $may_save, $pool) = @_;
+       $may_save = undef if $_no_auth_cache;
+       if (defined $realm && length $realm) {
+               print STDERR "Authentication realm: $realm\n";
+       }
+       my $username;
+       if (defined $_username) {
+               $username = $_username;
+       } else {
+               print STDERR "Username: ";
+               STDERR->flush;
+               chomp($username = <STDIN>);
        }
-       print "\t$chg\t$f\n" unless $_q;
+       $cred->username($username);
+       $cred->may_save($may_save);
+       $SVN::_Core::SVN_NO_ERROR;
+}
 
-       my ($hash, $pid, $in, $out);
-       my $pool = SVN::Pool->new;
-       defined($pid = open3($in, $out, '>&STDERR',
-                               qw/git-hash-object -w --stdin/)) or croak $!;
-       # redirect STDOUT for SVN 1.1.x compatibility
-       open my $stdout, '>&', \*STDOUT or croak $!;
-       open STDOUT, '>&', $in or croak $!;
-       my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool);
-       $in->flush == 0 or croak $!;
-       open STDOUT, '>&', $stdout or croak $!;
-       close $in or croak $!;
-       close $stdout or croak $!;
-       $pool->clear;
-       chomp($hash = do { local $/; <$out> });
-       close $out or croak $!;
-       waitpid $pid, 0;
-       $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
-
-       my $mode = exists $props->{'svn:executable'} ? '100755' : '100644';
-       if (exists $props->{'svn:special'}) {
-               $mode = '120000';
-               my $link = `git-cat-file blob $hash`;
-               $link =~ s/^link // or die "svn:special file with contents: <",
-                                               $link, "> is not understood\n";
-               defined($pid = open3($in, $out, '>&STDERR',
-                               qw/git-hash-object -w --stdin/)) or croak $!;
-               print $in $link;
-               $in->flush == 0 or croak $!;
-               close $in or croak $!;
-               chomp($hash = do { local $/; <$out> });
-               close $out or croak $!;
-               waitpid $pid, 0;
-               $hash =~ /^$sha1$/o or die "not a sha1: $hash\n";
+sub _read_password {
+       my ($prompt, $realm) = @_;
+       print STDERR $prompt;
+       STDERR->flush;
+       require Term::ReadKey;
+       Term::ReadKey::ReadMode('noecho');
+       my $password = '';
+       while (defined(my $key = Term::ReadKey::ReadKey(0))) {
+               last if $key =~ /[\012\015]/; # \n\r
+               $password .= $key;
+       }
+       Term::ReadKey::ReadMode('restore');
+       print STDERR "\n";
+       STDERR->flush;
+       $password;
+}
+
+sub libsvn_connect {
+       my ($url) = @_;
+       SVN::_Core::svn_config_ensure($_config_dir, undef);
+       my ($baton, $callbacks) = SVN::Core::auth_open_helper([
+           SVN::Client::get_simple_provider(),
+           SVN::Client::get_ssl_server_trust_file_provider(),
+           SVN::Client::get_simple_prompt_provider(
+             \&_simple_prompt, 2),
+           SVN::Client::get_ssl_client_cert_prompt_provider(
+             \&_ssl_client_cert_prompt, 2),
+           SVN::Client::get_ssl_client_cert_pw_prompt_provider(
+             \&_ssl_client_cert_pw_prompt, 2),
+           SVN::Client::get_username_provider(),
+           SVN::Client::get_ssl_server_trust_prompt_provider(
+             \&_ssl_server_trust_prompt),
+           SVN::Client::get_username_prompt_provider(
+             \&_username_prompt, 2),
+         ]);
+       my $config = SVN::Core::config_get_config($_config_dir);
+       my $ra = SVN::Ra->new(url => $url, auth => $baton,
+                             config => $config,
+                             pool => SVN::Pool->new,
+                             auth_provider_callbacks => $callbacks);
+       $ra->{svn_path} = $url;
+       $ra->{repos_root} = $ra->get_repos_root;
+       $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##;
+       push @repo_path_split_cache, qr/^(\Q$ra->{repos_root}\E)/;
+       return $ra;
+}
+
+sub libsvn_can_do_switch {
+       unless (defined $_svn_can_do_switch) {
+               my $pool = SVN::Pool->new;
+               my $rep = eval {
+                       $SVN->do_switch(1, '', 0, $SVN->{url},
+                                       SVN::Delta::Editor->new, $pool);
+               };
+               if ($@) {
+                       $_svn_can_do_switch = 0;
+               } else {
+                       $rep->abort_report($pool);
+                       $_svn_can_do_switch = 1;
+               }
+               $pool->clear;
        }
-       print $gui $mode,' ',$hash,"\t",$p,"\0" or croak $!;
+       $_svn_can_do_switch;
+}
+
+sub libsvn_dup_ra {
+       my ($ra) = @_;
+       SVN::Ra->new(map { $_ => $ra->{$_} } qw/config url
+                    auth auth_provider_callbacks repos_root svn_path/);
+}
+
+sub uri_encode {
+       my ($f) = @_;
+       $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg;
+       $f
+}
+
+sub uri_decode {
+       my ($f) = @_;
+       $f =~ tr/+/ /;
+       $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge;
+       $f
 }
 
 sub libsvn_log_entry {
-       my ($rev, $author, $date, $msg, $parents) = @_;
+       my ($rev, $author, $date, $msg, $parents, $untracked) = @_;
        my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T
                                         (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x)
                                or die "Unable to parse date: $date\n";
-       if (defined $_authors && ! defined $users{$author}) {
+       if (defined $author && length $author > 0 &&
+           defined $_authors && ! defined $users{$author}) {
                die "Author: $author not defined in $_authors file\n";
        }
        $msg = '' if ($rev == 0 && !defined $msg);
-       return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
-               author => $author, msg => $msg."\n", parents => $parents || [] }
+
+       open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!;
+       my $h;
+       print $un "r$rev\n" or croak $!;
+       $h = $untracked->{empty};
+       foreach (sort keys %$h) {
+               my $act = $h->{$_} ? '+empty_dir' : '-empty_dir';
+               print $un "  $act: ", uri_encode($_), "\n" or croak $!;
+               warn "W: $act: $_\n";
+       }
+       foreach my $t (qw/dir_prop file_prop/) {
+               $h = $untracked->{$t} or next;
+               foreach my $path (sort keys %$h) {
+                       my $ppath = $path eq '' ? '.' : $path;
+                       foreach my $prop (sort keys %{$h->{$path}}) {
+                               next if $SKIP{$prop};
+                               my $v = $h->{$path}->{$prop};
+                               if (defined $v) {
+                                       print $un "  +$t: ",
+                                                 uri_encode($ppath), ' ',
+                                                 uri_encode($prop), ' ',
+                                                 uri_encode($v), "\n"
+                                                 or croak $!;
+                               } else {
+                                       print $un "  -$t: ",
+                                                 uri_encode($ppath), ' ',
+                                                 uri_encode($prop), "\n"
+                                                 or croak $!;
+                               }
+                       }
+               }
+       }
+       foreach my $t (qw/absent_file absent_directory/) {
+               $h = $untracked->{$t} or next;
+               foreach my $parent (sort keys %$h) {
+                       foreach my $path (sort @{$h->{$parent}}) {
+                               print $un "  $t: ",
+                                     uri_encode("$parent/$path"), "\n"
+                                     or croak $!;
+                               warn "W: $t: $parent/$path ",
+                                    "Insufficient permissions?\n";
+                       }
+               }
+       }
+
+       # revprops (make this optional? it's an extra network trip...)
+       my $pool = SVN::Pool->new;
+       my $rp = $SVN->rev_proplist($rev, $pool);
+       foreach (sort keys %$rp) {
+               next if /^svn:(?:author|date|log)$/;
+               print $un "  rev_prop: ", uri_encode($_), ' ',
+                         uri_encode($rp->{$_}), "\n";
+       }
+       $pool->clear;
+       close $un or croak $!;
+
+       { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S",
+         author => $author, msg => $msg."\n", parents => $parents || [],
+         revprops => $rp }
 }
 
 sub process_rm {
-       my ($gui, $last_commit, $f) = @_;
-       $f =~ s#^\Q$SVN_PATH\E/?## or return;
+       my ($gui, $last_commit, $f, $q) = @_;
        # remove entire directories.
-       if (safe_qx('git-ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
-               defined(my $pid = open my $ls, '-|') or croak $!;
-               if (!$pid) {
-                       exec(qw/git-ls-tree -r --name-only -z/,
-                               $last_commit,'--',$f) or croak $!;
-               }
+       if (command('ls-tree',$last_commit,'--',$f) =~ /^040000 tree/) {
+               my ($ls, $ctx) = command_output_pipe(qw/ls-tree
+                                                    -r --name-only -z/,
+                                                    $last_commit,'--',$f);
                local $/ = "\0";
                while (<$ls>) {
                        print $gui '0 ',0 x 40,"\t",$_ or croak $!;
+                       print "\tD\t$_\n" unless $q;
                }
-               close $ls or croak $?;
+               print "\tD\t$f/\n" unless $q;
+               command_close_pipe($ls, $ctx);
+               return $SVN::Node::dir;
        } else {
                print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!;
+               print "\tD\t$f\n" unless $q;
+               return $SVN::Node::file;
        }
 }
 
 sub libsvn_fetch {
        my ($last_commit, $paths, $rev, $author, $date, $msg) = @_;
-       open my $gui, '| git-update-index -z --index-info' or croak $!;
-       my @amr;
-       foreach my $f (keys %$paths) {
-               my $m = $paths->{$f}->action();
-               $f =~ s#^/+##;
-               if ($m =~ /^[DR]$/) {
-                       print "\t$m\t$f\n" unless $_q;
-                       process_rm($gui, $last_commit, $f);
-                       next if $m eq 'D';
-                       # 'R' can be file replacements, too, right?
-               }
-               my $pool = SVN::Pool->new;
-               my $t = $SVN->check_path($f, $rev, $pool);
-               if ($t == $SVN::Node::file) {
-                       if ($m =~ /^[AMR]$/) {
-                               push @amr, [ $m, $f ];
-                       } else {
-                               die "Unrecognized action: $m, ($f r$rev)\n";
-                       }
-               } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) {
-                       my @traversed = ();
-                       libsvn_traverse($gui, '', $f, $rev, \@traversed);
-                       foreach (@traversed) {
-                               push @amr, [ $m, $_ ]
-                       }
-               }
-               $pool->clear;
-       }
-       foreach (@amr) {
-               libsvn_get_file($gui, $_->[1], $rev, $_->[0]);
+       my $pool = SVN::Pool->new;
+       my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q });
+       my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
+       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+       my (undef, $last_rev, undef) = cmt_metadata($last_commit);
+       $reporter->set_path('', $last_rev, 0, @lock, $pool);
+       $reporter->finish_report($pool);
+       $pool->clear;
+       unless ($ed->{git_commit_ok}) {
+               die "SVN connection failed somewhere...\n";
        }
-       close $gui or croak $?;
-       return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]);
+       libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed);
 }
 
 sub svn_grab_base_rev {
-       defined(my $pid = open my $fh, '-|') or croak $!;
-       if (!$pid) {
-               open my $null, '>', '/dev/null' or croak $!;
-               open STDERR, '>&', $null or croak $!;
-               exec qw/git-rev-parse --verify/,"refs/remotes/$GIT_SVN^0"
-                                                               or croak $!;
-       }
-       chomp(my $c = do { local $/; <$fh> });
-       close $fh;
+       my $c = eval { command_oneline([qw/rev-parse --verify/,
+                                       "refs/remotes/$GIT_SVN^0"],
+                                       { STDERR => 0 }) };
        if (defined $c && length $c) {
                my ($url, $rev, $uuid) = cmt_metadata($c);
                return ($rev, $c) if defined $rev;
@@ -2856,35 +2262,13 @@ sub libsvn_parse_revision {
                "Try using the command-line svn client instead\n";
 }
 
-sub libsvn_traverse {
-       my ($gui, $pfx, $path, $rev, $files) = @_;
-       my $cwd = "$pfx/$path";
-       my $pool = SVN::Pool->new;
-       $cwd =~ s#^/+##g;
-       my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool);
-       foreach my $d (keys %$dirent) {
-               my $t = $dirent->{$d}->kind;
-               if ($t == $SVN::Node::dir) {
-                       libsvn_traverse($gui, $cwd, $d, $rev, $files);
-               } elsif ($t == $SVN::Node::file) {
-                       my $file = "$cwd/$d";
-                       if (defined $files) {
-                               push @$files, $file;
-                       } else {
-                               libsvn_get_file($gui, $file, $rev, 'A');
-                       }
-               }
-       }
-       $pool->clear;
-}
-
 sub libsvn_traverse_ignore {
        my ($fh, $path, $r) = @_;
        $path =~ s#^/+##g;
        my $pool = SVN::Pool->new;
        my ($dirent, undef, $props) = $SVN->get_dir($path, $r, $pool);
        my $p = $path;
-       $p =~ s#^\Q$SVN_PATH\E/?##;
+       $p =~ s#^\Q$SVN->{svn_path}\E/##;
        print $fh length $p ? "\n# $p\n" : "\n# /\n";
        if (my $s = $props->{'svn:ignore'}) {
                $s =~ s/[\r\n]+/\n/g;
@@ -2908,25 +2292,18 @@ sub revisions_eq {
        my ($path, $r0, $r1) = @_;
        return 1 if $r0 == $r1;
        my $nr = 0;
-       if ($_use_lib) {
-               # should be OK to use Pool here (r1 - r0) should be small
-               my $pool = SVN::Pool->new;
-               libsvn_get_log($SVN, "/$path", $r0, $r1,
-                               0, 1, 1, sub {$nr++}, $pool);
-               $pool->clear;
-       } else {
-               my ($url, undef) = repo_path_split($SVN_URL);
-               my $svn_log = svn_log_raw("$url/$path","-r$r0:$r1");
-               while (next_log_entry($svn_log)) { $nr++ }
-               close $svn_log->{fh};
-       }
+       # should be OK to use Pool here (r1 - r0) should be small
+       my $pool = SVN::Pool->new;
+       libsvn_get_log($SVN, [$path], $r0, $r1,
+                       0, 0, 1, sub {$nr++}, $pool);
+       $pool->clear;
        return 0 if ($nr > 1);
        return 1;
 }
 
 sub libsvn_find_parent_branch {
        my ($paths, $rev, $author, $date, $msg) = @_;
-       my $svn_path = '/'.$SVN_PATH;
+       my $svn_path = '/'.$SVN->{svn_path};
 
        # look for a parent from another branch:
        my $i = $paths->{$svn_path} or return;
@@ -2937,7 +2314,7 @@ sub libsvn_find_parent_branch {
        $branch_from =~ s#^/##;
        my $l_map = {};
        read_url_paths_all($l_map, '', "$GIT_DIR/svn");
-       my $url = $SVN->{url};
+       my $url = $SVN->{repos_root};
        defined $l_map->{$url} or return;
        my $id = $l_map->{$url}->{$branch_from};
        if (!defined $id && $_follow_parent) {
@@ -2959,7 +2336,7 @@ sub libsvn_find_parent_branch {
                        $GIT_SVN = $ENV{GIT_SVN_ID} = $id;
                        init_vars();
                        $SVN_URL = "$url/$branch_from";
-                       $SVN_LOG = $SVN = undef;
+                       $SVN = undef;
                        setup_git_svn();
                        # we can't assume SVN_URL exists at r+1:
                        $_revision = "0:$r";
@@ -2974,9 +2351,27 @@ sub libsvn_find_parent_branch {
        if (revisions_eq($branch_from, $r0, $r)) {
                unlink $GIT_SVN_INDEX;
                print STDERR "Found branch parent: ($GIT_SVN) $parent\n";
-               sys(qw/git-read-tree/, $parent);
-               return libsvn_fetch($parent, $paths, $rev,
-                                       $author, $date, $msg);
+               command_noisy('read-tree', $parent);
+               unless (libsvn_can_do_switch()) {
+                       return _libsvn_new_tree($paths, $rev, $author, $date,
+                                               $msg, [$parent]);
+               }
+               # do_switch works with svn/trunk >= r22312, but that is not
+               # included with SVN 1.4.2 (the latest version at the moment),
+               # so we can't rely on it.
+               my $ra = libsvn_connect("$url/$branch_from");
+               my $ed = SVN::Git::Fetcher->new({c => $parent, q => $_q });
+               my $pool = SVN::Pool->new;
+               my $reporter = $ra->do_switch($rev, '', 1, $SVN->{url},
+                                             $ed, $pool);
+               my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+               $reporter->set_path('', $r0, 0, @lock, $pool);
+               $reporter->finish_report($pool);
+               $pool->clear;
+               unless ($ed->{git_commit_ok}) {
+                       die "SVN connection failed somewhere...\n";
+               }
+               return libsvn_log_entry($rev, $author, $date, $msg, [$parent]);
        }
        print STDERR "Nope, branch point not imported or unknown\n";
        return undef;
@@ -2984,6 +2379,7 @@ sub libsvn_find_parent_branch {
 
 sub libsvn_get_log {
        my ($ra, @args) = @_;
+       $args[4]-- if $args[4] && ! $_follow_parent;
        if ($SVN::Core::VERSION le '1.2.0') {
                splice(@args, 3, 1);
        }
@@ -2994,11 +2390,23 @@ sub libsvn_new_tree {
        if (my $log_entry = libsvn_find_parent_branch(@_)) {
                return $log_entry;
        }
-       my ($paths, $rev, $author, $date, $msg) = @_;
-       open my $gui, '| git-update-index -z --index-info' or croak $!;
-       libsvn_traverse($gui, '', $SVN_PATH, $rev);
-       close $gui or croak $?;
-       return libsvn_log_entry($rev, $author, $date, $msg);
+       my ($paths, $rev, $author, $date, $msg) = @_; # $pool is last
+       _libsvn_new_tree($paths, $rev, $author, $date, $msg, []);
+}
+
+sub _libsvn_new_tree {
+       my ($paths, $rev, $author, $date, $msg, $parents) = @_;
+       my $pool = SVN::Pool->new;
+       my $ed = SVN::Git::Fetcher->new({q => $_q});
+       my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool);
+       my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : ();
+       $reporter->set_path('', $rev, 1, @lock, $pool);
+       $reporter->finish_report($pool);
+       $pool->clear;
+       unless ($ed->{git_commit_ok}) {
+               die "SVN connection failed somewhere...\n";
+       }
+       libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed);
 }
 
 sub find_graft_path_commit {
@@ -3068,7 +2476,7 @@ sub libsvn_commit_cb {
                my $log = libsvn_log_entry($rev,$committer,$date,$msg);
                $log->{tree} = get_tree_from_treeish($c);
                my $cmt = git_commit($log, $cmt_last, $c);
-               my @diff = safe_qx('git-diff-tree', $cmt, $c);
+               my @diff = command('diff-tree', $cmt, $c);
                if (@diff) {
                        print STDERR "Trees differ: $cmt $c\n",
                                        join('',@diff),"\n";
@@ -3081,13 +2489,12 @@ sub libsvn_commit_cb {
 
 sub libsvn_ls_fullurl {
        my $fullurl = shift;
-       my ($repo, $path) = repo_path_split($fullurl);
-       $SVN ||= libsvn_connect($repo);
+       my $ra = libsvn_connect($fullurl);
        my @ret;
        my $pool = SVN::Pool->new;
-       my ($dirent, undef, undef) = $SVN->get_dir($path,
-                                               $SVN->get_latest_revnum, $pool);
-       foreach my $d (keys %$dirent) {
+       my $r = defined $_revision ? $_revision : $ra->get_latest_revnum;
+       my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool);
+       foreach my $d (sort keys %$dirent) {
                if ($dirent->{$d}->kind == $SVN::Node::dir) {
                        push @ret, "$d/"; # add '/' for compat with cli svn
                }
@@ -3107,8 +2514,9 @@ sub libsvn_skip_unknown_revs {
        # Wonderfully consistent library, eh?
        # 160013 - svn:// and file://
        # 175002 - http(s)://
+       # 175007 - http(s):// (this repo required authorization, too...)
        #   More codes may be discovered later...
-       if ($errno == 175002 || $errno == 160013) {
+       if ($errno == 175007 || $errno == 175002 || $errno == 160013) {
                return;
        }
        croak "Error from SVN, ($errno): ", $err->expanded_message,"\n";
@@ -3160,20 +2568,225 @@ sub revdb_get {
 sub copy_remote_ref {
        my $origin = $_cp_remote ? $_cp_remote : 'origin';
        my $ref = "refs/remotes/$GIT_SVN";
-       if (safe_qx('git-ls-remote', $origin, $ref)) {
-               sys(qw/git fetch/, $origin, "$ref:$ref");
+       if (command('ls-remote', $origin, $ref)) {
+               command_noisy('fetch', $origin, "$ref:$ref");
        } elsif ($_cp_remote && !$_upgrade) {
                die "Unable to find remote reference: ",
                                "refs/remotes/$GIT_SVN on $origin\n";
        }
 }
 
+{
+       my $kill_stupid_warnings = $SVN::Node::none.$SVN::Node::file.
+                               $SVN::Node::dir.$SVN::Node::unknown.
+                               $SVN::Node::none.$SVN::Node::file.
+                               $SVN::Node::dir.$SVN::Node::unknown.
+                               $SVN::Auth::SSL::CNMISMATCH.
+                               $SVN::Auth::SSL::NOTYETVALID.
+                               $SVN::Auth::SSL::EXPIRED.
+                               $SVN::Auth::SSL::UNKNOWNCA.
+                               $SVN::Auth::SSL::OTHER;
+}
+
+package SVN::Git::Fetcher;
+use vars qw/@ISA/;
+use strict;
+use warnings;
+use Carp qw/croak/;
+use IO::File qw//;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
+
+# file baton members: path, mode_a, mode_b, pool, fh, blob, base
+sub new {
+       my ($class, $git_svn) = @_;
+       my $self = SVN::Delta::Editor->new;
+       bless $self, $class;
+       $self->{c} = $git_svn->{c} if exists $git_svn->{c};
+       $self->{q} = $git_svn->{q};
+       $self->{empty} = {};
+       $self->{dir_prop} = {};
+       $self->{file_prop} = {};
+       $self->{absent_dir} = {};
+       $self->{absent_file} = {};
+       ($self->{gui}, $self->{ctx}) = command_input_pipe(
+                                            qw/update-index -z --index-info/);
+       require Digest::MD5;
+       $self;
+}
+
+sub open_root {
+       { path => '' };
+}
+
+sub open_directory {
+       my ($self, $path, $pb, $rev) = @_;
+       { path => $path };
+}
+
+sub delete_entry {
+       my ($self, $path, $rev, $pb) = @_;
+       my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q});
+       $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir;
+       undef;
+}
+
+sub open_file {
+       my ($self, $path, $pb, $rev) = @_;
+       my ($mode, $blob) = (command('ls-tree', $self->{c}, '--',$path)
+                            =~ /^(\d{6}) blob ([a-f\d]{40})\t/);
+       unless (defined $mode && defined $blob) {
+               die "$path was not found in commit $self->{c} (r$rev)\n";
+       }
+       { path => $path, mode_a => $mode, mode_b => $mode, blob => $blob,
+         pool => SVN::Pool->new, action => 'M' };
+}
+
+sub add_file {
+       my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
+       my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+       delete $self->{empty}->{$dir};
+       { path => $path, mode_a => 100644, mode_b => 100644,
+         pool => SVN::Pool->new, action => 'A' };
+}
+
+sub add_directory {
+       my ($self, $path, $cp_path, $cp_rev) = @_;
+       my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
+       delete $self->{empty}->{$dir};
+       $self->{empty}->{$path} = 1;
+       { path => $path };
+}
+
+sub change_dir_prop {
+       my ($self, $db, $prop, $value) = @_;
+       $self->{dir_prop}->{$db->{path}} ||= {};
+       $self->{dir_prop}->{$db->{path}}->{$prop} = $value;
+       undef;
+}
+
+sub absent_directory {
+       my ($self, $path, $pb) = @_;
+       $self->{absent_dir}->{$pb->{path}} ||= [];
+       push @{$self->{absent_dir}->{$pb->{path}}}, $path;
+       undef;
+}
+
+sub absent_file {
+       my ($self, $path, $pb) = @_;
+       $self->{absent_file}->{$pb->{path}} ||= [];
+       push @{$self->{absent_file}->{$pb->{path}}}, $path;
+       undef;
+}
+
+sub change_file_prop {
+       my ($self, $fb, $prop, $value) = @_;
+       if ($prop eq 'svn:executable') {
+               if ($fb->{mode_b} != 120000) {
+                       $fb->{mode_b} = defined $value ? 100755 : 100644;
+               }
+       } elsif ($prop eq 'svn:special') {
+               $fb->{mode_b} = defined $value ? 120000 : 100644;
+       } else {
+               $self->{file_prop}->{$fb->{path}} ||= {};
+               $self->{file_prop}->{$fb->{path}}->{$prop} = $value;
+       }
+       undef;
+}
+
+sub apply_textdelta {
+       my ($self, $fb, $exp) = @_;
+       my $fh = IO::File->new_tmpfile;
+       $fh->autoflush(1);
+       # $fh gets auto-closed() by SVN::TxDelta::apply(),
+       # (but $base does not,) so dup() it for reading in close_file
+       open my $dup, '<&', $fh or croak $!;
+       my $base = IO::File->new_tmpfile;
+       $base->autoflush(1);
+       if ($fb->{blob}) {
+               defined (my $pid = fork) or croak $!;
+               if (!$pid) {
+                       open STDOUT, '>&', $base or croak $!;
+                       print STDOUT 'link ' if ($fb->{mode_a} == 120000);
+                       exec qw/git-cat-file blob/, $fb->{blob} or croak $!;
+               }
+               waitpid $pid, 0;
+               croak $? if $?;
+
+               if (defined $exp) {
+                       seek $base, 0, 0 or croak $!;
+                       my $md5 = Digest::MD5->new;
+                       $md5->addfile($base);
+                       my $got = $md5->hexdigest;
+                       die "Checksum mismatch: $fb->{path} $fb->{blob}\n",
+                           "expected: $exp\n",
+                           "     got: $got\n" if ($got ne $exp);
+               }
+       }
+       seek $base, 0, 0 or croak $!;
+       $fb->{fh} = $dup;
+       $fb->{base} = $base;
+       [ SVN::TxDelta::apply($base, $fh, undef, $fb->{path}, $fb->{pool}) ];
+}
+
+sub close_file {
+       my ($self, $fb, $exp) = @_;
+       my $hash;
+       my $path = $fb->{path};
+       if (my $fh = $fb->{fh}) {
+               seek($fh, 0, 0) or croak $!;
+               my $md5 = Digest::MD5->new;
+               $md5->addfile($fh);
+               my $got = $md5->hexdigest;
+               die "Checksum mismatch: $path\n",
+                   "expected: $exp\n    got: $got\n" if ($got ne $exp);
+               seek($fh, 0, 0) or croak $!;
+               if ($fb->{mode_b} == 120000) {
+                       read($fh, my $buf, 5) == 5 or croak $!;
+                       $buf eq 'link ' or die "$path has mode 120000",
+                                              "but is not a link\n";
+               }
+               defined(my $pid = open my $out,'-|') or die "Can't fork: $!\n";
+               if (!$pid) {
+                       open STDIN, '<&', $fh or croak $!;
+                       exec qw/git-hash-object -w --stdin/ or croak $!;
+               }
+               chomp($hash = do { local $/; <$out> });
+               close $out or croak $!;
+               close $fh or croak $!;
+               $hash =~ /^[a-f\d]{40}$/ or die "not a sha1: $hash\n";
+               close $fb->{base} or croak $!;
+       } else {
+               $hash = $fb->{blob} or die "no blob information\n";
+       }
+       $fb->{pool}->clear;
+       my $gui = $self->{gui};
+       print $gui "$fb->{mode_b} $hash\t$path\0" or croak $!;
+       print "\t$fb->{action}\t$path\n" if $fb->{action} && ! $self->{q};
+       undef;
+}
+
+sub abort_edit {
+       my $self = shift;
+       eval { command_close_pipe($self->{gui}, $self->{ctx}) };
+       $self->SUPER::abort_edit(@_);
+}
+
+sub close_edit {
+       my $self = shift;
+       command_close_pipe($self->{gui}, $self->{ctx});
+       $self->{git_commit_ok} = 1;
+       $self->SUPER::close_edit(@_);
+}
+
 package SVN::Git::Editor;
 use vars qw/@ISA/;
 use strict;
 use warnings;
 use Carp qw/croak/;
 use IO::File;
+use Git qw/command command_oneline command_noisy
+           command_output_pipe command_input_pipe command_close_pipe/;
 
 sub new {
        my $class = shift;
@@ -3196,8 +2809,7 @@ sub split_path {
 }
 
 sub repo_path {
-       (defined $_[1] && length $_[1]) ? "$_[0]->{svn_path}/$_[1]"
-                                       : $_[0]->{svn_path}
+       (defined $_[1] && length $_[1]) ? $_[1] : ''
 }
 
 sub url_path {
@@ -3224,15 +2836,12 @@ sub rmdirs {
        delete $rm->{''}; # we never delete the url we're tracking
        return unless %$rm;
 
-       defined(my $pid = open my $fh,'-|') or croak $!;
-       if (!$pid) {
-               exec qw/git-ls-tree --name-only -r -z/, $self->{c} or croak $!;
-       }
+       my ($fh, $ctx) = command_output_pipe(
+                                  qw/ls-tree --name-only -r -z/, $self->{c});
        local $/ = "\0";
-       my @svn_path = split m#/#, $self->{svn_path};
        while (<$fh>) {
                chomp;
-               my @dn = (@svn_path, (split m#/#, $_));
+               my @dn = split m#/#, $_;
                while (pop @dn) {
                        delete $rm->{join '/', @dn};
                }
@@ -3241,13 +2850,13 @@ sub rmdirs {
                        return;
                }
        }
-       close $fh;
+       command_close_pipe($fh, $ctx);
 
        my ($r, $p, $bat) = ($self->{r}, $self->{pool}, $self->{bat});
        foreach my $d (sort { $b =~ tr#/#/# <=> $a =~ tr#/#/# } keys %$rm) {
                $self->close_directory($bat->{$d}, $p);
                my ($dn) = ($d =~ m#^(.*?)/?(?:[^/]+)$#);
-               print "\tD+\t/$d/\n" unless $q;
+               print "\tD+\t$d/\n" unless $q;
                $self->SUPER::delete_entry($d, $r, $bat->{$dn}, $p);
                delete $bat->{$d};
        }
@@ -3416,13 +3025,7 @@ __END__
 
 Data structures:
 
-$svn_log hashref (as returned by svn_log_raw)
-{
-       fh => file handle of the log file,
-       state => state of the log file parser (sep/msg/rev/msg_start...)
-}
-
-$log_msg hashref as returned by next_log_entry($svn_log)
+$log_msg hashref as returned by libsvn_log_entry()
 {
        msg => 'whitespace-formatted log entry
 ',                                             # trailing newline is preserved
@@ -3431,7 +3034,6 @@ $log_msg hashref as returned by next_log_entry($svn_log)
        author => 'committer name'
 };
 
-
 @mods = array of diff-index line hashes, each element represents one line
        of diff-index output
 
index cbaa8ab37c78769002e10947d7ca08fc440e552f..3af8c7e1106d755b1589750ff5673ff6a6cd3b14 100755 (executable)
@@ -31,12 +31,13 @@ $SIG{'PIPE'}="IGNORE";
 $ENV{'TZ'}="UTC";
 
 our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
-    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,$opt_P);
+    $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F,
+    $opt_P,$opt_R);
 
 sub usage() {
        print STDERR <<END;
 Usage: ${\basename $0}     # fetch/update GIT from SVN
-       [-o branch-for-HEAD] [-h] [-v] [-l max_rev]
+       [-o branch-for-HEAD] [-h] [-v] [-l max_rev] [-R repack_each_revs]
        [-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
        [-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
        [-m] [-M regex] [-A author_file] [-S] [-F] [-P project_name] [SVN_URL]
@@ -44,7 +45,7 @@ END
        exit(1);
 }
 
-getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:SP:uv") or usage();
+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";
@@ -52,6 +53,7 @@ my $trunk_name = $opt_T || "trunk";
 my $branch_name = $opt_b || "branches";
 my $project_name = $opt_P || "";
 $project_name = "/" . $project_name if ($project_name);
+my $repack_after = $opt_R || 1000;
 
 @ARGV == 1 or @ARGV == 2 or usage();
 
@@ -146,6 +148,7 @@ sub file {
        print "... $rev $path ...\n" if $opt_v;
        my (undef, $properties);
        my $pool = SVN::Pool->new();
+       $path =~ s#^/*##;
        eval { (undef, $properties)
                   = $self->{'svn'}->get_file($path,$rev,$fh,$pool); };
        $pool->clear;
@@ -181,6 +184,7 @@ sub ignore {
        my($self,$path,$rev) = @_;
 
        print "... $rev $path ...\n" if $opt_v;
+       $path =~ s#^/*##;
        my (undef,undef,$properties)
            = $self->{'svn'}->get_dir($path,$rev,undef);
        if (exists $properties->{'svn:ignore'}) {
@@ -197,6 +201,7 @@ sub ignore {
 
 sub dir_list {
        my($self,$path,$rev) = @_;
+       $path =~ s#^/*##;
        my ($dirents,undef,$properties)
            = $self->{'svn'}->get_dir($path,$rev,undef);
        return $dirents;
@@ -280,7 +285,7 @@ my $last_rev = "";
 my $last_branch;
 my $current_rev = $opt_s || 1;
 unless(-d $git_dir) {
-       system("git-init-db");
+       system("git-init");
        die "Cannot init the GIT db at $git_tree: $?\n" if $?;
        system("git-read-tree");
        die "Cannot init an empty tree: $?\n" if $?;
@@ -354,6 +359,7 @@ open BRANCHES,">>", "$git_dir/svn2git";
 sub node_kind($$) {
        my ($svnpath, $revision) = @_;
        my $pool=SVN::Pool->new;
+       $svnpath =~ s#^/*##;
        my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool);
        $pool->clear;
        return $kind;
@@ -934,11 +940,27 @@ if ($opt_l < $current_rev) {
     exit;
 }
 
-print "Fetching from $current_rev to $opt_l ...\n" if $opt_v;
+print "Processing from $current_rev to $opt_l ...\n" if $opt_v;
 
-my $pool=SVN::Pool->new;
-$svn->{'svn'}->get_log("/",$current_rev,$opt_l,0,1,1,\&commit_all,$pool);
-$pool->clear;
+my $from_rev;
+my $to_rev = $current_rev - 1;
+
+while ($to_rev < $opt_l) {
+       $from_rev = $to_rev + 1;
+       $to_rev = $from_rev + $repack_after;
+       $to_rev = $opt_l if $opt_l < $to_rev;
+       print "Fetching from $from_rev to $to_rev ...\n" if $opt_v;
+       my $pool=SVN::Pool->new;
+       $svn->{'svn'}->get_log("/",$from_rev,$to_rev,0,1,1,\&commit_all,$pool);
+       $pool->clear;
+       my $pid = fork();
+       die "Fork: $!\n" unless defined $pid;
+       unless($pid) {
+               exec("git-repack", "-d")
+                       or die "Cannot repack: $!\n";
+       }
+       waitpid($pid, 0);
+}
 
 
 unlink($git_index);
index ac269e3277d9b8c844e8c43b95695375dd829746..4a0a7b660780b7c377ce80ac49dc99cea3e8137d 100755 (executable)
@@ -1,16 +1,18 @@
 #!/bin/sh
 # Copyright (c) 2005 Linus Torvalds
 
-USAGE='-l [<pattern>] | [-a | -s | -u <key-id>] [-f | -d] [-m <msg>] <tagname> [<head>]'
+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
@@ -37,6 +39,21 @@ do
        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
@@ -46,11 +63,28 @@ do
        ;;
     -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-update-ref -m 'tag: delete' -d "refs/tags/$tag_name" "$tag" &&
-               echo "Deleted tag $tag_name."
+       git-verify-tag -v "$tag"
        exit $?
        ;;
     -*)
@@ -78,12 +112,15 @@ git-check-ref-format "tags/$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
-: ${username:=$(expr "z$tagger" : 'z\(.*>\)')}
+
+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" ]; then
+    if [ -z "$message_given" ]; then
         ( echo "#"
           echo "# Write a tag message"
           echo "#" ) > "$GIT_DIR"/TAG_EDITMSG
@@ -95,7 +132,7 @@ if [ "$annotate" ]; then
     grep -v '^#' <"$GIT_DIR"/TAG_EDITMSG |
     git-stripspace >"$GIT_DIR"/TAG_FINALMSG
 
-    [ -s "$GIT_DIR"/TAG_FINALMSG ] || {
+    [ -s "$GIT_DIR"/TAG_FINALMSG -o -n "$message_given" ] || {
        echo >&2 "No tag message?"
        exit 1
     }
index 36f171b3028a3460c1c51f9c1f5747a9a0d2d850..8db7dd0b7d371cfeec84e70ff2a51ccf53a994e7 100755 (executable)
@@ -34,7 +34,10 @@ t)
        ;;
 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
diff --git a/git.c b/git.c
index 1aa07a5164012dddf2fb0dca23c08d9c487e7d54..4dd196721f552579c6cd5ec75275b869c72e5118 100644 (file)
--- a/git.c
+++ b/git.c
@@ -1,20 +1,8 @@
-#include <stdio.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <dirent.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-#include <errno.h>
-#include <limits.h>
-#include <stdarg.h>
-#include "git-compat-util.h"
+#include "builtin.h"
 #include "exec_cmd.h"
 #include "cache.h"
 #include "quote.h"
 
-#include "builtin.h"
-
 const char git_usage_string[] =
        "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
 
@@ -71,16 +59,18 @@ static int handle_options(const char*** argv, int* argc)
                } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
                        setup_pager();
                } else if (!strcmp(cmd, "--git-dir")) {
-                       if (*argc < 1)
-                               return -1;
-                       setenv("GIT_DIR", (*argv)[1], 1);
+                       if (*argc < 2) {
+                               fprintf(stderr, "No directory given for --git-dir.\n" );
+                               usage(git_usage_string);
+                       }
+                       setenv(GIT_DIR_ENVIRONMENT, (*argv)[1], 1);
                        (*argv)++;
                        (*argc)--;
                } else if (!strncmp(cmd, "--git-dir=", 10)) {
-                       setenv("GIT_DIR", cmd + 10, 1);
+                       setenv(GIT_DIR_ENVIRONMENT, cmd + 10, 1);
                } else if (!strcmp(cmd, "--bare")) {
-                       static char git_dir[1024];
-                       setenv("GIT_DIR", getcwd(git_dir, 1024), 1);
+                       static char git_dir[PATH_MAX+1];
+                       setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 1);
                } else {
                        fprintf(stderr, "Unknown option: %s\n", cmd);
                        usage(git_usage_string);
@@ -169,6 +159,16 @@ static int handle_alias(int *argcp, const char ***argv)
        alias_command = (*argv)[0];
        git_config(git_alias_config);
        if (alias_string) {
+               if (alias_string[0] == '!') {
+                       trace_printf("trace: alias to shell cmd: %s => %s\n",
+                                    alias_command, alias_string + 1);
+                       ret = system(alias_string + 1);
+                       if (ret >= 0 && WIFEXITED(ret) &&
+                           WEXITSTATUS(ret) != 127)
+                               exit(WEXITSTATUS(ret));
+                       die("Failed to run '%s' when expanding alias '%s'\n",
+                           alias_string + 1, alias_command);
+               }
                count = split_cmdline(alias_string, &new_argv);
                option_count = handle_options(&new_argv, &count);
                memmove(new_argv - option_count, new_argv,
@@ -209,6 +209,11 @@ const char git_version_string[] = GIT_VERSION;
 
 #define RUN_SETUP      (1<<0)
 #define USE_PAGER      (1<<1)
+/*
+ * require working tree to be present -- anything uses this needs
+ * RUN_SETUP for reading from the configuration file.
+ */
+#define NOT_BARE       (1<<2)
 
 static void handle_internal_command(int argc, const char **argv, char **envp)
 {
@@ -218,36 +223,42 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                int (*fn)(int, const char **, const char *);
                int option;
        } commands[] = {
-               { "add", cmd_add, RUN_SETUP },
-               { "annotate", cmd_annotate, },
+               { "add", cmd_add, RUN_SETUP | NOT_BARE },
+               { "annotate", cmd_annotate, USE_PAGER },
                { "apply", cmd_apply },
                { "archive", cmd_archive },
-               { "blame", cmd_blame, RUN_SETUP | USE_PAGER },
+               { "blame", cmd_blame, RUN_SETUP },
                { "branch", cmd_branch, RUN_SETUP },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "checkout-index", cmd_checkout_index, RUN_SETUP },
                { "check-ref-format", cmd_check_ref_format },
                { "cherry", cmd_cherry, RUN_SETUP },
                { "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, RUN_SETUP | USE_PAGER },
                { "diff-files", cmd_diff_files, RUN_SETUP },
                { "diff-index", cmd_diff_index, RUN_SETUP },
-               { "diff-stages", cmd_diff_stages, RUN_SETUP },
                { "diff-tree", cmd_diff_tree, RUN_SETUP },
                { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP },
                { "for-each-ref", cmd_for_each_ref, RUN_SETUP },
                { "format-patch", cmd_format_patch, RUN_SETUP },
+               { "fsck", cmd_fsck, RUN_SETUP },
+               { "fsck-objects", cmd_fsck, RUN_SETUP },
                { "get-tar-commit-id", cmd_get_tar_commit_id },
                { "grep", cmd_grep, RUN_SETUP },
                { "help", cmd_help },
+               { "init", cmd_init_db },
                { "init-db", cmd_init_db },
                { "log", cmd_log, RUN_SETUP | USE_PAGER },
                { "ls-files", cmd_ls_files, RUN_SETUP },
                { "ls-tree", cmd_ls_tree, RUN_SETUP },
                { "mailinfo", cmd_mailinfo },
                { "mailsplit", cmd_mailsplit },
-               { "mv", cmd_mv, RUN_SETUP },
+               { "merge-base", cmd_merge_base, RUN_SETUP },
+               { "merge-file", cmd_merge_file },
+               { "mv", cmd_mv, RUN_SETUP | NOT_BARE },
                { "name-rev", cmd_name_rev, RUN_SETUP },
                { "pack-objects", cmd_pack_objects, RUN_SETUP },
                { "pickaxe", cmd_blame, RUN_SETUP | USE_PAGER },
@@ -255,11 +266,14 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                { "prune-packed", cmd_prune_packed, RUN_SETUP },
                { "push", cmd_push, RUN_SETUP },
                { "read-tree", cmd_read_tree, RUN_SETUP },
-               { "repo-config", cmd_repo_config },
+               { "reflog", cmd_reflog, RUN_SETUP },
+               { "repo-config", cmd_config },
+               { "rerere", cmd_rerere, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
-               { "rm", cmd_rm, RUN_SETUP },
-               { "runstatus", cmd_runstatus, RUN_SETUP },
+               { "rm", cmd_rm, RUN_SETUP | NOT_BARE },
+               { "runstatus", cmd_runstatus, RUN_SETUP | NOT_BARE },
+               { "shortlog", cmd_shortlog, RUN_SETUP | USE_PAGER },
                { "show-branch", cmd_show_branch, RUN_SETUP },
                { "show", cmd_show, RUN_SETUP | USE_PAGER },
                { "stripspace", cmd_stripspace },
@@ -295,6 +309,9 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
                        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));
@@ -380,8 +397,15 @@ int main(int argc, const char **argv, char **envp)
                done_alias = 1;
        }
 
-       if (errno == ENOENT)
+       if (errno == ENOENT) {
+               if (done_alias) {
+                       fprintf(stderr, "Expansion of alias '%s' failed; "
+                               "'%s' is not a git-command\n",
+                               cmd, argv[0]);
+                       exit(1);
+               }
                help_unknown_cmd(cmd);
+       }
 
        fprintf(stderr, "Failed to run command '%s': %s\n",
                cmd, strerror(errno));
index 83268fc9d9e3ab63920b5ec4a5ffd71e8dd11b0d..46aee88fd15d934dd84783981f2be3ed086c9cf3 100644 (file)
@@ -9,29 +9,23 @@ 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, perl-Git
+Requires:      git-core, git-svn, git-cvs, git-arch, git-email, gitk, git-gui, perl-Git
 
 %description
-This is a stupid (but extremely fast) directory content manager.  It
-doesn't do a whole lot, but what it _does_ do is track directory
-contents efficiently. It is intended to be the base of an efficient,
-distributed source code management system. This package includes
-rudimentary tools that can be used as a SCM, but you should look
-elsewhere for tools for ordinary humans layered on top of this.
+Git is a fast, scalable, distributed revision control system with an
+unusually rich command set that provides both high-level operations
+and full access to internals.
 
 This is a dummy package which brings in all subpackages.
 
 %package core
 Summary:       Core git tools
 Group:         Development/Tools
-Requires:      zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, python >= 2.3, expat
+Requires:      zlib >= 1.2, rsync, curl, less, openssh-clients, expat
 %description core
-This is a stupid (but extremely fast) directory content manager.  It
-doesn't do a whole lot, but what it _does_ do is track directory
-contents efficiently. It is intended to be the base of an efficient,
-distributed source code management system. This package includes
-rudimentary tools that can be used as a SCM, but you should look
-elsewhere for tools for ordinary humans layered on top of this.
+Git is a fast, scalable, distributed revision control system with an
+unusually rich command set that provides both high-level operations
+and full access to internals.
 
 These are the core tools with minimal dependencies.
 
@@ -63,6 +57,13 @@ Requires:    git-core = %{version}-%{release}
 %description email
 Git tools for sending email.
 
+%package gui
+Summary:        Git GUI tool
+Group:          Development/Tools
+Requires:       git-core = %{version}-%{release}, tk >= 8.4
+%description gui
+Git GUI tool
+
 %package -n gitk
 Summary:        Git revision tree visualiser ('gitk')
 Group:          Development/Tools
@@ -89,17 +90,18 @@ make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" WITH_OWN_SUBPROCESS_PY=YesPlease \
 
 %install
 rm -rf $RPM_BUILD_ROOT
-make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
+make %{_smp_mflags} CFLAGS="$RPM_OPT_FLAGS" DESTDIR=$RPM_BUILD_ROOT \
+     WITH_OWN_SUBPROCESS_PY=YesPlease \
      prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \
      install %{!?_without_docs: install-doc}
 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 "archimport|svn|cvs|email|gitk" | 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 "archimport|svn|git-cvs|email|gitk" | 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
@@ -138,6 +140,16 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %{_mandir}/man1/*email*.1*}
 %{!?_without_docs: %doc Documentation/*email*.html }
 
+%files gui
+%defattr(-,root,root)
+%{_bindir}/git-gui
+%{_bindir}/git-citool
+# 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}
+
 %files -n gitk
 %defattr(-,root,root)
 %doc Documentation/*gitk*.txt
@@ -155,6 +167,12 @@ rm -rf $RPM_BUILD_ROOT
 %{!?_without_docs: %doc Documentation/*.html }
 
 %changelog
+* Mon Feb 13 2007 Nicolas Pitre <nico@cam.org>
+- Update core package description (Git isn't as stupid as it used to be)
+
+* Mon Feb 12 2007 Junio C Hamano <junkio@cox.net>
+- Add git-gui and git-citool.
+
 * Mon Nov 14 2005 H. Peter Anvin <hpa@zytor.com> 0.99.9j-1
 - Change subpackage names to git-<name> instead of git-core-<name>
 - Create empty root package which brings in all subpackages
diff --git a/gitMergeCommon.py b/gitMergeCommon.py
deleted file mode 100644 (file)
index fdbf9e4..0000000
+++ /dev/null
@@ -1,275 +0,0 @@
-#
-# Copyright (C) 2005 Fredrik Kuivinen
-#
-
-import sys, re, os, traceback
-from sets import Set
-
-def die(*args):
-    printList(args, sys.stderr)
-    sys.exit(2)
-
-def printList(list, file=sys.stdout):
-    for x in list:
-        file.write(str(x))
-        file.write(' ')
-    file.write('\n')
-
-import subprocess
-
-# Debugging machinery
-# -------------------
-
-DEBUG = 0
-functionsToDebug = Set()
-
-def addDebug(func):
-    if type(func) == str:
-        functionsToDebug.add(func)
-    else:
-        functionsToDebug.add(func.func_name)
-
-def debug(*args):
-    if DEBUG:
-        funcName = traceback.extract_stack()[-2][2]
-        if funcName in functionsToDebug:
-            printList(args)
-
-# Program execution
-# -----------------
-
-class ProgramError(Exception):
-    def __init__(self, progStr, error):
-        self.progStr = progStr
-        self.error = error
-
-    def __str__(self):
-        return self.progStr + ': ' + self.error
-
-addDebug('runProgram')
-def runProgram(prog, input=None, returnCode=False, env=None, pipeOutput=True):
-    debug('runProgram prog:', str(prog), 'input:', str(input))
-    if type(prog) is str:
-        progStr = prog
-    else:
-        progStr = ' '.join(prog)
-    
-    try:
-        if pipeOutput:
-            stderr = subprocess.STDOUT
-            stdout = subprocess.PIPE
-        else:
-            stderr = None
-            stdout = None
-        pop = subprocess.Popen(prog,
-                               shell = type(prog) is str,
-                               stderr=stderr,
-                               stdout=stdout,
-                               stdin=subprocess.PIPE,
-                               env=env)
-    except OSError, e:
-        debug('strerror:', e.strerror)
-        raise ProgramError(progStr, e.strerror)
-
-    if input != None:
-        pop.stdin.write(input)
-    pop.stdin.close()
-
-    if pipeOutput:
-        out = pop.stdout.read()
-    else:
-        out = ''
-
-    code = pop.wait()
-    if returnCode:
-        ret = [out, code]
-    else:
-        ret = out
-    if code != 0 and not returnCode:
-        debug('error output:', out)
-        debug('prog:', prog)
-        raise ProgramError(progStr, out)
-#    debug('output:', out.replace('\0', '\n'))
-    return ret
-
-# Code for computing common ancestors
-# -----------------------------------
-
-currentId = 0
-def getUniqueId():
-    global currentId
-    currentId += 1
-    return currentId
-
-# The 'virtual' commit objects have SHAs which are integers
-shaRE = re.compile('^[0-9a-f]{40}$')
-def isSha(obj):
-    return (type(obj) is str and bool(shaRE.match(obj))) or \
-           (type(obj) is int and obj >= 1)
-
-class Commit(object):
-    __slots__ = ['parents', 'firstLineMsg', 'children', '_tree', 'sha',
-                 'virtual']
-
-    def __init__(self, sha, parents, tree=None):
-        self.parents = parents
-        self.firstLineMsg = None
-        self.children = []
-
-        if tree:
-            tree = tree.rstrip()
-            assert(isSha(tree))
-        self._tree = tree
-
-        if not sha:
-            self.sha = getUniqueId()
-            self.virtual = True
-            self.firstLineMsg = 'virtual commit'
-            assert(isSha(tree))
-        else:
-            self.virtual = False
-            self.sha = sha.rstrip()
-        assert(isSha(self.sha))
-
-    def tree(self):
-        self.getInfo()
-        assert(self._tree != None)
-        return self._tree
-
-    def shortInfo(self):
-        self.getInfo()
-        return str(self.sha) + ' ' + self.firstLineMsg
-
-    def __str__(self):
-        return self.shortInfo()
-
-    def getInfo(self):
-        if self.virtual or self.firstLineMsg != None:
-            return
-        else:
-            info = runProgram(['git-cat-file', 'commit', self.sha])
-            info = info.split('\n')
-            msg = False
-            for l in info:
-                if msg:
-                    self.firstLineMsg = l
-                    break
-                else:
-                    if l.startswith('tree'):
-                        self._tree = l[5:].rstrip()
-                    elif l == '':
-                        msg = True
-
-class Graph:
-    def __init__(self):
-        self.commits = []
-        self.shaMap = {}
-
-    def addNode(self, node):
-        assert(isinstance(node, Commit))
-        self.shaMap[node.sha] = node
-        self.commits.append(node)
-        for p in node.parents:
-            p.children.append(node)
-        return node
-
-    def reachableNodes(self, n1, n2):
-        res = {}
-        def traverse(n):
-            res[n] = True
-            for p in n.parents:
-                traverse(p)
-
-        traverse(n1)
-        traverse(n2)
-        return res
-
-    def fixParents(self, node):
-        for x in range(0, len(node.parents)):
-            node.parents[x] = self.shaMap[node.parents[x]]
-
-# addDebug('buildGraph')
-def buildGraph(heads):
-    debug('buildGraph heads:', heads)
-    for h in heads:
-        assert(isSha(h))
-
-    g = Graph()
-
-    out = runProgram(['git-rev-list', '--parents'] + heads)
-    for l in out.split('\n'):
-        if l == '':
-            continue
-        shas = l.split(' ')
-
-        # This is a hack, we temporarily use the 'parents' attribute
-        # to contain a list of SHA1:s. They are later replaced by proper
-        # Commit objects.
-        c = Commit(shas[0], shas[1:])
-
-        g.commits.append(c)
-        g.shaMap[c.sha] = c
-
-    for c in g.commits:
-        g.fixParents(c)
-
-    for c in g.commits:
-        for p in c.parents:
-            p.children.append(c)
-    return g
-
-# Write the empty tree to the object database and return its SHA1
-def writeEmptyTree():
-    tmpIndex = os.environ.get('GIT_DIR', '.git') + '/merge-tmp-index'
-    def delTmpIndex():
-        try:
-            os.unlink(tmpIndex)
-        except OSError:
-            pass
-    delTmpIndex()
-    newEnv = os.environ.copy()
-    newEnv['GIT_INDEX_FILE'] = tmpIndex
-    res = runProgram(['git-write-tree'], env=newEnv).rstrip()
-    delTmpIndex()
-    return res
-
-def addCommonRoot(graph):
-    roots = []
-    for c in graph.commits:
-        if len(c.parents) == 0:
-            roots.append(c)
-
-    superRoot = Commit(sha=None, parents=[], tree=writeEmptyTree())
-    graph.addNode(superRoot)
-    for r in roots:
-        r.parents = [superRoot]
-    superRoot.children = roots
-    return superRoot
-
-def getCommonAncestors(graph, commit1, commit2):
-    '''Find the common ancestors for commit1 and commit2'''
-    assert(isinstance(commit1, Commit) and isinstance(commit2, Commit))
-
-    def traverse(start, set):
-        stack = [start]
-        while len(stack) > 0:
-            el = stack.pop()
-            set.add(el)
-            for p in el.parents:
-                if p not in set:
-                    stack.append(p)
-    h1Set = Set()
-    h2Set = Set()
-    traverse(commit1, h1Set)
-    traverse(commit2, h2Set)
-    shared = h1Set.intersection(h2Set)
-
-    if len(shared) == 0:
-        shared = [addCommonRoot(graph)]
-        
-    res = Set()
-
-    for s in shared:
-        if len([c for c in s.children if c in shared]) == 0:
-            res.add(s)
-    return list(res)
diff --git a/gitk b/gitk
index ab383b3ad29cea5c7c45a8f757941acbc3abaf33..9ddff3e7f7b011564c56fb619f57bed66d875f7e 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -12,7 +12,7 @@ proc gitdir {} {
     if {[info exists env(GIT_DIR)]} {
        return $env(GIT_DIR)
     } else {
-       return ".git"
+       return [exec git rev-parse --git-dir]
     }
 }
 
@@ -309,9 +309,9 @@ proc readrefs {} {
     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
        catch {unset $v}
     }
-    set refd [open [list | git ls-remote [gitdir]] r]
+    set refd [open [list | git show-ref] r]
     while {0 <= [set n [gets $refd line]]} {
-       if {![regexp {^([0-9a-f]{40})   refs/([^^]*)$} $line \
+       if {![regexp {^([0-9a-f]{40}) refs/([^^]*)$} $line \
            match id path]} {
            continue
        }
@@ -427,7 +427,7 @@ proc makewindow {} {
     .bar.view add separator
     .bar.view add radiobutton -label "All files" -command {showview 0} \
        -variable selectedview -value 0
-    
+
     menu .bar.help
     .bar add cascade -label "Help" -menu .bar.help
     .bar.help add command -label "About gitk" -command about
@@ -435,56 +435,60 @@ proc makewindow {} {
     .bar.help configure -font $uifont
     . configure -menu .bar
 
-    if {![info exists geometry(canv1)]} {
-       set geometry(canv1) [expr {45 * $charspc}]
-       set geometry(canv2) [expr {30 * $charspc}]
-       set geometry(canv3) [expr {15 * $charspc}]
-       set geometry(canvh) [expr {25 * $linespc + 4}]
-       set geometry(ctextw) 80
-       set geometry(ctexth) 30
-       set geometry(cflistw) 30
-    }
+    # the gui has upper and lower half, parts of a paned window.
     panedwindow .ctop -orient vertical
-    if {[info exists geometry(width)]} {
-       .ctop conf -width $geometry(width) -height $geometry(height)
-       set texth [expr {$geometry(height) - $geometry(canvh) - 56}]
-       set geometry(ctexth) [expr {($texth - 8) /
-                                   [font metrics $textfont -linespace]}]
-    }
-    frame .ctop.top
-    frame .ctop.top.bar
-    frame .ctop.top.lbar
-    pack .ctop.top.lbar -side bottom -fill x
-    pack .ctop.top.bar -side bottom -fill x
-    set cscroll .ctop.top.csb
-    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
-    pack $cscroll -side right -fill y
-    panedwindow .ctop.top.clist -orient horizontal -sashpad 0 -handlesize 4
-    pack .ctop.top.clist -side top -fill both -expand 1
-    .ctop add .ctop.top
-    set canv .ctop.top.clist.canv
-    canvas $canv -height $geometry(canvh) -width $geometry(canv1) \
+
+    # possibly use assumed geometry
+    if {![info exists geometry(pwsash0)]} {
+        set geometry(topheight) [expr {15 * $linespc}]
+        set geometry(topwidth) [expr {80 * $charspc}]
+        set geometry(botheight) [expr {15 * $linespc}]
+        set geometry(botwidth) [expr {50 * $charspc}]
+        set geometry(pwsash0) "[expr {40 * $charspc}] 2"
+        set geometry(pwsash1) "[expr {60 * $charspc}] 2"
+    }
+
+    # the upper half will have a paned window, a scroll bar to the right, and some stuff below
+    frame .tf -height $geometry(topheight) -width $geometry(topwidth)
+    frame .tf.histframe
+    panedwindow .tf.histframe.pwclist -orient horizontal -sashpad 0 -handlesize 4
+
+    # create three canvases
+    set cscroll .tf.histframe.csb
+    set canv .tf.histframe.pwclist.canv
+    canvas $canv \
        -background $bgcolor -bd 0 \
        -yscrollincr $linespc -yscrollcommand "scrollcanv $cscroll"
-    .ctop.top.clist add $canv
-    set canv2 .ctop.top.clist.canv2
-    canvas $canv2 -height $geometry(canvh) -width $geometry(canv2) \
+    .tf.histframe.pwclist add $canv
+    set canv2 .tf.histframe.pwclist.canv2
+    canvas $canv2 \
        -background $bgcolor -bd 0 -yscrollincr $linespc
-    .ctop.top.clist add $canv2
-    set canv3 .ctop.top.clist.canv3
-    canvas $canv3 -height $geometry(canvh) -width $geometry(canv3) \
+    .tf.histframe.pwclist add $canv2
+    set canv3 .tf.histframe.pwclist.canv3
+    canvas $canv3 \
        -background $bgcolor -bd 0 -yscrollincr $linespc
-    .ctop.top.clist add $canv3
-    bind .ctop.top.clist <Configure> {resizeclistpanes %W %w}
+    .tf.histframe.pwclist add $canv3
+    eval .tf.histframe.pwclist sash place 0 $geometry(pwsash0)
+    eval .tf.histframe.pwclist sash place 1 $geometry(pwsash1)
+
+    # a scroll bar to rule them
+    scrollbar $cscroll -command {allcanvs yview} -highlightthickness 0
+    pack $cscroll -side right -fill y
+    bind .tf.histframe.pwclist <Configure> {resizeclistpanes %W %w}
     lappend bglist $canv $canv2 $canv3
+    pack .tf.histframe.pwclist -fill both -expand 1 -side left
+
+    # we have two button bars at bottom of top frame. Bar 1
+    frame .tf.bar
+    frame .tf.lbar -height 15
 
-    set sha1entry .ctop.top.bar.sha1
+    set sha1entry .tf.bar.sha1
     set entries $sha1entry
-    set sha1but .ctop.top.bar.sha1label
+    set sha1but .tf.bar.sha1label
     button $sha1but -text "SHA1 ID: " -state disabled -relief flat \
        -command gotocommit -width 8 -font $uifont
     $sha1but conf -disabledforeground [$sha1but cget -foreground]
-    pack .ctop.top.bar.sha1label -side left
+    pack .tf.bar.sha1label -side left
     entry $sha1entry -width 40 -font $textfont -textvariable sha1string
     trace add variable sha1string write sha1change
     pack $sha1entry -side left -pady 2
@@ -505,91 +509,107 @@ proc makewindow {} {
        0x00, 0x38, 0xff, 0x7f, 0xff, 0x7f, 0xff, 0x7f, 0x00, 0x38, 0x00, 0x1c,
        0x00, 0x0e, 0x00, 0x07, 0x80, 0x03, 0xc0, 0x01};
     }
-    button .ctop.top.bar.leftbut -image bm-left -command goback \
+    button .tf.bar.leftbut -image bm-left -command goback \
        -state disabled -width 26
-    pack .ctop.top.bar.leftbut -side left -fill y
-    button .ctop.top.bar.rightbut -image bm-right -command goforw \
+    pack .tf.bar.leftbut -side left -fill y
+    button .tf.bar.rightbut -image bm-right -command goforw \
        -state disabled -width 26
-    pack .ctop.top.bar.rightbut -side left -fill y
+    pack .tf.bar.rightbut -side left -fill y
 
-    button .ctop.top.bar.findbut -text "Find" -command dofind -font $uifont
-    pack .ctop.top.bar.findbut -side left
+    button .tf.bar.findbut -text "Find" -command dofind -font $uifont
+    pack .tf.bar.findbut -side left
     set findstring {}
-    set fstring .ctop.top.bar.findstring
+    set fstring .tf.bar.findstring
     lappend entries $fstring
     entry $fstring -width 30 -font $textfont -textvariable findstring
     trace add variable findstring write find_change
-    pack $fstring -side left -expand 1 -fill x
+    pack $fstring -side left -expand 1 -fill x -in .tf.bar
     set findtype Exact
-    set findtypemenu [tk_optionMenu .ctop.top.bar.findtype \
-                         findtype Exact IgnCase Regexp]
+    set findtypemenu [tk_optionMenu .tf.bar.findtype \
+                     findtype Exact IgnCase Regexp]
     trace add variable findtype write find_change
-    .ctop.top.bar.findtype configure -font $uifont
-    .ctop.top.bar.findtype.menu configure -font $uifont
+    .tf.bar.findtype configure -font $uifont
+    .tf.bar.findtype.menu configure -font $uifont
     set findloc "All fields"
-    tk_optionMenu .ctop.top.bar.findloc findloc "All fields" Headline \
+    tk_optionMenu .tf.bar.findloc findloc "All fields" Headline \
        Comments Author Committer
     trace add variable findloc write find_change
-    .ctop.top.bar.findloc configure -font $uifont
-    .ctop.top.bar.findloc.menu configure -font $uifont
-    pack .ctop.top.bar.findloc -side right
-    pack .ctop.top.bar.findtype -side right
-
-    label .ctop.top.lbar.flabel -text "Highlight:  Commits " \
-       -font $uifont
-    pack .ctop.top.lbar.flabel -side left -fill y
+    .tf.bar.findloc configure -font $uifont
+    .tf.bar.findloc.menu configure -font $uifont
+    pack .tf.bar.findloc -side right
+    pack .tf.bar.findtype -side right
+
+    # build up the bottom bar of upper window
+    label .tf.lbar.flabel -text "Highlight:  Commits " \
+    -font $uifont
+    pack .tf.lbar.flabel -side left -fill y
     set gdttype "touching paths:"
-    set gm [tk_optionMenu .ctop.top.lbar.gdttype gdttype "touching paths:" \
-               "adding/removing string:"]
+    set gm [tk_optionMenu .tf.lbar.gdttype gdttype "touching paths:" \
+       "adding/removing string:"]
     trace add variable gdttype write hfiles_change
     $gm conf -font $uifont
-    .ctop.top.lbar.gdttype conf -font $uifont
-    pack .ctop.top.lbar.gdttype -side left -fill y
-    entry .ctop.top.lbar.fent -width 25 -font $textfont \
+    .tf.lbar.gdttype conf -font $uifont
+    pack .tf.lbar.gdttype -side left -fill y
+    entry .tf.lbar.fent -width 25 -font $textfont \
        -textvariable highlight_files
     trace add variable highlight_files write hfiles_change
-    lappend entries .ctop.top.lbar.fent
-    pack .ctop.top.lbar.fent -side left -fill x -expand 1
-    label .ctop.top.lbar.vlabel -text " OR in view" -font $uifont
-    pack .ctop.top.lbar.vlabel -side left -fill y
+    lappend entries .tf.lbar.fent
+    pack .tf.lbar.fent -side left -fill x -expand 1
+    label .tf.lbar.vlabel -text " OR in view" -font $uifont
+    pack .tf.lbar.vlabel -side left -fill y
     global viewhlmenu selectedhlview
-    set viewhlmenu [tk_optionMenu .ctop.top.lbar.vhl selectedhlview None]
-    $viewhlmenu entryconf 0 -command delvhighlight
+    set viewhlmenu [tk_optionMenu .tf.lbar.vhl selectedhlview None]
+    $viewhlmenu entryconf None -command delvhighlight
     $viewhlmenu conf -font $uifont
-    .ctop.top.lbar.vhl conf -font $uifont
-    pack .ctop.top.lbar.vhl -side left -fill y
-    label .ctop.top.lbar.rlabel -text " OR " -font $uifont
-    pack .ctop.top.lbar.rlabel -side left -fill y
+    .tf.lbar.vhl conf -font $uifont
+    pack .tf.lbar.vhl -side left -fill y
+    label .tf.lbar.rlabel -text " OR " -font $uifont
+    pack .tf.lbar.rlabel -side left -fill y
     global highlight_related
-    set m [tk_optionMenu .ctop.top.lbar.relm highlight_related None \
-              "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
+    set m [tk_optionMenu .tf.lbar.relm highlight_related None \
+       "Descendent" "Not descendent" "Ancestor" "Not ancestor"]
     $m conf -font $uifont
-    .ctop.top.lbar.relm conf -font $uifont
+    .tf.lbar.relm conf -font $uifont
     trace add variable highlight_related write vrel_change
-    pack .ctop.top.lbar.relm -side left -fill y
-
-    panedwindow .ctop.cdet -orient horizontal
-    .ctop add .ctop.cdet
-    frame .ctop.cdet.left
-    frame .ctop.cdet.left.bot
-    pack .ctop.cdet.left.bot -side bottom -fill x
-    button .ctop.cdet.left.bot.search -text "Search" -command dosearch \
+    pack .tf.lbar.relm -side left -fill y
+
+    # Finish putting the upper half of the viewer together
+    pack .tf.lbar -in .tf -side bottom -fill x
+    pack .tf.bar -in .tf -side bottom -fill x
+    pack .tf.histframe -fill both -side top -expand 1
+    .ctop add .tf
+    .ctop paneconfigure .tf -height $geometry(topheight)
+    .ctop paneconfigure .tf -width $geometry(topwidth)
+
+    # now build up the bottom
+    panedwindow .pwbottom -orient horizontal
+
+    # lower left, a text box over search bar, scroll bar to the right
+    # if we know window height, then that will set the lower text height, otherwise
+    # we set lower text height which will drive window height
+    if {[info exists geometry(main)]} {
+        frame .bleft -width $geometry(botwidth)
+    } else {
+        frame .bleft -width $geometry(botwidth) -height $geometry(botheight)
+    }
+    frame .bleft.top
+
+    button .bleft.top.search -text "Search" -command dosearch \
        -font $uifont
-    pack .ctop.cdet.left.bot.search -side left -padx 5
-    set sstring .ctop.cdet.left.bot.sstring
+    pack .bleft.top.search -side left -padx 5
+    set sstring .bleft.top.sstring
     entry $sstring -width 20 -font $textfont -textvariable searchstring
     lappend entries $sstring
     trace add variable searchstring write incrsearch
     pack $sstring -side left -expand 1 -fill x
-    set ctext .ctop.cdet.left.ctext
+    set ctext .bleft.ctext
     text $ctext -background $bgcolor -foreground $fgcolor \
        -state disabled -font $textfont \
-       -width $geometry(ctextw) -height $geometry(ctexth) \
        -yscrollcommand scrolltext -wrap none
-    scrollbar .ctop.cdet.left.sb -command "$ctext yview"
-    pack .ctop.cdet.left.sb -side right -fill y
+    scrollbar .bleft.sb -command "$ctext yview"
+    pack .bleft.top -side top -fill x
+    pack .bleft.sb -side right -fill y
     pack $ctext -side left -fill both -expand 1
-    .ctop.cdet add .ctop.cdet.left
     lappend bglist $ctext
     lappend fglist $ctext
 
@@ -620,36 +640,46 @@ proc makewindow {} {
     $ctext tag conf msep -font [concat $textfont bold]
     $ctext tag conf found -back yellow
 
-    frame .ctop.cdet.right
-    frame .ctop.cdet.right.mode
-    radiobutton .ctop.cdet.right.mode.patch -text "Patch" \
+    .pwbottom add .bleft
+    .pwbottom paneconfigure .bleft -width $geometry(botwidth)
+
+    # lower right
+    frame .bright
+    frame .bright.mode
+    radiobutton .bright.mode.patch -text "Patch" \
        -command reselectline -variable cmitmode -value "patch"
-    radiobutton .ctop.cdet.right.mode.tree -text "Tree" \
+    radiobutton .bright.mode.tree -text "Tree" \
        -command reselectline -variable cmitmode -value "tree"
-    grid .ctop.cdet.right.mode.patch .ctop.cdet.right.mode.tree -sticky ew
-    pack .ctop.cdet.right.mode -side top -fill x
-    set cflist .ctop.cdet.right.cfiles
+    grid .bright.mode.patch .bright.mode.tree -sticky ew
+    pack .bright.mode -side top -fill x
+    set cflist .bright.cfiles
     set indent [font measure $mainfont "nn"]
-    text $cflist -width $geometry(cflistw) \
+    text $cflist \
        -background $bgcolor -foreground $fgcolor \
        -font $mainfont \
        -tabs [list $indent [expr {2 * $indent}]] \
-       -yscrollcommand ".ctop.cdet.right.sb set" \
+       -yscrollcommand ".bright.sb set" \
        -cursor [. cget -cursor] \
        -spacing1 1 -spacing3 1
     lappend bglist $cflist
     lappend fglist $cflist
-    scrollbar .ctop.cdet.right.sb -command "$cflist yview"
-    pack .ctop.cdet.right.sb -side right -fill y
+    scrollbar .bright.sb -command "$cflist yview"
+    pack .bright.sb -side right -fill y
     pack $cflist -side left -fill both -expand 1
     $cflist tag configure highlight \
        -background [$cflist cget -selectbackground]
     $cflist tag configure bold -font [concat $mainfont bold]
-    .ctop.cdet add .ctop.cdet.right
-    bind .ctop.cdet <Configure> {resizecdetpanes %W %w}
 
-    pack .ctop -side top -fill both -expand 1
+    .pwbottom add .bright
+    .ctop add .pwbottom
+
+    # restore window position if known
+    if {[info exists geometry(main)]} {
+        wm geometry . "$geometry(main)"
+    }
 
+    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"
@@ -699,7 +729,7 @@ proc makewindow {} {
     bind . <Control-KP_Add> {incrfont 1}
     bind . <Control-minus> {incrfont -1}
     bind . <Control-KP_Subtract> {incrfont -1}
-    bind . <Destroy> {savestuff %W}
+    wm protocol . WM_DELETE_WINDOW doquit
     bind . <Button-1> "click %W"
     bind $fstring <Key-Return> dofind
     bind $sha1entry <Key-Return> gotocommit
@@ -802,18 +832,15 @@ proc savestuff {w} {
        puts $f [list set fgcolor $fgcolor]
        puts $f [list set colors $colors]
        puts $f [list set diffcolors $diffcolors]
-       puts $f "set geometry(width) [winfo width .ctop]"
-       puts $f "set geometry(height) [winfo height .ctop]"
-       puts $f "set geometry(canv1) [expr {[winfo width $canv]-2}]"
-       puts $f "set geometry(canv2) [expr {[winfo width $canv2]-2}]"
-       puts $f "set geometry(canv3) [expr {[winfo width $canv3]-2}]"
-       puts $f "set geometry(canvh) [expr {[winfo height $canv]-2}]"
-       set wid [expr {([winfo width $ctext] - 8) \
-                          / [font measure $textfont "0"]}]
-       puts $f "set geometry(ctextw) $wid"
-       set wid [expr {([winfo width $cflist] - 11) \
-                          / [font measure [$cflist cget -font] "0"]}]
-       puts $f "set geometry(cflistw) $wid"
+
+       puts $f "set geometry(main) [wm geometry .]"
+       puts $f "set geometry(topwidth) [winfo width .tf]"
+       puts $f "set geometry(topheight) [winfo height .tf]"
+        puts $f "set geometry(pwsash0) \"[.tf.histframe.pwclist sash coord 0]\""
+        puts $f "set geometry(pwsash1) \"[.tf.histframe.pwclist sash coord 1]\""
+       puts $f "set geometry(botwidth) [winfo width .bleft]"
+       puts $f "set geometry(botheight) [winfo height .bleft]"
+
        puts -nonewline $f "set permviews {"
        for {set v 0} {$v < $nextviewnum} {incr v} {
            if {$viewperm($v)} {
@@ -1402,7 +1429,7 @@ proc newview {ishighlight} {
     set newviewname($nextviewnum) "View $nextviewnum"
     set newviewperm($nextviewnum) 0
     set newviewargs($nextviewnum) [shellarglist $revtreeargs]
-    vieweditor $top $nextviewnum "Gitk view definition" 
+    vieweditor $top $nextviewnum "Gitk view definition"
 }
 
 proc editview {} {
@@ -1474,7 +1501,7 @@ proc doviewmenu {m first cmd op argv} {
 proc allviewmenus {n op args} {
     global viewhlmenu
 
-    doviewmenu .bar.view 7 [list showview $n] $op $args
+    doviewmenu .bar.view 5 [list showview $n] $op $args
     doviewmenu $viewhlmenu 1 [list addvhighlight $n] $op $args
 }
 
@@ -1516,7 +1543,7 @@ proc newviewok {top n} {
        set viewperm($n) $newviewperm($n)
        if {$newviewname($n) ne $viewname($n)} {
            set viewname($n) $newviewname($n)
-           doviewmenu .bar.view 7 [list showview $n] \
+           doviewmenu .bar.view 5 [list showview $n] \
                entryconf [list -label $viewname($n)]
            doviewmenu $viewhlmenu 1 [list addvhighlight $n] \
                entryconf [list -label $viewname($n) -value $viewname($n)]
@@ -1632,8 +1659,8 @@ proc showview {n} {
 
     set curview $n
     set selectedview $n
-    .bar.view entryconf 2 -state [expr {$n == 0? "disabled": "normal"}]
-    .bar.view entryconf 3 -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf Edit* -state [expr {$n == 0? "disabled": "normal"}]
+    .bar.view entryconf Delete* -state [expr {$n == 0? "disabled": "normal"}]
 
     if {![info exists viewdata($n)]} {
        set pending_select $selid
@@ -3897,7 +3924,7 @@ proc selectline {l isnew} {
        }
        $ctext insert end "\n"
     }
+
     set headers {}
     set olds [lindex $parentlist $l]
     if {[llength $olds] > 1} {
@@ -4006,7 +4033,7 @@ proc selnextpage {dir} {
         set l [expr $numcommits - 1]
     }
     unmarkmatches
-    selectline $l 1    
+    selectline $l 1
 }
 
 proc unselectline {} {
@@ -4043,11 +4070,11 @@ proc addtohistory {cmd} {
     }
     incr historyindex
     if {$historyindex > 1} {
-       .ctop.top.bar.leftbut conf -state normal
+       .tf.bar.leftbut conf -state normal
     } else {
-       .ctop.top.bar.leftbut conf -state disabled
+       .tf.bar.leftbut conf -state disabled
     }
-    .ctop.top.bar.rightbut conf -state disabled
+    .tf.bar.rightbut conf -state disabled
 }
 
 proc godo {elt} {
@@ -4067,10 +4094,10 @@ proc goback {} {
     if {$historyindex > 1} {
        incr historyindex -1
        godo [lindex $history [expr {$historyindex - 1}]]
-       .ctop.top.bar.rightbut conf -state normal
+       .tf.bar.rightbut conf -state normal
     }
     if {$historyindex <= 1} {
-       .ctop.top.bar.leftbut conf -state disabled
+       .tf.bar.leftbut conf -state disabled
     }
 }
 
@@ -4081,10 +4108,10 @@ proc goforw {} {
        set cmd [lindex $history $historyindex]
        incr historyindex
        godo $cmd
-       .ctop.top.bar.leftbut conf -state normal
+       .tf.bar.leftbut conf -state normal
     }
     if {$historyindex >= [llength $history]} {
-       .ctop.top.bar.rightbut conf -state disabled
+       .tf.bar.rightbut conf -state disabled
     }
 }
 
@@ -4591,7 +4618,7 @@ proc searchmarkvisible {doall} {
 proc scrolltext {f0 f1} {
     global searchstring
 
-    .ctop.cdet.left.sb set $f0 $f1
+    .bleft.sb set $f0 $f1
     if {$searchstring ne {}} {
        searchmarkvisible 0
     }
@@ -4899,9 +4926,9 @@ proc rowmenu {x y id} {
     } else {
        set state normal
     }
-    $rowctxmenu entryconfigure 0 -state $state
-    $rowctxmenu entryconfigure 1 -state $state
-    $rowctxmenu entryconfigure 2 -state $state
+    $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
 }
@@ -5776,6 +5803,7 @@ proc showtag {tag isnew} {
 proc doquit {} {
     global stopped
     set stopped 100
+    savestuff .
     destroy .
 }
 
@@ -6193,7 +6221,7 @@ set wrcomcmd "git diff-tree --stdin -p --pretty"
 
 set gitencoding {}
 catch {
-    set gitencoding [exec git repo-config --get i18n.commitencoding]
+    set gitencoding [exec git config --get i18n.commitencoding]
 }
 if {$gitencoding == ""} {
     set gitencoding "utf-8"
@@ -6293,6 +6321,7 @@ set stuffsaved 0
 set patchnum 0
 setcoords
 makewindow
+wm title . "[file tail $argv0]: [file tail [pwd]]"
 readrefs
 
 if {$cmdline_files ne {} || $revtreeargs ne {}} {
@@ -6305,8 +6334,8 @@ if {$cmdline_files ne {} || $revtreeargs ne {}} {
     set viewargs(1) $revtreeargs
     set viewperm(1) 0
     addviewmenu 1
-    .bar.view entryconf 2 -state normal
-    .bar.view entryconf 3 -state normal
+    .bar.view entryconf Edit* -state normal
+    .bar.view entryconf Delete* -state normal
 }
 
 if {[info exists permviews]} {
index 974b47f19ca61bd5d12ec88229957c3434d23265..7177c6e86b8e8c3dc1e5d0db4bce4122cbe2430a 100644 (file)
@@ -334,11 +334,13 @@ div.diff.extended_header {
        padding: 2px 0px 2px 0px;
 }
 
+div.diff a.list,
 div.diff a.path,
 div.diff a.hash {
        text-decoration: none;
 }
 
+div.diff a.list:hover,
 div.diff a.path:hover,
 div.diff a.hash:hover {
        text-decoration: underline;
@@ -362,14 +364,25 @@ div.diff.rem {
        color: #cc0000;
 }
 
+div.diff.chunk_header a,
 div.diff.chunk_header {
        color: #990099;
+}
 
+div.diff.chunk_header {
        border: dotted #ffe0ff;
        border-width: 1px 0px 0px 0px;
        margin-top: 2px;
 }
 
+div.diff.chunk_header span.chunk_info {
+       background-color: #ffeeff;
+}
+
+div.diff.chunk_header span.section {
+       color: #aa22aa;
+}
+
 div.diff.incomplete {
        color: #cccccc;
 }
index 758759576cbbac596b15dd4940aca24ff6dbdd84..653ca3cc60ef161f06bd3a6652eba7ad3453cc2e 100755 (executable)
@@ -18,6 +18,10 @@ use File::Find qw();
 use File::Basename qw(basename);
 binmode STDOUT, ':utf8';
 
+BEGIN {
+       CGI->compile() if $ENV{MOD_PERL};
+}
+
 our $cgi = new CGI;
 our $version = "++GIT_VERSION++";
 our $my_url = $cgi->url();
@@ -120,7 +124,7 @@ our %feature = (
        # To disable system wide have in $GITWEB_CONFIG
        # $feature{'snapshot'}{'default'} = [undef];
        # To have project specific config enable override in $GITWEB_CONFIG
-       # $feature{'blame'}{'override'} = 1;
+       # $feature{'snapshot'}{'override'} = 1;
        # and in project config gitweb.snapshot = none|gzip|bzip2;
        'snapshot' => {
                'sub' => \&feature_snapshot,
@@ -128,6 +132,12 @@ our %feature = (
                #         => [content-encoding, suffix, program]
                'default' => ['x-gzip', 'gz', 'gzip']},
 
+       # Enable text search, which will list the commits which match author,
+       # committer or commit text to a given string.  Enabled by default.
+       'search' => {
+               'override' => 0,
+               'default' => [1]},
+
        # Enable the pickaxe search, which will list the commits that modified
        # a given string in a file. This can be practical and quite faster
        # alternative to 'blame', but still potentially CPU-intensive.
@@ -351,6 +361,9 @@ if (defined $searchtext) {
        if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) {
                die_error(undef, "Invalid search parameter");
        }
+       if (length($searchtext) < 2) {
+               die_error(undef, "At least two characters are required for search parameter");
+       }
        $searchtext = quotemeta $searchtext;
 }
 
@@ -425,6 +438,7 @@ my %actions = (
        "history" => \&git_history,
        "log" => \&git_log,
        "rss" => \&git_rss,
+       "atom" => \&git_atom,
        "search" => \&git_search,
        "search_help" => \&git_search_help,
        "shortlog" => \&git_shortlog,
@@ -433,6 +447,7 @@ my %actions = (
        "tags" => \&git_tags,
        "tree" => \&git_tree,
        "snapshot" => \&git_snapshot,
+       "object" => \&git_object,
        # those below don't need $project
        "opml" => \&git_opml,
        "project_list" => \&git_project_list,
@@ -459,7 +474,8 @@ exit;
 
 sub href(%) {
        my %params = @_;
-       my $href = $my_uri;
+       # default is to use -absolute url() i.e. $my_uri
+       my $href = $params{-full} ? $my_url : $my_uri;
 
        # XXX: Warning: If you touch this, check the search form for updating,
        # too.
@@ -583,7 +599,21 @@ sub esc_html ($;%) {
        return $str;
 }
 
-# Make control characterss "printable".
+# quote control characters and escape filename to HTML
+sub esc_path {
+       my $str = shift;
+       my %opts = @_;
+
+       $str = to_utf8($str);
+       $str = escapeHTML($str);
+       if ($opts{'-nbsp'}) {
+               $str =~ s/ /&nbsp;/g;
+       }
+       $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
+       return $str;
+}
+
+# Make control characters "printable", using character escape codes (CEC)
 sub quot_cec {
        my $cntrl = shift;
        my %es = ( # character escape codes, aka escape sequences
@@ -603,22 +633,14 @@ sub quot_cec {
        return "<span class=\"cntrl\">$chr</span>";
 }
 
-# Alternatively use unicode control pictures codepoints.
+# Alternatively use unicode control pictures codepoints,
+# Unicode "printable representation" (PR)
 sub quot_upr {
        my $cntrl = shift;
        my $chr = sprintf('&#%04d;', 0x2400+ord($cntrl));
        return "<span class=\"cntrl\">$chr</span>";
 }
 
-# quote control characters and escape filename to HTML
-sub esc_path {
-       my $str = shift;
-
-       $str = esc_html($str);
-       $str =~ s|([[:cntrl:]])|quot_cec($1)|eg;
-       return $str;
-}
-
 # git may return quoted and escaped filenames
 sub unquote {
        my $str = shift;
@@ -812,21 +834,19 @@ sub file_type_long {
 
 ## ----------------------------------------------------------------------
 ## functions returning short HTML fragments, or transforming HTML fragments
-## which don't beling to other sections
+## which don't belong to other sections
 
 # format line of commit message.
 sub format_log_line_html {
        my $line = shift;
 
        $line = esc_html($line, -nbsp=>1);
-       if ($line =~ m/([0-9a-fA-F]{40})/) {
+       if ($line =~ m/([0-9a-fA-F]{8,40})/) {
                my $hash_text = $1;
-               if (git_get_type($hash_text) eq "commit") {
-                       my $link =
-                               $cgi->a({-href => href(action=>"commit", hash=>$hash_text),
-                                       -class => "text"}, $hash_text);
-                       $line =~ s/$hash_text/$link/;
-               }
+               my $link =
+                       $cgi->a({-href => href(action=>"object", hash=>$hash_text),
+                               -class => "text"}, $hash_text);
+               $line =~ s/$hash_text/$link/;
        }
        return $line;
 }
@@ -848,7 +868,8 @@ sub format_ref_marker {
                                $name = $ref;
                        }
 
-                       $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>";
+                       $markers .= " <span class=\"$type\" title=\"$ref\">" .
+                                   esc_html($name) . "</span>";
                }
        }
 
@@ -874,8 +895,10 @@ sub format_subject_html {
        }
 }
 
+# format patch (diff) line (rather not to be used for diff headers)
 sub format_diff_line {
        my $line = shift;
+       my ($from, $to) = @_;
        my $char = substr($line, 0, 1);
        my $diff_class = "";
 
@@ -891,6 +914,25 @@ sub format_diff_line {
                $diff_class = " incomplete";
        }
        $line = untabify($line);
+       if ($from && $to && $line =~ m/^\@{2} /) {
+               my ($from_text, $from_start, $from_lines, $to_text, $to_start, $to_lines, $section) =
+                       $line =~ m/^\@{2} (-(\d+)(?:,(\d+))?) (\+(\d+)(?:,(\d+))?) \@{2}(.*)$/;
+
+               $from_lines = 0 unless defined $from_lines;
+               $to_lines   = 0 unless defined $to_lines;
+
+               if ($from->{'href'}) {
+                       $from_text = $cgi->a({-href=>"$from->{'href'}#l$from_start",
+                                            -class=>"list"}, $from_text);
+               }
+               if ($to->{'href'}) {
+                       $to_text   = $cgi->a({-href=>"$to->{'href'}#l$to_start",
+                                            -class=>"list"}, $to_text);
+               }
+               $line = "<span class=\"chunk_info\">@@ $from_text $to_text @@</span>" .
+                       "<span class=\"section\">" . esc_html($section, -nbsp=>1) . "</span>";
+               return "<div class=\"diff$diff_class\">$line</div>\n";
+       }
        return "<div class=\"diff$diff_class\">" . esc_html($line, -nbsp=>1) . "</div>\n";
 }
 
@@ -944,7 +986,7 @@ sub git_get_project_config {
        $key =~ s/^gitweb\.//;
        return if ($key =~ m/\W/);
 
-       my @x = (git_cmd(), 'repo-config');
+       my @x = (git_cmd(), 'config');
        if (defined $type) { push @x, $type; }
        push @x, "--get";
        push @x, "gitweb.$key";
@@ -1110,8 +1152,9 @@ sub git_get_last_activity {
 
        $git_dir = "$projectroot/$path";
        open($fd, "-|", git_cmd(), 'for-each-ref',
-            '--format=%(refname) %(committer)',
+            '--format=%(committer)',
             '--sort=-committerdate',
+            '--count=1',
             'refs/heads') or return;
        my $most_recent = <$fd>;
        close $fd or return;
@@ -1125,14 +1168,15 @@ sub git_get_last_activity {
 sub git_get_references {
        my $type = shift || "";
        my %refs;
-       # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c      refs/tags/v2.6.11
-       # c39ae07f393806ccf406ef966e9a15afc43cc36a      refs/tags/v2.6.11^{}
-       open my $fd, "-|", $GIT, "peek-remote", "$projectroot/$project/"
+       # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11
+       # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{}
+       open my $fd, "-|", git_cmd(), "show-ref", "--dereference",
+               ($type ? ("--", "refs/$type") : ()) # use -- <pattern> if $type
                or return;
 
        while (my $line = <$fd>) {
                chomp $line;
-               if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) {
+               if ($line =~ m!^([0-9a-fA-F]{40})\srefs/($type/?[^^]+)!) {
                        if (defined $refs{$1}) {
                                push @{$refs{$1}}, $2;
                        } else {
@@ -1176,10 +1220,12 @@ sub parse_date {
        $date{'mday'} = $mday;
        $date{'day'} = $days[$wday];
        $date{'month'} = $months[$mon];
-       $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
-                          $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
+       $date{'rfc2822'}   = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000",
+                            $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec;
        $date{'mday-time'} = sprintf "%d %s %02d:%02d",
                             $mday, $months[$mon], $hour ,$min;
+       $date{'iso-8601'}  = sprintf "%04d-%02d-%02dT%02d:%02d:%02dZ",
+                            1900+$year, $mon, $mday, $hour ,$min, $sec;
 
        $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/;
        my $local = $epoch + ((int $1 + ($2/60)) * 3600);
@@ -1187,9 +1233,9 @@ sub parse_date {
        $date{'hour_local'} = $hour;
        $date{'minute_local'} = $min;
        $date{'tz_local'} = $tz;
-       $date{'iso-tz'} = sprintf ("%04d-%02d-%02d %02d:%02d:%02d %s",
-                                  1900+$year, $mon+1, $mday,
-                                  $hour, $min, $sec, $tz);
+       $date{'iso-tz'} = sprintf("%04d-%02d-%02d %02d:%02d:%02d %s",
+                                 1900+$year, $mon+1, $mday,
+                                 $hour, $min, $sec, $tz);
        return %date;
 }
 
@@ -1228,42 +1274,31 @@ sub parse_tag {
        return %tag
 }
 
-sub parse_commit {
-       my $commit_id = shift;
-       my $commit_text = shift;
-
-       my @commit_lines;
+sub parse_commit_text {
+       my ($commit_text, $withparents) = @_;
+       my @commit_lines = split '\n', $commit_text;
        my %co;
 
-       if (defined $commit_text) {
-               @commit_lines = @$commit_text;
-       } else {
-               local $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list",
-                       "--header", "--parents", "--max-count=1",
-                       $commit_id, "--"
-                       or return;
-               @commit_lines = split '\n', <$fd>;
-               close $fd or return;
-               pop @commit_lines;
-       }
+       pop @commit_lines; # Remove '\0'
+
        my $header = shift @commit_lines;
        if (!($header =~ m/^[0-9a-fA-F]{40}/)) {
                return;
        }
        ($co{'id'}, my @parents) = split ' ', $header;
-       $co{'parents'} = \@parents;
-       $co{'parent'} = $parents[0];
        while (my $line = shift @commit_lines) {
                last if $line eq "\n";
                if ($line =~ m/^tree ([0-9a-fA-F]{40})$/) {
                        $co{'tree'} = $1;
+               } elsif ((!defined $withparents) && ($line =~ m/^parent ([0-9a-fA-F]{40})$/)) {
+                       push @parents, $1;
                } elsif ($line =~ m/^author (.*) ([0-9]+) (.*)$/) {
                        $co{'author'} = $1;
                        $co{'author_epoch'} = $2;
                        $co{'author_tz'} = $3;
-                       if ($co{'author'} =~ m/^([^<]+) </) {
-                               $co{'author_name'} = $1;
+                       if ($co{'author'} =~ m/^([^<]+) <([^>]*)>/) {
+                               $co{'author_name'}  = $1;
+                               $co{'author_email'} = $2;
                        } else {
                                $co{'author_name'} = $co{'author'};
                        }
@@ -1272,12 +1307,19 @@ sub parse_commit {
                        $co{'committer_epoch'} = $2;
                        $co{'committer_tz'} = $3;
                        $co{'committer_name'} = $co{'committer'};
-                       $co{'committer_name'} =~ s/ <.*//;
+                       if ($co{'committer'} =~ m/^([^<]+) <([^>]*)>/) {
+                               $co{'committer_name'}  = $1;
+                               $co{'committer_email'} = $2;
+                       } else {
+                               $co{'committer_name'} = $co{'committer'};
+                       }
                }
        }
        if (!defined $co{'tree'}) {
                return;
        };
+       $co{'parents'} = \@parents;
+       $co{'parent'} = $parents[0];
 
        foreach my $title (@commit_lines) {
                $title =~ s/^    //;
@@ -1327,6 +1369,52 @@ sub parse_commit {
        return %co;
 }
 
+sub parse_commit {
+       my ($commit_id) = @_;
+       my %co;
+
+       local $/ = "\0";
+
+       open my $fd, "-|", git_cmd(), "rev-list",
+               "--parents",
+               "--header",
+               "--max-count=1",
+               $commit_id,
+               "--",
+               or die_error(undef, "Open git-rev-list failed");
+       %co = parse_commit_text(<$fd>, 1);
+       close $fd;
+
+       return %co;
+}
+
+sub parse_commits {
+       my ($commit_id, $maxcount, $skip, $arg, $filename) = @_;
+       my @cos;
+
+       $maxcount ||= 1;
+       $skip ||= 0;
+
+       local $/ = "\0";
+
+       open my $fd, "-|", git_cmd(), "rev-list",
+               "--header",
+               ($arg ? ($arg) : ()),
+               ("--max-count=" . $maxcount),
+               ("--skip=" . $skip),
+               $commit_id,
+               "--",
+               ($filename ? ($filename) : ())
+               or die_error(undef, "Open git-rev-list failed");
+       while (my $line = <$fd>) {
+               my %co = parse_commit_text($line);
+               push @cos, \%co;
+       }
+       close $fd;
+
+       return wantarray ? @cos : \@cos;
+}
+
 # parse ref from ref_file, given by ref_id, with given type
 sub parse_ref {
        my $ref_file = shift;
@@ -1602,7 +1690,7 @@ sub git_header_html {
 
        my $title = "$site_name";
        if (defined $project) {
-               $title .= " - $project";
+               $title .= " - " . to_utf8($project);
                if (defined $action) {
                        $title .= "/$action";
                        if (defined $file_name) {
@@ -1627,6 +1715,7 @@ sub git_header_html {
        }
        print $cgi->header(-type=>$content_type, -charset => 'utf-8',
                           -status=> $status, -expires => $expires);
+       my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : '';
        print <<EOF;
 <?xml version="1.0" encoding="utf-8"?>
 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
@@ -1635,7 +1724,7 @@ sub git_header_html {
 <!-- git core binaries version $git_version -->
 <head>
 <meta http-equiv="content-type" content="$content_type; charset=utf-8"/>
-<meta name="generator" content="gitweb/$version git/$git_version"/>
+<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/>
 <meta name="robots" content="index, nofollow"/>
 <title>$title</title>
 EOF
@@ -1650,14 +1739,17 @@ EOF
                }
        }
        if (defined $project) {
-               printf('<link rel="alternate" title="%s log" '.
-                      'href="%s" type="application/rss+xml"/>'."\n",
+               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 Atom feed" '.
+                      'href="%s" type="application/atom+xml" />'."\n",
+                      esc_param($project), href(action=>"atom"));
        } else {
                printf('<link rel="alternate" title="%s projects list" '.
                       'href="%s" type="text/plain; charset=utf-8"/>'."\n",
                       $site_name, href(project=>undef, action=>"project_index"));
-               printf('<link rel="alternate" title="%s projects logs" '.
+               printf('<link rel="alternate" title="%s projects feeds" '.
                       'href="%s" type="text/x-opml"/>'."\n",
                       $site_name, href(project=>undef, action=>"opml"));
        }
@@ -1685,6 +1777,9 @@ EOF
                        print " / $action";
                }
                print "\n";
+       }
+       my ($have_search) = gitweb_check_feature('search');
+       if ((defined $project) && ($have_search)) {
                if (!defined $searchtext) {
                        $searchtext = "";
                }
@@ -1723,7 +1818,9 @@ sub git_footer_html {
                        print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n";
                }
                print $cgi->a({-href => href(action=>"rss"),
-                             -class => "rss_logo"}, "RSS") . "\n";
+                             -class => "rss_logo"}, "RSS") . " ";
+               print $cgi->a({-href => href(action=>"atom"),
+                             -class => "rss_logo"}, "Atom") . "\n";
        } else {
                print $cgi->a({-href => href(project=>undef, action=>"opml"),
                              -class => "rss_logo"}, "OPML") . " ";
@@ -1866,7 +1963,7 @@ sub git_print_page_path {
 
        print "<div class=\"page_path\">";
        print $cgi->a({-href => href(action=>"tree", hash_base=>$hb),
-                     -title => 'tree root'}, "[$project]");
+                     -title => 'tree root'}, to_utf8("[$project]"));
        print " / ";
        if (defined $name) {
                my @dirname = split '/', $name;
@@ -1946,12 +2043,73 @@ sub git_print_log ($;%) {
        }
 }
 
+# return link target (what link points to)
+sub git_get_link_target {
+       my $hash = shift;
+       my $link_target;
+
+       # read link
+       open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
+               or return;
+       {
+               local $/;
+               $link_target = <$fd>;
+       }
+       close $fd
+               or return;
+
+       return $link_target;
+}
+
+# given link target, and the directory (basedir) the link is in,
+# return target of link relative to top directory (top tree);
+# return undef if it is not possible (including absolute links).
+sub normalize_link_target {
+       my ($link_target, $basedir, $hash_base) = @_;
+
+       # we can normalize symlink target only if $hash_base is provided
+       return unless $hash_base;
+
+       # absolute symlinks (beginning with '/') cannot be normalized
+       return if (substr($link_target, 0, 1) eq '/');
+
+       # normalize link target to path from top (root) tree (dir)
+       my $path;
+       if ($basedir) {
+               $path = $basedir . '/' . $link_target;
+       } else {
+               # we are in top (root) tree (dir)
+               $path = $link_target;
+       }
+
+       # remove //, /./, and /../
+       my @path_parts;
+       foreach my $part (split('/', $path)) {
+               # discard '.' and ''
+               next if (!$part || $part eq '.');
+               # handle '..'
+               if ($part eq '..') {
+                       if (@path_parts) {
+                               pop @path_parts;
+                       } else {
+                               # link leads outside repository (outside top dir)
+                               return;
+                       }
+               } else {
+                       push @path_parts, $part;
+               }
+       }
+       $path = join('/', @path_parts);
+
+       return $path;
+}
+
 # print tree entry (row of git_tree), but without encompassing <tr> element
 sub git_print_tree_entry {
        my ($t, $basedir, $hash_base, $have_blame) = @_;
 
        my %base_key = ();
-       $base_key{hash_base} = $hash_base if defined $hash_base;
+       $base_key{'hash_base'} = $hash_base if defined $hash_base;
 
        # The format of a table row is: mode list link.  Where mode is
        # the mode of the entry, list is the name of the entry, an href,
@@ -1962,16 +2120,31 @@ sub git_print_tree_entry {
                print "<td class=\"list\">" .
                        $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
                                               file_name=>"$basedir$t->{'name'}", %base_key),
-                               -class => "list"}, esc_path($t->{'name'})) . "</td>\n";
+                               -class => "list"}, esc_path($t->{'name'}));
+               if (S_ISLNK(oct $t->{'mode'})) {
+                       my $link_target = git_get_link_target($t->{'hash'});
+                       if ($link_target) {
+                               my $norm_target = normalize_link_target($link_target, $basedir, $hash_base);
+                               if (defined $norm_target) {
+                                       print " -> " .
+                                             $cgi->a({-href => href(action=>"object", hash_base=>$hash_base,
+                                                                    file_name=>$norm_target),
+                                                      -title => $norm_target}, esc_path($link_target));
+                               } else {
+                                       print " -> " . esc_path($link_target);
+                               }
+                       }
+               }
+               print "</td>\n";
                print "<td class=\"link\">";
                print $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
-                             "blob");
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "blob");
                if ($have_blame) {
                        print " | " .
                              $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
-                                                          file_name=>"$basedir$t->{'name'}", %base_key)},
-                                           "blame");
+                                                    file_name=>"$basedir$t->{'name'}", %base_key)},
+                                     "blame");
                }
                if (defined $hash_base) {
                        print " | " .
@@ -1993,8 +2166,8 @@ sub git_print_tree_entry {
                print "</td>\n";
                print "<td class=\"link\">";
                print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
-                                            file_name=>"$basedir$t->{'name'}", %base_key)},
-                             "tree");
+                                            file_name=>"$basedir$t->{'name'}", %base_key)},
+                             "tree");
                if (defined $hash_base) {
                        print " | " .
                              $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
@@ -2062,7 +2235,11 @@ sub git_difftree_body {
                                # link to patch
                                $patchno++;
                                print $cgi->a({-href => "#patch$patchno"}, "patch");
+                               print " | ";
                        }
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                                     "blob");
                        print "</td>\n";
 
                } elsif ($diff{'status'} eq "D") { # deleted
@@ -2082,13 +2259,11 @@ sub git_difftree_body {
                        }
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
                                                     hash_base=>$parent, file_name=>$diff{'file'})},
-                                     "blob") . " | ";
+                                     "blob") . " | ";
                        if ($have_blame) {
-                               print $cgi->a({-href =>
-                                                  href(action=>"blame",
-                                                       hash_base=>$parent,
-                                                       file_name=>$diff{'file'})},
-                                             "blame") . " | ";
+                               print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+                                                            file_name=>$diff{'file'})},
+                                             "blame") . " | ";
                        }
                        print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
                                                     file_name=>$diff{'file'})},
@@ -2099,7 +2274,7 @@ sub git_difftree_body {
                        my $mode_chnge = "";
                        if ($diff{'from_mode'} != $diff{'to_mode'}) {
                                $mode_chnge = "<span class=\"file_status mode_chnge\">[changed";
-                               if ($from_file_type != $to_file_type) {
+                               if ($from_file_type ne $to_file_type) {
                                        $mode_chnge .= " from $from_file_type to $to_file_type";
                                }
                                if (($from_mode_oct & 0777) != ($to_mode_oct & 0777)) {
@@ -2133,13 +2308,12 @@ sub git_difftree_body {
                                      " | ";
                        }
                        print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
-                                                    hash_base=>$hash, file_name=>$diff{'file'})},
-                                     "blob") . " | ";
+                                                    hash_base=>$hash, file_name=>$diff{'file'})},
+                                      "blob") . " | ";
                        if ($have_blame) {
-                               print $cgi->a({-href => href(action=>"blame",
-                                                            hash_base=>$hash,
-                                                            file_name=>$diff{'file'})},
-                                             "blame") . " | ";
+                               print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+                                                            file_name=>$diff{'file'})},
+                                             "blame") . " | ";
                        }
                        print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
                                                     file_name=>$diff{'file'})},
@@ -2178,17 +2352,16 @@ sub git_difftree_body {
                                              "diff") .
                                      " | ";
                        }
-                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
-                                                    hash_base=>$parent, file_name=>$diff{'from_file'})},
-                                     "blob") . " | ";
+                       print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+                                                    hash_base=>$parent, file_name=>$diff{'to_file'})},
+                                     "blob") . " | ";
                        if ($have_blame) {
-                               print $cgi->a({-href => href(action=>"blame",
-                                                            hash_base=>$hash,
-                                                            file_name=>$diff{'to_file'})},
-                                             "blame") . " | ";
+                               print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+                                                            file_name=>$diff{'to_file'})},
+                                             "blame") . " | ";
                        }
-                       print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
-                                                   file_name=>$diff{'from_file'})},
+                       print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
+                                                   file_name=>$diff{'to_file'})},
                                      "history");
                        print "</td>\n";
 
@@ -2202,31 +2375,55 @@ sub git_patchset_body {
        my ($fd, $difftree, $hash, $hash_parent) = @_;
 
        my $patch_idx = 0;
-       my $in_header = 0;
-       my $patch_found = 0;
+       my $patch_line;
        my $diffinfo;
        my (%from, %to);
 
        print "<div class=\"patchset\">\n";
 
-       LINE:
-       while (my $patch_line = <$fd>) {
+       # skip to first patch
+       while ($patch_line = <$fd>) {
                chomp $patch_line;
 
-               if ($patch_line =~ m/^diff /) { # "git diff" header
-                       # beginning of patch (in patchset)
-                       if ($patch_found) {
-                               # close extended header for previous empty patch
-                               if ($in_header) {
-                                       print "</div>\n" # class="diff extended_header"
-                               }
-                               # close previous patch
-                               print "</div>\n"; # class="patch"
-                       } else {
-                               # first patch in patchset
-                               $patch_found = 1;
+               last if ($patch_line =~ m/^diff /);
+       }
+
+ PATCH:
+       while ($patch_line) {
+               my @diff_header;
+               my ($from_id, $to_id);
+
+               # git diff header
+               #assert($patch_line =~ m/^diff /) if DEBUG;
+               #assert($patch_line !~ m!$/$!) if DEBUG; # is chomp-ed
+               push @diff_header, $patch_line;
+
+               # extended diff header
+       EXTENDED_HEADER:
+               while ($patch_line = <$fd>) {
+                       chomp $patch_line;
+
+                       last EXTENDED_HEADER if ($patch_line =~ m/^--- |^diff /);
+
+                       if ($patch_line =~ m/^index ([0-9a-fA-F]{40})..([0-9a-fA-F]{40})/) {
+                               $from_id = $1;
+                               $to_id   = $2;
                        }
-                       print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
+
+                       push @diff_header, $patch_line;
+               }
+               my $last_patch_line = $patch_line;
+
+               # check if current patch belong to current raw line
+               # and parse raw git-diff line if needed
+               if (defined $diffinfo &&
+                   $diffinfo->{'from_id'} eq $from_id &&
+                   $diffinfo->{'to_id'}   eq $to_id) {
+                       # this is split patch
+                       print "<div class=\"patch cont\">\n";
+               } else {
+                       # advance raw git-diff output if needed
+                       $patch_idx++ if defined $diffinfo;
 
                        # read and prepare patch information
                        if (ref($difftree->[$patch_idx]) eq "HASH") {
@@ -2241,106 +2438,126 @@ sub git_patchset_body {
                                $from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
                                                     hash=>$diffinfo->{'from_id'},
                                                     file_name=>$from{'file'});
+                       } else {
+                               delete $from{'href'};
                        }
                        if ($diffinfo->{'status'} ne "D") { # not deleted file
                                $to{'href'} = href(action=>"blob", hash_base=>$hash,
                                                   hash=>$diffinfo->{'to_id'},
                                                   file_name=>$to{'file'});
+                       } else {
+                               delete $to{'href'};
                        }
-                       $patch_idx++;
-
-                       # print "git diff" header
-                       $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 "<div class=\"diff extended_header\">\n";
-                       $in_header = 1;
-                       next LINE;
+                       # this is first patch for raw difftree line with $patch_idx index
+                       # we index @$difftree array from 0, but number patches from 1
+                       print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
                }
 
-               if ($in_header) {
-                       if ($patch_line !~ m/^---/) {
-                               # 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 <mode>
-                               if ($patch_line =~ m/\s(\d{6})$/) {
-                                       $patch_line .= '<span class="info"> (' .
-                                                      file_type_long($1) .
-                                                      ')</span>';
+               # print "git diff" header
+               $patch_line = shift @diff_header;
+               $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 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 <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/) {
+                               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;
                                }
-                               # match <hash>
-                               if ($patch_line =~ m/^index/) {
-                                       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'});
-                                       $patch_line =~ s!$from_id\.\.$to_id!$from_link..$to_link!;
+                               if ($to{'href'}) {
+                                       $to_link = $cgi->a({-href=>$to{'href'}, -class=>"hash"},
+                                                          substr($diffinfo->{'to_id'},0,7));
+                               } else {
+                                       $to_link = '0' x 7;
                                }
-                               print $patch_line . "<br/>\n";
+                               #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 "</div>\n"  if (@diff_header > 0); # class="diff extended_header"
 
-                       } else {
-                               #$in_header && $patch_line =~ m/^---/;
-                               print "</div>\n"; # class="diff extended_header"
-                               $in_header = 0;
+               # from-file/to-file diff header
+               $patch_line = $last_patch_line;
+               if (! $patch_line) {
+                       print "</div>\n"; # class="patch"
+                       last PATCH;
+               }
+               next PATCH if ($patch_line =~ m/^diff /);
+               #assert($patch_line =~ m/^---/) if DEBUG;
+               if ($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";
 
-                               if ($from{'href'}) {
-                                       $patch_line = '--- a/' .
-                                                     $cgi->a({-href=>$from{'href'}, -class=>"path"},
-                                                             esc_path($from{'file'}));
-                               }
-                               print "<div class=\"diff from_file\">$patch_line</div>\n";
+               $patch_line = <$fd>;
+               chomp $patch_line;
 
-                               $patch_line = <$fd>;
-                               chomp $patch_line;
+               #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";
 
-                               #$patch_line =~ m/^+++/;
-                               if ($to{'href'}) {
-                                       $patch_line = '+++ b/' .
-                                                     $cgi->a({-href=>$to{'href'}, -class=>"path"},
-                                                             esc_path($to{'file'}));
-                               }
-                               print "<div class=\"diff to_file\">$patch_line</div>\n";
+               # the patch itself
+       LINE:
+               while ($patch_line = <$fd>) {
+                       chomp $patch_line;
 
-                       }
+                       next PATCH if ($patch_line =~ m/^diff /);
 
-                       next LINE;
+                       print format_diff_line($patch_line, \%from, \%to);
                }
 
-               print format_diff_line($patch_line);
+       } continue {
+               print "</div>\n"; # class="patch"
        }
-       print "</div>\n" if $in_header; # extended header
-
-       print "</div>\n" if $patch_found; # class="patch"
 
        print "</div>\n"; # class="patchset"
 }
@@ -2361,6 +2578,7 @@ 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'} = to_utf8($descr);
                        $pr->{'descr'} = chop_str($descr, 25, 5);
                }
                if (!defined $pr->{'owner'}) {
@@ -2396,7 +2614,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'project'),
-                                      -class => "header"}, "Project") .
+                                      -class => "header"}, "Project") .
                              "</th>\n";
                }
                if ($order eq "descr") {
@@ -2405,7 +2623,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'descr'),
-                                      -class => "header"}, "Description") .
+                                      -class => "header"}, "Description") .
                              "</th>\n";
                }
                if ($order eq "owner") {
@@ -2414,7 +2632,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'owner'),
-                                      -class => "header"}, "Owner") .
+                                      -class => "header"}, "Owner") .
                              "</th>\n";
                }
                if ($order eq "age") {
@@ -2423,7 +2641,7 @@ sub git_project_list_body {
                } else {
                        print "<th>" .
                              $cgi->a({-href => href(project=>undef, order=>'age'),
-                                      -class => "header"}, "Last Change") .
+                                      -class => "header"}, "Last Change") .
                              "</th>\n";
                }
                print "<th></th>\n" .
@@ -2448,7 +2666,9 @@ sub git_project_list_body {
                }
                print "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
                                        -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" .
-                     "<td>" . esc_html($pr->{'descr'}) . "</td>\n" .
+                     "<td>" . $cgi->a({-href => href(project=>$pr->{'path'}, action=>"summary"),
+                                       -class => "list", -title => $pr->{'descr_long'}},
+                                       esc_html($pr->{'descr'})) . "</td>\n" .
                      "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n";
                print "<td class=\"". age_class($pr->{'age'}) . "\">" .
                      $pr->{'age_string'} . "</td>\n" .
@@ -2474,18 +2694,19 @@ sub git_project_list_body {
 
 sub git_shortlog_body {
        # uses global variable $project
-       my ($revlist, $from, $to, $refs, $extra) = @_;
+       my ($commitlist, $from, $to, $refs, $extra) = @_;
+
+       my $have_snapshot = gitweb_have_snapshot();
 
        $from = 0 unless defined $from;
-       $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
+       $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
 
        print "<table class=\"shortlog\" cellspacing=\"0\">\n";
        my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
-               my $commit = $revlist->[$i];
-               #my $ref = defined $refs ? format_ref_marker($refs, $commit) : '';
+               my %co = %{$commitlist->[$i]};
+               my $commit = $co{'id'};
                my $ref = format_ref_marker($refs, $commit);
-               my %co = parse_commit($commit);
                if ($alternate) {
                        print "<tr class=\"dark\">\n";
                } else {
@@ -2503,7 +2724,7 @@ 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 (gitweb_have_snapshot()) {
+               if ($have_snapshot) {
                        print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
                }
                print "</td>\n" .
@@ -2519,23 +2740,19 @@ sub git_shortlog_body {
 
 sub git_history_body {
        # Warning: assumes constant type (blob or tree) during history
-       my ($revlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
+       my ($commitlist, $from, $to, $refs, $hash_base, $ftype, $extra) = @_;
 
        $from = 0 unless defined $from;
-       $to = $#{$revlist} unless (defined $to && $to <= $#{$revlist});
+       $to = $#{$commitlist} unless (defined $to && $to <= $#{$commitlist});
 
        print "<table class=\"history\" cellspacing=\"0\">\n";
        my $alternate = 1;
        for (my $i = $from; $i <= $to; $i++) {
-               if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
-                       next;
-               }
-
-               my $commit = $1;
-               my %co = parse_commit($commit);
+               my %co = %{$commitlist->[$i]};
                if (!%co) {
                        next;
                }
+               my $commit = $co{'id'};
 
                my $ref = format_ref_marker($refs, $commit);
 
@@ -2603,8 +2820,12 @@ sub git_tags_body {
                        print "<tr class=\"light\">\n";
                }
                $alternate ^= 1;
-               print "<td><i>$tag{'age'}</i></td>\n" .
-                     "<td>" .
+               if (defined $tag{'age'}) {
+                       print "<td><i>$tag{'age'}</i></td>\n";
+               } else {
+                       print "<td></td>\n";
+               }
+               print "<td>" .
                      $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}),
                               -class => "list name"}, esc_html($tag{'name'})) .
                      "</td>\n" .
@@ -2678,6 +2899,58 @@ sub git_heads_body {
        print "</table>\n";
 }
 
+sub git_search_grep_body {
+       my ($commitlist, $from, $to, $extra) = @_;
+       $from = 0 unless defined $from;
+       $to = $#{$commitlist} if (!defined $to || $#{$commitlist} < $to);
+
+       print "<table class=\"grep\" cellspacing=\"0\">\n";
+       my $alternate = 1;
+       for (my $i = $from; $i <= $to; $i++) {
+               my %co = %{$commitlist->[$i]};
+               if (!%co) {
+                       next;
+               }
+               my $commit = $co{'id'};
+               if ($alternate) {
+                       print "<tr class=\"dark\">\n";
+               } else {
+                       print "<tr class=\"light\">\n";
+               }
+               $alternate ^= 1;
+               print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
+                     "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
+                     "<td>" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
+                              esc_html(chop_str($co{'title'}, 50)) . "<br/>");
+               my $comment = $co{'comment'};
+               foreach my $line (@$comment) {
+                       if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
+                               my $lead = esc_html($1) || "";
+                               $lead = chop_str($lead, 30, 10);
+                               my $match = esc_html($2) || "";
+                               my $trail = esc_html($3) || "";
+                               $trail = chop_str($trail, 30, 10);
+                               my $text = "$lead<span class=\"match\">$match</span>$trail";
+                               print chop_str($text, 80, 5) . "<br/>\n";
+                       }
+               }
+               print "</td>\n" .
+                     "<td class=\"link\">" .
+                     $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
+                     " | " .
+                     $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
+               print "</td>\n" .
+                     "</tr>\n";
+       }
+       if (defined $extra) {
+               print "<tr>\n" .
+                     "<td colspan=\"3\">$extra</td>\n" .
+                     "</tr>\n";
+       }
+       print "</table>\n";
+}
+
 ## ======================================================================
 ## ======================================================================
 ## actions
@@ -2733,7 +3006,7 @@ sub git_project_index {
 
        foreach my $pr (@projects) {
                if (!exists $pr->{'owner'}) {
-                       $pr->{'owner'} = get_file_owner("$projectroot/$project");
+                       $pr->{'owner'} = get_file_owner("$projectroot/$pr->{'path'}");
                }
 
                my ($path, $owner) = ($pr->{'path'}, $pr->{'owner'});
@@ -2749,15 +3022,17 @@ sub git_project_index {
 
 sub git_summary {
        my $descr = git_get_project_description($project) || "none";
-       my $head = git_get_head_hash($project);
-       my %co = parse_commit($head);
+       my %co = parse_commit("HEAD");
        my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
+       my $head = $co{'id'};
 
        my $owner = git_get_project_owner($project);
 
        my $refs = git_get_references();
-       my @taglist  = git_get_tags_list(15);
-       my @headlist = git_get_heads_list(15);
+       # These get_*_list functions return one more to allow us to see if
+       # there are more ...
+       my @taglist  = git_get_tags_list(16);
+       my @headlist = git_get_heads_list(16);
        my @forklist;
        my ($check_forks) = gitweb_check_feature('forks');
 
@@ -2793,30 +3068,32 @@ sub git_summary {
                }
        }
 
-       open my $fd, "-|", git_cmd(), "rev-list", "--max-count=17",
-               git_get_head_hash($project), "--"
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd;
+       # we need to request one more than 16 (0..15) to check if
+       # those 16 are all
+       my @commitlist = parse_commits($head, 17);
        git_print_header_div('shortlog');
-       git_shortlog_body(\@revlist, 0, 15, $refs,
+       git_shortlog_body(\@commitlist, 0, 15, $refs,
+                         $#commitlist <=  15 ? undef :
                          $cgi->a({-href => href(action=>"shortlog")}, "..."));
 
        if (@taglist) {
                git_print_header_div('tags');
                git_tags_body(\@taglist, 0, 15,
+                             $#taglist <=  15 ? undef :
                              $cgi->a({-href => href(action=>"tags")}, "..."));
        }
 
        if (@headlist) {
                git_print_header_div('heads');
                git_heads_body(\@headlist, $head, 0, 15,
+                              $#headlist <= 15 ? undef :
                               $cgi->a({-href => href(action=>"heads")}, "..."));
        }
 
        if (@forklist) {
                git_print_header_div('forks');
                git_project_list_body(\@forklist, undef, 0, 15,
+                                     $#forklist <= 15 ? undef :
                                      $cgi->a({-href => href(action=>"forks")}, "..."),
                                      'noheader');
        }
@@ -2851,8 +3128,8 @@ sub git_tag {
        print "<div class=\"page_body\">";
        my $comment = $tag{'comment'};
        foreach my $line (@$comment) {
-               chomp($line);
-               print esc_html($line) . "<br/>\n";
+               chomp $line;
+               print esc_html($line, -nbsp=>1) . "<br/>\n";
        }
        print "</div>\n";
        git_footer_html();
@@ -2921,7 +3198,7 @@ HTML
                        }
                }
                my $data = $_;
-               chomp($data);
+               chomp $data;
                my $rev = substr($full_rev, 0, 8);
                my $author = $meta->{'author'};
                my %date = parse_date($meta->{'author-time'},
@@ -2942,9 +3219,14 @@ HTML
                                      esc_html($rev));
                        print "</td>\n";
                }
+               open (my $dd, "-|", git_cmd(), "rev-parse", "$full_rev^")
+                       or die_error("could not open git-rev-parse");
+               my $parent_commit = <$dd>;
+               close $dd;
+               chomp($parent_commit);
                my $blamed = href(action => 'blame',
                                  file_name => $meta->{'filename'},
-                                 hash_base => $full_rev);
+                                 hash_base => $parent_commit);
                print "<td class=\"linenr\">";
                print $cgi->a({ -href => "$blamed#l$orig_lineno",
                                -id => "l$lineno",
@@ -3146,10 +3428,13 @@ sub git_blob {
        open my $fd, "-|", git_cmd(), "cat-file", "blob", $hash
                or die_error(undef, "Couldn't cat $file_name, $hash");
        my $mimetype = blob_mimetype($fd, $file_name);
-       if ($mimetype !~ m/^text\//) {
+       if ($mimetype !~ m!^(?:text/|image/(?:gif|png|jpeg)$)!) {
                close $fd;
                return git_blob_plain($mimetype);
        }
+       # we can have blame only for text/* mimetype
+       $have_blame &&= ($mimetype =~ m!^text/!);
+
        git_header_html(undef, $expires);
        my $formats_nav = '';
        if (defined $hash_base && (my %co = parse_commit($hash_base))) {
@@ -3186,13 +3471,24 @@ sub git_blob {
        }
        git_print_page_path($file_name, "blob", $hash_base);
        print "<div class=\"page_body\">\n";
-       my $nr;
-       while (my $line = <$fd>) {
-               chomp $line;
-               $nr++;
-               $line = untabify($line);
-               printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
-                      $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+       if ($mimetype =~ m!^text/!) {
+               my $nr;
+               while (my $line = <$fd>) {
+                       chomp $line;
+                       $nr++;
+                       $line = untabify($line);
+                       printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n",
+                              $nr, $nr, $nr, esc_html($line, -nbsp=>1);
+               }
+       } elsif ($mimetype =~ m!^image/!) {
+               print qq!<img type="$mimetype"!;
+               if ($file_name) {
+                       print qq! alt="$file_name" title="$file_name"!;
+               }
+               print qq! src="! .
+                     href(action=>"blob_plain", hash=>$hash,
+                          hash_base=>$hash_base, file_name=>$file_name) .
+                     qq!" />\n!;
        }
        close $fd
                or print "Reading blob failed.\n";
@@ -3314,11 +3610,10 @@ sub git_snapshot {
                $hash = git_get_head_hash($project);
        }
 
-       my $filename = basename($project) . "-$hash.tar.$suffix";
+       my $filename = to_utf8(basename($project)) . "-$hash.tar.$suffix";
 
        print $cgi->header(
-               -type => 'application/x-tar',
-               -content_encoding => $ctype,
+               -type => "application/$ctype",
                -content_disposition => 'inline; filename="' . "$filename" . '"',
                -status => '200 OK');
 
@@ -3345,28 +3640,25 @@ sub git_log {
        }
        my $refs = git_get_references();
 
-       my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd;
+       my @commitlist = parse_commits($hash, 101, (100 * $page));
 
-       my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist);
+       my $paging_nav = format_paging_nav('log', $hash, $head, $page, (100 * ($page+1)));
 
        git_header_html();
        git_print_page_nav('log','', $hash,undef,undef, $paging_nav);
 
-       if (!@revlist) {
+       if (!@commitlist) {
                my %co = parse_commit($hash);
 
                git_print_header_div('summary', $project);
                print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n";
        }
-       for (my $i = ($page * 100); $i <= $#revlist; $i++) {
-               my $commit = $revlist[$i];
-               my $ref = format_ref_marker($refs, $commit);
-               my %co = parse_commit($commit);
+       my $to = ($#commitlist >= 99) ? (99) : ($#commitlist);
+       for (my $i = 0; $i <= $to; $i++) {
+               my %co = %{$commitlist[$i]};
                next if !%co;
+               my $commit = $co{'id'};
+               my $ref = format_ref_marker($refs, $commit);
                my %ad = parse_date($co{'author_epoch'});
                git_print_header_div('commit',
                               "<span class=\"age\">$co{'age_string'}</span>" .
@@ -3388,10 +3680,17 @@ sub git_log {
                git_print_log($co{'comment'}, -final_empty_line=> 1);
                print "</div>\n";
        }
+       if ($#commitlist >= 100) {
+               print "<div class=\"page_nav\">\n";
+               print $cgi->a({-href => href(action=>"log", hash=>$hash, page=>$page+1),
+                              -accesskey => "n", -title => "Alt-n"}, "next");
+               print "</div>\n";
+       }
        git_footer_html();
 }
 
 sub git_commit {
+       $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash);
        if (!%co) {
                die_error(undef, "Unknown commit object");
@@ -3399,15 +3698,46 @@ sub git_commit {
        my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
        my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'});
 
-       my $parent = $co{'parent'};
+       my $parent  = $co{'parent'};
+       my $parents = $co{'parents'}; # listref
+
+       # we need to prepare $formats_nav before any parameter munging
+       my $formats_nav;
+       if (!defined $parent) {
+               # --root commitdiff
+               $formats_nav .= '(initial)';
+       } elsif (@$parents == 1) {
+               # single parent commit
+               $formats_nav .=
+                       '(parent: ' .
+                       $cgi->a({-href => href(action=>"commit",
+                                              hash=>$parent)},
+                               esc_html(substr($parent, 0, 7))) .
+                       ')';
+       } else {
+               # merge commit
+               $formats_nav .=
+                       '(merge: ' .
+                       join(' ', map {
+                               $cgi->a({-href => href(action=>"commitdiff",
+                                                      hash=>$_)},
+                                       esc_html(substr($_, 0, 7)));
+                       } @$parents ) .
+                       ')';
+       }
+
        if (!defined $parent) {
                $parent = "--root";
        }
-       open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
-               @diff_opts, $parent, $hash, "--"
-               or die_error(undef, "Open git-diff-tree failed");
-       my @difftree = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading git-diff-tree failed");
+       my @difftree;
+       if (@$parents <= 1) {
+               # difftree output is not printed for merges
+               open my $fd, "-|", git_cmd(), "diff-tree", '-r', "--no-commit-id",
+                       @diff_opts, $parent, $hash, "--"
+                               or die_error(undef, "Open git-diff-tree failed");
+               @difftree = map { chomp; $_ } <$fd>;
+               close $fd or die_error(undef, "Reading git-diff-tree failed");
+       }
 
        # non-textual hash id's can be cached
        my $expires;
@@ -3419,16 +3749,10 @@ sub git_commit {
 
        my $have_snapshot = gitweb_have_snapshot();
 
-       my @views_nav = ();
-       if (defined $file_name && defined $co{'parent'}) {
-               push @views_nav,
-                       $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
-                               "blame");
-       }
        git_header_html(undef, $expires);
        git_print_page_nav('commit', '',
                           $hash, $co{'tree'}, $hash,
-                          join (' | ', @views_nav));
+                          $formats_nav);
 
        if (defined $co{'parent'}) {
                git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash);
@@ -3469,7 +3793,7 @@ sub git_commit {
        }
        print "</td>" .
              "</tr>\n";
-       my $parents = $co{'parents'};
+
        foreach my $par (@$parents) {
                print "<tr>" .
                      "<td>parent</td>" .
@@ -3491,11 +3815,61 @@ sub git_commit {
        git_print_log($co{'comment'});
        print "</div>\n";
 
-       git_difftree_body(\@difftree, $hash, $parent);
+       if (@$parents <= 1) {
+               # do not output difftree/whatchanged for merges
+               git_difftree_body(\@difftree, $hash, $parent);
+       }
 
        git_footer_html();
 }
 
+sub git_object {
+       # object is defined by:
+       # - hash or hash_base alone
+       # - hash_base and file_name
+       my $type;
+
+       # - hash or hash_base alone
+       if ($hash || ($hash_base && !defined $file_name)) {
+               my $object_id = $hash || $hash_base;
+
+               my $git_command = git_cmd_str();
+               open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null"
+                       or die_error('404 Not Found', "Object does not exist");
+               $type = <$fd>;
+               chomp $type;
+               close $fd
+                       or die_error('404 Not Found', "Object does not exist");
+
+       # - hash_base and file_name
+       } elsif ($hash_base && defined $file_name) {
+               $file_name =~ s,/+$,,;
+
+               system(git_cmd(), "cat-file", '-e', $hash_base) == 0
+                       or die_error('404 Not Found', "Base object does not exist");
+
+               # here errors should not hapen
+               open my $fd, "-|", git_cmd(), "ls-tree", $hash_base, "--", $file_name
+                       or die_error(undef, "Open git-ls-tree failed");
+               my $line = <$fd>;
+               close $fd;
+
+               #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa  panic.c'
+               unless ($line && $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t/) {
+                       die_error('404 Not Found', "File or directory for given base does not exist");
+               }
+               $type = $2;
+               $hash = $3;
+       } else {
+               die_error('404 Not Found', "Not enough information to find object");
+       }
+
+       print $cgi->redirect(-uri => href(action=>$type, -full=>1,
+                                         hash=>$hash, hash_base=>$hash_base,
+                                         file_name=>$file_name),
+                            -status => '302 Found');
+}
+
 sub git_blobdiff {
        my $format = shift || 'html';
 
@@ -3669,6 +4043,7 @@ sub git_blobdiff_plain {
 
 sub git_commitdiff {
        my $format = shift || 'html';
+       $hash ||= $hash_base || "HEAD";
        my %co = parse_commit($hash);
        if (!%co) {
                die_error(undef, "Unknown commit object");
@@ -3731,7 +4106,8 @@ sub git_commitdiff {
                        $hash_parent, $hash, "--"
                        or die_error(undef, "Open git-diff-tree failed");
 
-               while (chomp(my $line = <$fd>)) {
+               while (my $line = <$fd>) {
+                       chomp $line;
                        # empty line ends raw part of diff-tree output
                        last unless $line;
                        push @difftree, $line;
@@ -3838,12 +4214,7 @@ sub git_history {
                $ftype = git_get_type($hash);
        }
 
-       open my $fd, "-|",
-               git_cmd(), "rev-list", $limit, "--full-history", $hash_base, "--", $file_name
-                       or die_error(undef, "Open git-rev-list-failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd
-               or die_error(undef, "Reading git-rev-list failed");
+       my @commitlist = parse_commits($hash_base, 101, (100 * $page), "--full-history", $file_name);
 
        my $paging_nav = '';
        if ($page > 0) {
@@ -3859,7 +4230,7 @@ sub git_history {
                $paging_nav .= "first";
                $paging_nav .= " &sdot; prev";
        }
-       if ($#revlist >= (100 * ($page+1)-1)) {
+       if ($#commitlist >= 100) {
                $paging_nav .= " &sdot; " .
                        $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
                                               file_name=>$file_name, page=>$page+1),
@@ -3868,11 +4239,11 @@ sub git_history {
                $paging_nav .= " &sdot; next";
        }
        my $next_link = '';
-       if ($#revlist >= (100 * ($page+1)-1)) {
+       if ($#commitlist >= 100) {
                $next_link =
                        $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base,
                                               file_name=>$file_name, page=>$page+1),
-                                -title => "Alt-n"}, "next");
+                                -accesskey => "n", -title => "Alt-n"}, "next");
        }
 
        git_header_html();
@@ -3880,13 +4251,17 @@ sub git_history {
        git_print_header_div('commit', esc_html($co{'title'}), $hash_base);
        git_print_page_path($file_name, $ftype, $hash_base);
 
-       git_history_body(\@revlist, ($page * 100), $#revlist,
+       git_history_body(\@commitlist, 0, 99,
                         $refs, $hash_base, $ftype, $next_link);
 
        git_footer_html();
 }
 
 sub git_search {
+       my ($have_search) = gitweb_check_feature('search');
+       if (!$have_search) {
+               die_error('403 Permission denied', "Permission denied");
+       }
        if (!defined $searchtext) {
                die_error(undef, "Text field empty");
        }
@@ -3897,6 +4272,9 @@ sub git_search {
        if (!%co) {
                die_error(undef, "Unknown commit object");
        }
+       if (!defined $page) {
+               $page = 0;
+       }
 
        $searchtype ||= 'commit';
        if ($searchtype eq 'pickaxe') {
@@ -3909,66 +4287,63 @@ sub git_search {
        }
 
        git_header_html();
-       git_print_page_nav('','', $hash,$co{'tree'},$hash);
-       git_print_header_div('commit', esc_html($co{'title'}), $hash);
 
-       print "<table cellspacing=\"0\">\n";
-       my $alternate = 1;
        if ($searchtype eq 'commit' or $searchtype eq 'author' or $searchtype eq 'committer') {
-               $/ = "\0";
-               open my $fd, "-|", git_cmd(), "rev-list",
-                       "--header", "--parents", $hash, "--"
-                       or next;
-               while (my $commit_text = <$fd>) {
-                       if (!grep m/$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       if ($searchtype eq 'author' && !grep m/\nauthor .*$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       if ($searchtype eq 'committer' && !grep m/\ncommitter .*$searchtext/i, $commit_text) {
-                               next;
-                       }
-                       my @commit_lines = split "\n", $commit_text;
-                       my %co = parse_commit(undef, \@commit_lines);
-                       if (!%co) {
-                               next;
-                       }
-                       if ($alternate) {
-                               print "<tr class=\"dark\">\n";
-                       } else {
-                               print "<tr class=\"light\">\n";
-                       }
-                       $alternate ^= 1;
-                       print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" .
-                             "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 5)) . "</i></td>\n" .
-                             "<td>" .
-                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'}), -class => "list subject"},
-                                      esc_html(chop_str($co{'title'}, 50)) . "<br/>");
-                       my $comment = $co{'comment'};
-                       foreach my $line (@$comment) {
-                               if ($line =~ m/^(.*)($searchtext)(.*)$/i) {
-                                       my $lead = esc_html($1) || "";
-                                       $lead = chop_str($lead, 30, 10);
-                                       my $match = esc_html($2) || "";
-                                       my $trail = esc_html($3) || "";
-                                       $trail = chop_str($trail, 30, 10);
-                                       my $text = "$lead<span class=\"match\">$match</span>$trail";
-                                       print chop_str($text, 80, 5) . "<br/>\n";
-                               }
-                       }
-                       print "</td>\n" .
-                             "<td class=\"link\">" .
-                             $cgi->a({-href => href(action=>"commit", hash=>$co{'id'})}, "commit") .
-                             " | " .
-                             $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$co{'id'})}, "tree");
-                       print "</td>\n" .
-                             "</tr>\n";
+               my $greptype;
+               if ($searchtype eq 'commit') {
+                       $greptype = "--grep=";
+               } elsif ($searchtype eq 'author') {
+                       $greptype = "--author=";
+               } elsif ($searchtype eq 'committer') {
+                       $greptype = "--committer=";
                }
-               close $fd;
+               $greptype .= $searchtext;
+               my @commitlist = parse_commits($hash, 101, (100 * $page), $greptype);
+
+               my $paging_nav = '';
+               if ($page > 0) {
+                       $paging_nav .=
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype)},
+                                       "first");
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page-1),
+                                        -accesskey => "p", -title => "Alt-p"}, "prev");
+               } else {
+                       $paging_nav .= "first";
+                       $paging_nav .= " &sdot; prev";
+               }
+               if ($#commitlist >= 100) {
+                       $paging_nav .= " &sdot; " .
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
+               } else {
+                       $paging_nav .= " &sdot; next";
+               }
+               my $next_link = '';
+               if ($#commitlist >= 100) {
+                       $next_link =
+                               $cgi->a({-href => href(action=>"search", hash=>$hash,
+                                                      searchtext=>$searchtext, searchtype=>$searchtype,
+                                                      page=>$page+1),
+                                        -accesskey => "n", -title => "Alt-n"}, "next");
+               }
+
+               git_print_page_nav('','', $hash,$co{'tree'},$hash, $paging_nav);
+               git_print_header_div('commit', esc_html($co{'title'}), $hash);
+               git_search_grep_body(\@commitlist, 0, 99, $next_link);
        }
 
        if ($searchtype eq 'pickaxe') {
+               git_print_page_nav('','', $hash,$co{'tree'},$hash);
+               git_print_header_div('commit', esc_html($co{'title'}), $hash);
+
+               print "<table cellspacing=\"0\">\n";
+               my $alternate = 1;
                $/ = "\n";
                my $git_command = git_cmd_str();
                open my $fd, "-|", "$git_command rev-list $hash | " .
@@ -4023,8 +4398,9 @@ sub git_search {
                        }
                }
                close $fd;
+
+               print "</table>\n";
        }
-       print "</table>\n";
        git_footer_html();
 }
 
@@ -4063,95 +4439,253 @@ sub git_shortlog {
        }
        my $refs = git_get_references();
 
-       my $limit = sprintf("--max-count=%i", (100 * ($page+1)));
-       open my $fd, "-|", git_cmd(), "rev-list", $limit, $hash, "--"
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd;
+       my @commitlist = parse_commits($hash, 101, (100 * $page));
 
-       my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist);
+       my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, (100 * ($page+1)));
        my $next_link = '';
-       if ($#revlist >= (100 * ($page+1)-1)) {
+       if ($#commitlist >= 100) {
                $next_link =
                        $cgi->a({-href => href(action=>"shortlog", hash=>$hash, page=>$page+1),
-                                -title => "Alt-n"}, "next");
+                                -accesskey => "n", -title => "Alt-n"}, "next");
        }
 
-
        git_header_html();
        git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav);
        git_print_header_div('summary', $project);
 
-       git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link);
+       git_shortlog_body(\@commitlist, 0, 99, $refs, $next_link);
 
        git_footer_html();
 }
 
 ## ......................................................................
-## feeds (RSS, OPML)
+## feeds (RSS, Atom; OPML)
 
-sub git_rss {
-       # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
-       open my $fd, "-|", git_cmd(), "rev-list", "--max-count=150",
-               git_get_head_hash($project), "--"
-               or die_error(undef, "Open git-rev-list failed");
-       my @revlist = map { chomp; $_ } <$fd>;
-       close $fd or die_error(undef, "Reading git-rev-list failed");
-       print $cgi->header(-type => 'text/xml', -charset => 'utf-8');
-       print <<XML;
-<?xml version="1.0" encoding="utf-8"?>
+sub git_feed {
+       my $format = shift || 'atom';
+       my ($have_blame) = gitweb_check_feature('blame');
+
+       # Atom: http://www.atomenabled.org/developers/syndication/
+       # RSS:  http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ
+       if ($format ne 'rss' && $format ne 'atom') {
+               die_error(undef, "Unknown web feed format");
+       }
+
+       # 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 %latest_commit;
+       my %latest_date;
+       my $content_type = "application/$format+xml";
+       if (defined $cgi->http('HTTP_ACCEPT') &&
+                $cgi->Accept('text/xml') > $cgi->Accept($content_type)) {
+               # browser (feed reader) prefers text/xml
+               $content_type = 'text/xml';
+       }
+       if (defined($commitlist[0])) {
+               %latest_commit = %{$commitlist[0]};
+               %latest_date   = parse_date($latest_commit{'author_epoch'});
+               print $cgi->header(
+                       -type => $content_type,
+                       -charset => 'utf-8',
+                       -last_modified => $latest_date{'rfc2822'});
+       } else {
+               print $cgi->header(
+                       -type => $content_type,
+                       -charset => 'utf-8');
+       }
+
+       # Optimization: skip generating the body if client asks only
+       # for Last-Modified date.
+       return if ($cgi->request_method() eq 'HEAD');
+
+       # header variables
+       my $title = "$site_name - $project/$action";
+       my $feed_type = 'log';
+       if (defined $hash) {
+               $title .= " - '$hash'";
+               $feed_type = 'branch log';
+               if (defined $file_name) {
+                       $title .= " :: $file_name";
+                       $feed_type = 'history';
+               }
+       } elsif (defined $file_name) {
+               $title .= " - $file_name";
+               $feed_type = 'history';
+       }
+       $title .= " $feed_type";
+       my $descr = git_get_project_description($project);
+       if (defined $descr) {
+               $descr = esc_html($descr);
+       } else {
+               $descr = "$project " .
+                        ($format eq 'rss' ? 'RSS' : 'Atom') .
+                        " feed";
+       }
+       my $owner = git_get_project_owner($project);
+       $owner = esc_html($owner);
+
+       #header
+       my $alt_url;
+       if (defined $file_name) {
+               $alt_url = href(-full=>1, action=>"history", hash=>$hash, file_name=>$file_name);
+       } elsif (defined $hash) {
+               $alt_url = href(-full=>1, action=>"log", hash=>$hash);
+       } else {
+               $alt_url = href(-full=>1, action=>"summary");
+       }
+       print qq!<?xml version="1.0" encoding="utf-8"?>\n!;
+       if ($format eq 'rss') {
+               print <<XML;
 <rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
 <channel>
-<title>$project $my_uri $my_url</title>
-<link>${\esc_html("$my_url?p=$project;a=summary")}</link>
-<description>$project log</description>
-<language>en</language>
 XML
+               print "<title>$title</title>\n" .
+                     "<link>$alt_url</link>\n" .
+                     "<description>$descr</description>\n" .
+                     "<language>en</language>\n";
+       } elsif ($format eq 'atom') {
+               print <<XML;
+<feed xmlns="http://www.w3.org/2005/Atom">
+XML
+               print "<title>$title</title>\n" .
+                     "<subtitle>$descr</subtitle>\n" .
+                     '<link rel="alternate" type="text/html" href="' .
+                     $alt_url . '" />' . "\n" .
+                     '<link rel="self" type="' . $content_type . '" href="' .
+                     $cgi->self_url() . '" />' . "\n" .
+                     "<id>" . href(-full=>1) . "</id>\n" .
+                     # use project owner for feed author
+                     "<author><name>$owner</name></author>\n";
+               if (defined $favicon) {
+                       print "<icon>" . esc_url($favicon) . "</icon>\n";
+               }
+               if (defined $logo_url) {
+                       # not twice as wide as tall: 72 x 27 pixels
+                       print "<logo>" . esc_url($logo) . "</logo>\n";
+               }
+               if (! %latest_date) {
+                       # dummy date to keep the feed valid until commits trickle in:
+                       print "<updated>1970-01-01T00:00:00Z</updated>\n";
+               } else {
+                       print "<updated>$latest_date{'iso-8601'}</updated>\n";
+               }
+       }
 
-       for (my $i = 0; $i <= $#revlist; $i++) {
-               my $commit = $revlist[$i];
-               my %co = parse_commit($commit);
+       # contents
+       for (my $i = 0; $i <= $#commitlist; $i++) {
+               my %co = %{$commitlist[$i]};
+               my $commit = $co{'id'};
                # we read 150, we always show 30 and the ones more recent than 48 hours
-               if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) {
+               if (($i >= 20) && ((time - $co{'author_epoch'}) > 48*60*60)) {
                        last;
                }
-               my %cd = parse_date($co{'committer_epoch'});
-               open $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
-                       $co{'parent'}, $co{'id'}, "--"
+               my %cd = parse_date($co{'author_epoch'});
+
+               # get list of changed files
+               open my $fd, "-|", git_cmd(), "diff-tree", '-r', @diff_opts,
+                       $co{'parent'}, $co{'id'}, "--", (defined $file_name ? $file_name : ())
                        or next;
                my @difftree = map { chomp; $_ } <$fd>;
                close $fd
                        or next;
-               print "<item>\n" .
-                     "<title>" .
-                     sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) .
-                     "</title>\n" .
-                     "<author>" . esc_html($co{'author'}) . "</author>\n" .
-                     "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
-                     "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" .
-                     "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" .
-                     "<description>" . esc_html($co{'title'}) . "</description>\n" .
-                     "<content:encoded>" .
-                     "<![CDATA[\n";
+
+               # print element (entry, item)
+               my $co_url = href(-full=>1, action=>"commit", hash=>$commit);
+               if ($format eq 'rss') {
+                       print "<item>\n" .
+                             "<title>" . esc_html($co{'title'}) . "</title>\n" .
+                             "<author>" . esc_html($co{'author'}) . "</author>\n" .
+                             "<pubDate>$cd{'rfc2822'}</pubDate>\n" .
+                             "<guid isPermaLink=\"true\">$co_url</guid>\n" .
+                             "<link>$co_url</link>\n" .
+                             "<description>" . esc_html($co{'title'}) . "</description>\n" .
+                             "<content:encoded>" .
+                             "<![CDATA[\n";
+               } elsif ($format eq 'atom') {
+                       print "<entry>\n" .
+                             "<title type=\"html\">" . esc_html($co{'title'}) . "</title>\n" .
+                             "<updated>$cd{'iso-8601'}</updated>\n" .
+                             "<author>\n" .
+                             "  <name>" . esc_html($co{'author_name'}) . "</name>\n";
+                       if ($co{'author_email'}) {
+                               print "  <email>" . esc_html($co{'author_email'}) . "</email>\n";
+                       }
+                       print "</author>\n" .
+                             # use committer for contributor
+                             "<contributor>\n" .
+                             "  <name>" . esc_html($co{'committer_name'}) . "</name>\n";
+                       if ($co{'committer_email'}) {
+                               print "  <email>" . esc_html($co{'committer_email'}) . "</email>\n";
+                       }
+                       print "</contributor>\n" .
+                             "<published>$cd{'iso-8601'}</published>\n" .
+                             "<link rel=\"alternate\" type=\"text/html\" href=\"$co_url\" />\n" .
+                             "<id>$co_url</id>\n" .
+                             "<content type=\"xhtml\" xml:base=\"" . esc_url($my_url) . "\">\n" .
+                             "<div xmlns=\"http://www.w3.org/1999/xhtml\">\n";
+               }
                my $comment = $co{'comment'};
+               print "<pre>\n";
                foreach my $line (@$comment) {
-                       $line = to_utf8($line);
-                       print "$line<br/>\n";
+                       $line = esc_html($line);
+                       print "$line\n";
                }
-               print "<br/>\n";
-               foreach my $line (@difftree) {
-                       if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) {
-                               next;
+               print "</pre><ul>\n";
+               foreach my $difftree_line (@difftree) {
+                       my %difftree = parse_difftree_raw_line($difftree_line);
+                       next if !$difftree{'from_id'};
+
+                       my $file = $difftree{'file'} || $difftree{'to_file'};
+
+                       print "<li>" .
+                             "[" .
+                             $cgi->a({-href => href(-full=>1, action=>"blobdiff",
+                                                    hash=>$difftree{'to_id'}, hash_parent=>$difftree{'from_id'},
+                                                    hash_base=>$co{'id'}, hash_parent_base=>$co{'parent'},
+                                                    file_name=>$file, file_parent=>$difftree{'from_file'}),
+                                     -title => "diff"}, 'D');
+                       if ($have_blame) {
+                               print $cgi->a({-href => href(-full=>1, action=>"blame",
+                                                            file_name=>$file, hash_base=>$commit),
+                                             -title => "blame"}, 'B');
+                       }
+                       # if this is not a feed of a file history
+                       if (!defined $file_name || $file_name ne $file) {
+                               print $cgi->a({-href => href(-full=>1, action=>"history",
+                                                            file_name=>$file, hash=>$commit),
+                                             -title => "history"}, 'H');
                        }
-                       my $file = esc_path(unquote($7));
-                       $file = to_utf8($file);
-                       print "$file<br/>\n";
+                       $file = esc_path($file);
+                       print "] ".
+                             "$file</li>\n";
+               }
+               if ($format eq 'rss') {
+                       print "</ul>]]>\n" .
+                             "</content:encoded>\n" .
+                             "</item>\n";
+               } elsif ($format eq 'atom') {
+                       print "</ul>\n</div>\n" .
+                             "</content>\n" .
+                             "</entry>\n";
                }
-               print "]]>\n" .
-                     "</content:encoded>\n" .
-                     "</item>\n";
        }
-       print "</channel></rss>";
+
+       # end of feed
+       if ($format eq 'rss') {
+               print "</channel>\n</rss>\n";
+       }       elsif ($format eq 'atom') {
+               print "</feed>\n";
+       }
+}
+
+sub git_rss {
+       git_feed('rss');
+}
+
+sub git_atom {
+       git_feed('atom');
 }
 
 sub git_opml {
diff --git a/grep.c b/grep.c
index 0fc078ec0ac42e39125c8e5a8f05a3b6ecae4fa3..fcc676230282e4c5bda4f9f97c5ae65c1a6beb30 100644 (file)
--- a/grep.c
+++ b/grep.c
@@ -1,5 +1,4 @@
 #include "cache.h"
-#include <regex.h>
 #include "grep.h"
 
 void append_grep_pattern(struct grep_opt *opt, const char *pat,
diff --git a/help.c b/help.c
index 0824c25226ad7b106e56d1b7c23fad40eed92749..b6674635a2e08197cc13aaa6acbe6290a303d6b3 100644 (file)
--- a/help.c
+++ b/help.c
@@ -3,12 +3,11 @@
  *
  * Builtin help-related commands (help, usage, version)
  */
-#include <sys/ioctl.h>
 #include "cache.h"
 #include "builtin.h"
 #include "exec_cmd.h"
 #include "common-cmds.h"
-
+#include <sys/ioctl.h>
 
 /* most GUI terminals set COLUMNS (although some don't export it) */
 static int term_columns(void)
@@ -169,8 +168,8 @@ static void list_common_cmds_help(void)
 
        puts("The most commonly used git commands are:");
        for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
-               printf("    %s", common_cmds[i].name);
-               mput_char(' ', longest - strlen(common_cmds[i].name) + 4);
+               printf("   %s   ", common_cmds[i].name);
+               mput_char(' ', longest - strlen(common_cmds[i].name));
                puts(common_cmds[i].help);
        }
        puts("(use 'git help -a' to get a list of all installed git commands)");
index 396552da022a1dca9c43738be4d87c664f31a06c..9f790a08e5d2760524ab7a34e98071062b3ed42f 100644 (file)
@@ -71,7 +71,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
        int posn = 0;
        struct object_request *obj_req = (struct object_request *)data;
        do {
-               ssize_t retval = write(obj_req->local,
+               ssize_t retval = xwrite(obj_req->local,
                                       (char *) ptr + posn, size - posn);
                if (retval < 0)
                        return posn;
@@ -175,7 +175,7 @@ static void start_object_request(struct object_request *obj_req)
        prevlocal = open(prevfile, O_RDONLY);
        if (prevlocal != -1) {
                do {
-                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
                        if (prev_read>0) {
                                if (fwrite_sha1_file(prev_buf,
                                                     1,
@@ -809,6 +809,7 @@ static int fetch_pack(struct alt_base *repo, unsigned char *sha1)
                return error("Unable to start request");
        }
 
+       target->pack_size = ftell(packfile);
        fclose(packfile);
 
        ret = move_temp_to_file(tmpfile, filename);
@@ -1002,7 +1003,6 @@ int main(int argc, const char **argv)
        int arg = 1;
        int rc = 0;
 
-       setup_ident();
        setup_git_directory();
        git_config(git_default_config);
 
@@ -1069,7 +1069,7 @@ int main(int argc, const char **argv)
                fprintf(stderr,
 "Some loose object were found to be corrupt, but they might be just\n"
 "a false '404 Not Found' error message sent with incorrect HTTP\n"
-"status code.  Suggest running git fsck-objects.\n");
+"status code.  Suggest running git-fsck.\n");
        }
        return rc;
 }
index ecefdfd4f8c9c17282f5cec10640343359278028..b128c0146c6f1d8ca69d802d15d6bc778e5780a6 100644 (file)
@@ -195,7 +195,7 @@ static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
        int posn = 0;
        struct transfer_request *request = (struct transfer_request *)data;
        do {
-               ssize_t retval = write(request->local_fileno,
+               ssize_t retval = xwrite(request->local_fileno,
                                       (char *) ptr + posn, size - posn);
                if (retval < 0)
                        return posn;
@@ -288,7 +288,7 @@ static void start_fetch_loose(struct transfer_request *request)
        prevlocal = open(prevfile, O_RDONLY);
        if (prevlocal != -1) {
                do {
-                       prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE);
+                       prev_read = xread(prevlocal, prev_buf, PREV_BUF_SIZE);
                        if (prev_read>0) {
                                if (fwrite_sha1_file(prev_buf,
                                                     1,
@@ -770,11 +770,14 @@ static void finish_request(struct transfer_request *request)
                                request->url, curl_errorstr);
                        remote->can_update_info_refs = 0;
                } else {
+                       off_t pack_size = ftell(request->local_stream);
+
                        fclose(request->local_stream);
                        request->local_stream = NULL;
                        if (!move_temp_to_file(request->tmpfile,
                                               request->filename)) {
                                target = (struct packed_git *)request->userData;
+                               target->pack_size = pack_size;
                                lst = &remote->packs;
                                while (*lst != target)
                                        lst = &((*lst)->next);
@@ -2296,7 +2299,6 @@ int main(int argc, char **argv)
        struct ref *ref;
 
        setup_git_directory();
-       setup_ident();
 
        remote = xcalloc(sizeof(*remote), 1);
 
diff --git a/http.h b/http.h
index 6e12e416889503553ae502be31b2e9ed9e7dc4dd..324fcf4f5482dc67c3f68df0be30fb0aa210401e 100644 (file)
--- a/http.h
+++ b/http.h
@@ -18,7 +18,7 @@
 #define curl_global_init(a) do { /* nothing */ } while(0)
 #endif
 
-#if LIBCURL_VERSION_NUM < 0x070c04
+#if (LIBCURL_VERSION_NUM < 0x070c04) || (LIBCURL_VERSION_NUM == 0x071000)
 #define NO_CURL_EASY_DUPHANDLE
 #endif
 
diff --git a/ident.c b/ident.c
index efec97ffd0a9f3aa6573f3476c749d38d6fc388c..bb03bddd34f2471ed130c499affc369758d4bfd9 100644 (file)
--- a/ident.c
+++ b/ident.c
@@ -7,9 +7,6 @@
  */
 #include "cache.h"
 
-#include <pwd.h>
-#include <netdb.h>
-
 static char git_default_date[50];
 
 static void copy_gecos(struct passwd *w, char *name, int sz)
@@ -46,19 +43,13 @@ static void copy_gecos(struct passwd *w, char *name, int sz)
 
 }
 
-int setup_ident(void)
+static void copy_email(struct passwd *pw)
 {
-       int len;
-       struct passwd *pw = getpwuid(getuid());
-
-       if (!pw)
-               die("You don't exist. Go away!");
-
-       /* Get the name ("gecos") */
-       copy_gecos(pw, git_default_name, sizeof(git_default_name));
-
-       /* Make up a fake email address (name + '@' + hostname [+ '.' + domainname]) */
-       len = strlen(pw->pw_name);
+       /*
+        * Make up a fake email address
+        * (name + '@' + hostname [+ '.' + domainname])
+        */
+       int len = strlen(pw->pw_name);
        if (len > sizeof(git_default_email)/2)
                die("Your sysadmin must hate you!");
        memcpy(git_default_email, pw->pw_name, len);
@@ -71,13 +62,37 @@ int setup_ident(void)
                len = strlen(git_default_email);
                git_default_email[len++] = '.';
                if (he && (domainname = strchr(he->h_name, '.')))
-                       strlcpy(git_default_email + len, domainname + 1, sizeof(git_default_email) - len);
+                       strlcpy(git_default_email + len, domainname + 1,
+                               sizeof(git_default_email) - len);
                else
-                       strlcpy(git_default_email + len, "(none)", sizeof(git_default_email) - len);
+                       strlcpy(git_default_email + len, "(none)",
+                               sizeof(git_default_email) - len);
        }
+}
+
+static void setup_ident(void)
+{
+       struct passwd *pw = NULL;
+
+       /* Get the name ("gecos") */
+       if (!git_default_name[0]) {
+               pw = getpwuid(getuid());
+               if (!pw)
+                       die("You don't exist. Go away!");
+               copy_gecos(pw, git_default_name, sizeof(git_default_name));
+       }
+
+       if (!git_default_email[0]) {
+               if (!pw)
+                       pw = getpwuid(getuid());
+               if (!pw)
+                       die("You don't exist. Go away!");
+               copy_email(pw);
+       }
+
        /* And set the default date */
-       datestamp(git_default_date, sizeof(git_default_date));
-       return 0;
+       if (!git_default_date[0])
+               datestamp(git_default_date, sizeof(git_default_date));
 }
 
 static int add_raw(char *buf, int size, int offset, const char *str)
@@ -158,32 +173,47 @@ static int copy(char *buf, int size, int offset, const char *src)
 static const char au_env[] = "GIT_AUTHOR_NAME";
 static const char co_env[] = "GIT_COMMITTER_NAME";
 static const char *env_hint =
-"\n*** Environment problem:\n"
+"\n"
 "*** Your name cannot be determined from your system services (gecos).\n"
-"*** You would need to set %s and %s\n"
-"*** environment variables; otherwise you won't be able to perform\n"
-"*** certain operations because of \"empty ident\" errors.\n"
-"*** Alternatively, you can use user.name configuration variable.\n\n";
-
-static const char *get_ident(const char *name, const char *email,
-                            const char *date_str, int error_on_no_name)
+"\n"
+"Run\n"
+"\n"
+"  git config user.email \"you@email.com\"\n"
+"  git config user.name \"Your Name\"\n"
+"\n"
+"To set the identity in this repository.\n"
+"Add --global to set your account\'s default\n"
+"\n";
+
+const char *fmt_ident(const char *name, const char *email,
+                     const char *date_str, int error_on_no_name)
 {
        static char buffer[1000];
        char date[50];
        int i;
 
+       setup_ident();
        if (!name)
                name = git_default_name;
        if (!email)
                email = git_default_email;
 
        if (!*name) {
-               if (name == git_default_name && env_hint) {
+               struct passwd *pw;
+
+               if (0 <= error_on_no_name &&
+                   name == git_default_name && env_hint) {
                        fprintf(stderr, env_hint, au_env, co_env);
                        env_hint = NULL; /* warn only once, for "git-var -l" */
                }
-               if (error_on_no_name)
+               if (0 < error_on_no_name)
                        die("empty ident %s <%s> not allowed", name, email);
+               pw = getpwuid(getuid());
+               if (!pw)
+                       die("You don't exist. Go away!");
+               strlcpy(git_default_name, pw->pw_name,
+                       sizeof(git_default_name));
+               name = git_default_name;
        }
 
        strcpy(date, git_default_date);
@@ -203,7 +233,7 @@ static const char *get_ident(const char *name, const char *email,
 
 const char *git_author_info(int error_on_no_name)
 {
-       return get_ident(getenv("GIT_AUTHOR_NAME"),
+       return fmt_ident(getenv("GIT_AUTHOR_NAME"),
                         getenv("GIT_AUTHOR_EMAIL"),
                         getenv("GIT_AUTHOR_DATE"),
                         error_on_no_name);
@@ -211,7 +241,7 @@ const char *git_author_info(int error_on_no_name)
 
 const char *git_committer_info(int error_on_no_name)
 {
-       return get_ident(getenv("GIT_COMMITTER_NAME"),
+       return fmt_ident(getenv("GIT_COMMITTER_NAME"),
                         getenv("GIT_COMMITTER_EMAIL"),
                         getenv("GIT_COMMITTER_DATE"),
                         error_on_no_name);
index a6a65680ee6daf062180d2580e83616cfff4cda4..3eaf025720c5432065b851ce98f861eee27eadee 100644 (file)
 
 #include "cache.h"
 
-#include <assert.h>
-#include <netinet/in.h>
-#include <netinet/tcp.h>
-#include <arpa/inet.h>
-#include <sys/socket.h>
-#include <netdb.h>
-
 typedef struct store_conf {
        char *name;
        const char *path; /* should this be here? its interpretation is driver-specific */
@@ -103,8 +96,8 @@ typedef struct {
 
 static int Verbose, Quiet;
 
-static void info( const char *, ... );
-static void warn( const char *, ... );
+static void imap_info( const char *, ... );
+static void imap_warn( const char *, ... );
 
 static char *next_arg( char ** );
 
@@ -231,7 +224,7 @@ socket_perror( const char *func, Socket_t *sock, int ret )
 static int
 socket_read( Socket_t *sock, char *buf, int len )
 {
-       int n = read( sock->fd, buf, len );
+       int n = xread( sock->fd, buf, len );
        if (n <= 0) {
                socket_perror( "read", sock, n );
                close( sock->fd );
@@ -243,7 +236,7 @@ socket_read( Socket_t *sock, char *buf, int len )
 static int
 socket_write( Socket_t *sock, const char *buf, int len )
 {
-       int n = write( sock->fd, buf, len );
+       int n = write_in_full( sock->fd, buf, len );
        if (n != len) {
                socket_perror( "write", sock, n );
                close( sock->fd );
@@ -304,7 +297,7 @@ buffer_gets( buffer_t * b, char **s )
 }
 
 static void
-info( const char *msg, ... )
+imap_info( const char *msg, ... )
 {
        va_list va;
 
@@ -317,7 +310,7 @@ info( const char *msg, ... )
 }
 
 static void
-warn( const char *msg, ... )
+imap_warn( const char *msg, ... )
 {
        va_list va;
 
@@ -397,7 +390,7 @@ arc4_init( void )
                fprintf( stderr, "Fatal: no random number source available.\n" );
                exit( 3 );
        }
-       if (read( fd, dat, 128 ) != 128) {
+       if (read_in_full( fd, dat, 128 ) != 128) {
                fprintf( stderr, "Fatal: cannot read random number source.\n" );
                exit( 3 );
        }
@@ -910,7 +903,7 @@ imap_open_store( imap_server_conf_t *srvc )
        /* open connection to IMAP server */
 
        if (srvc->tunnel) {
-               info( "Starting tunnel '%s'... ", srvc->tunnel );
+               imap_info( "Starting tunnel '%s'... ", srvc->tunnel );
 
                if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) {
                        perror( "socketpair" );
@@ -933,31 +926,31 @@ imap_open_store( imap_server_conf_t *srvc )
 
                imap->buf.sock.fd = a[1];
 
-               info( "ok\n" );
+               imap_info( "ok\n" );
        } else {
                memset( &addr, 0, sizeof(addr) );
                addr.sin_port = htons( srvc->port );
                addr.sin_family = AF_INET;
 
-               info( "Resolving %s... ", srvc->host );
+               imap_info( "Resolving %s... ", srvc->host );
                he = gethostbyname( srvc->host );
                if (!he) {
                        perror( "gethostbyname" );
                        goto bail;
                }
-               info( "ok\n" );
+               imap_info( "ok\n" );
 
                addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]);
 
                s = socket( PF_INET, SOCK_STREAM, 0 );
 
-               info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
+               imap_info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) );
                if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) {
                        close( s );
                        perror( "connect" );
                        goto bail;
                }
-               info( "ok\n" );
+               imap_info( "ok\n" );
 
                imap->buf.sock.fd = s;
 
@@ -986,7 +979,7 @@ imap_open_store( imap_server_conf_t *srvc )
 
        if (!preauth) {
 
-               info ("Logging in...\n");
+               imap_info ("Logging in...\n");
                if (!srvc->user) {
                        fprintf( stderr, "Skipping server %s, no user\n", srvc->host );
                        goto bail;
@@ -1013,7 +1006,7 @@ imap_open_store( imap_server_conf_t *srvc )
                        fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host );
                        goto bail;
                }
-               warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
+               imap_warn( "*** IMAP Warning *** Password is being sent in the clear\n" );
                if (imap_exec( ctx, NULL, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) {
                        fprintf( stderr, "IMAP error: LOGIN failed\n" );
                        goto bail;
index 8331d99a62a457cb341a834792aedf5de9c5625f..72e0962415d74c856917f6bb56e6fa2fea950c25 100644 (file)
@@ -6,8 +6,6 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
-#include <sys/time.h>
-#include <signal.h>
 
 static const char index_pack_usage[] =
 "git-index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
@@ -87,7 +85,7 @@ static unsigned display_progress(unsigned n, unsigned total, unsigned last_pc)
 static unsigned char input_buffer[4096];
 static unsigned long input_offset, input_len, consumed_bytes;
 static SHA_CTX input_ctx;
-static int input_fd, output_fd, mmap_fd;
+static int input_fd, output_fd, pack_fd;
 
 /* Discard current buffer used content. */
 static void flush(void)
@@ -96,7 +94,7 @@ static void flush(void)
                if (output_fd >= 0)
                        write_or_die(output_fd, input_buffer, input_offset);
                SHA1_Update(&input_ctx, input_buffer, input_offset);
-               memcpy(input_buffer, input_buffer + input_offset, input_len);
+               memmove(input_buffer, input_buffer + input_offset, input_len);
                input_offset = 0;
        }
 }
@@ -148,14 +146,14 @@ static const char *open_pack_file(const char *pack_name)
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                if (output_fd < 0)
                        die("unable to create %s: %s\n", pack_name, strerror(errno));
-               mmap_fd = output_fd;
+               pack_fd = output_fd;
        } else {
                input_fd = open(pack_name, O_RDONLY);
                if (input_fd < 0)
                        die("cannot open packfile '%s': %s",
                            pack_name, strerror(errno));
                output_fd = -1;
-               mmap_fd = input_fd;
+               pack_fd = input_fd;
        }
        SHA1_Init(&input_ctx);
        return pack_name;
@@ -268,7 +266,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        case OBJ_TAG:
                break;
        default:
-               bad_object(obj->offset, "bad object type %d", obj->type);
+               bad_object(obj->offset, "unknown object type %d", obj->type);
        }
        obj->hdr_size = consumed_bytes - obj->offset;
 
@@ -279,27 +277,25 @@ 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 pg_offset = from % getpagesize();
-       unsigned char *map, *data;
+       unsigned char *src, *data;
        z_stream stream;
        int st;
 
-       map = mmap(NULL, len + pg_offset, PROT_READ, MAP_PRIVATE,
-                  mmap_fd, from - pg_offset);
-       if (map == MAP_FAILED)
-               die("cannot mmap pack file: %s", strerror(errno));
+       src = xmalloc(len);
+       if (pread(pack_fd, src, len, from) != len)
+               die("cannot pread pack file: %s", strerror(errno));
        data = xmalloc(obj->size);
        memset(&stream, 0, sizeof(stream));
        stream.next_out = data;
        stream.avail_out = obj->size;
-       stream.next_in = map + pg_offset;
+       stream.next_in = src;
        stream.avail_in = len;
        inflateInit(&stream);
        while ((st = inflate(&stream, Z_FINISH)) == Z_OK);
        inflateEnd(&stream);
        if (st != Z_STREAM_END || stream.total_out != obj->size)
                die("serious inflate inconsistency");
-       munmap(map, len + pg_offset);
+       free(src);
        return data;
 }
 
@@ -642,7 +638,7 @@ static void readjust_pack_header_and_sha1(unsigned char *sha1)
        /* Rewrite pack header with updated object number */
        if (lseek(output_fd, 0, SEEK_SET) != 0)
                die("cannot seek back: %s", strerror(errno));
-       if (xread(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+       if (read_in_full(output_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
                die("cannot read pack header back: %s", strerror(errno));
        hdr.hdr_entries = htonl(nr_objects);
        if (lseek(output_fd, 0, SEEK_SET) != 0)
@@ -818,7 +814,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
                char buf[48];
                int len = snprintf(buf, sizeof(buf), "%s\t%s\n",
                                   report, sha1_to_hex(sha1));
-               xwrite(1, buf, len);
+               write_or_die(1, buf, len);
 
                /*
                 * Let's just mimic git-unpack-objects here and write
index 5d9d1889f088c64131cd3c116e6a16876ce95db8..f992ef77533737fe2ec52dc3b662cb6cacca0ea2 100644 (file)
@@ -2,8 +2,6 @@
  * Copyright 2006 Jon Loeliger
  */
 
-#include <string.h>
-
 #include "git-compat-util.h"
 #include "interpolate.h"
 
index 7b6875cce6c5a08db06e637abc48ce8f26216db4..7cfe8b3587c10bf0791c066a72482771e7daae77 100644 (file)
@@ -184,7 +184,7 @@ int fetch_ref(char *ref, unsigned char *sha1)
                fprintf(stderr, "cannot open %s\n", filename);
                return -1;
        }
-       if (read(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
+       if (read_in_full(ifd, hex, 40) != 40 || get_sha1_hex(hex, sha1)) {
                close(ifd);
                fprintf(stderr, "cannot read from %s\n", filename);
                return -1;
@@ -210,7 +210,6 @@ int main(int argc, const char **argv)
        char **commit_id;
        int arg = 1;
 
-       setup_ident();
        setup_git_directory();
        git_config(git_default_config);
 
index 2a2fea3cb6bd1de059e7c0f8c2008c5fe8376b93..4824f4dc026e7b3f978fe4e9b2154335359e9d2e 100644 (file)
@@ -1,7 +1,6 @@
 /*
  * Copyright (c) 2005, Junio C Hamano
  */
-#include <signal.h>
 #include "cache.h"
 
 static struct lock_file *lock_file_list;
@@ -28,9 +27,12 @@ static int lock_file(struct lock_file *lk, const char *path)
        sprintf(lk->filename, "%s.lock", path);
        fd = open(lk->filename, O_RDWR | O_CREAT | O_EXCL, 0666);
        if (0 <= fd) {
-               if (!lk->next) {
+               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);
                }
@@ -38,6 +40,8 @@ static int lock_file(struct lock_file *lk, const char *path)
                        return error("cannot fix permission bits on %s",
                                     lk->filename);
        }
+       else
+               lk->filename[0] = 0;
        return fd;
 }
 
@@ -45,7 +49,7 @@ int hold_lock_file_for_update(struct lock_file *lk, const char *path, int die_on
 {
        int fd = lock_file(lk, path);
        if (fd < 0 && die_on_error)
-               die("unable to create '%s': %s", path, strerror(errno));
+               die("unable to create '%s.lock': %s", path, strerror(errno));
        return fd;
 }
 
index 8787df5cc647dda24f09b077276cbe0aa054fc47..ac8619404797633436258dbf9d76f9f4aa323395 100644 (file)
@@ -2,6 +2,7 @@
 #include "diff.h"
 #include "commit.h"
 #include "log-tree.h"
+#include "reflog-walk.h"
 
 static void show_parents(struct commit *commit, int abbrev)
 {
@@ -101,6 +102,16 @@ static int append_signoff(char *buf, int buf_sz, int at, const char *signoff)
        return at;
 }
 
+static unsigned int digits_in_number(unsigned int number)
+{
+       unsigned int i = 10, result = 1;
+       while (i <= number) {
+               i *= 10;
+               result++;
+       }
+       return result;
+}
+
 void show_log(struct rev_info *opt, const char *sep)
 {
        static char this_header[16384];
@@ -114,6 +125,14 @@ void show_log(struct rev_info *opt, const char *sep)
 
        opt->loginfo = NULL;
        if (!opt->verbose_header) {
+               if (opt->left_right) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
                fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit), stdout);
                if (opt->parents)
                        show_parents(commit, abbrev_commit);
@@ -134,7 +153,7 @@ void show_log(struct rev_info *opt, const char *sep)
        if (*sep != '\n' && opt->commit_format == CMIT_FMT_ONELINE)
                extra = "\n";
        if (opt->shown_one && opt->commit_format != CMIT_FMT_ONELINE)
-               putchar('\n');
+               putchar(opt->diffopt.line_termination);
        opt->shown_one = 1;
 
        /*
@@ -146,7 +165,8 @@ void show_log(struct rev_info *opt, const char *sep)
                if (opt->total > 0) {
                        static char buffer[64];
                        snprintf(buffer, sizeof(buffer),
-                                       "Subject: [PATCH %d/%d] ",
+                                       "Subject: [PATCH %0*d/%d] ",
+                                       digits_in_number(opt->total),
                                        opt->nr, opt->total);
                        subject = buffer;
                } else if (opt->total == 0)
@@ -192,10 +212,20 @@ void show_log(struct rev_info *opt, const char *sep)
                        opt->diffopt.stat_sep = buffer;
                }
        } else {
-               printf("%s%s%s",
-                      diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
-                      opt->commit_format == CMIT_FMT_ONELINE ? "" : "commit ",
-                      diff_unique_abbrev(commit->object.sha1, abbrev_commit));
+               fputs(diff_get_color(opt->diffopt.color_diff, DIFF_COMMIT),
+                     stdout);
+               if (opt->commit_format != CMIT_FMT_ONELINE)
+                       fputs("commit ", stdout);
+               if (opt->left_right) {
+                       if (commit->object.flags & BOUNDARY)
+                               putchar('-');
+                       else if (commit->object.flags & SYMMETRIC_LEFT)
+                               putchar('<');
+                       else
+                               putchar('>');
+               }
+               fputs(diff_unique_abbrev(commit->object.sha1, abbrev_commit),
+                     stdout);
                if (opt->parents)
                        show_parents(commit, abbrev_commit);
                if (parent)
@@ -205,6 +235,15 @@ void show_log(struct rev_info *opt, const char *sep)
                printf("%s",
                       diff_get_color(opt->diffopt.color_diff, DIFF_RESET));
                putchar(opt->commit_format == CMIT_FMT_ONELINE ? ' ' : '\n');
+               if (opt->reflog_info) {
+                       show_reflog_message(opt->reflog_info,
+                                   opt->commit_format == CMIT_FMT_ONELINE,
+                                   opt->relative_date);
+                       if (opt->commit_format == CMIT_FMT_ONELINE) {
+                               printf("%s", sep);
+                               return;
+                       }
+               }
        }
 
        /*
@@ -243,9 +282,8 @@ int log_tree_diff_flush(struct rev_info *opt)
                    opt->commit_format != CMIT_FMT_ONELINE) {
                        int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
                        if ((pch & opt->diffopt.output_format) == pch)
-                               printf("---%c", opt->diffopt.line_termination);
-                       else
-                               putchar(opt->diffopt.line_termination);
+                               printf("---");
+                       putchar('\n');
                }
        }
        diff_flush(&opt->diffopt);
index fc9b148993027d4064257d4df8e63a12e1afc5d4..69dc1ebbf7685f3dd30dcca64974eabba8ee137b 100644 (file)
@@ -3,52 +3,6 @@
 #include "xdiff-interface.h"
 #include "blob.h"
 
-static void rm_temp_file(const char *filename)
-{
-       unlink(filename);
-       free((void *)filename);
-}
-
-static const char *write_temp_file(mmfile_t *f)
-{
-       int fd;
-       const char *tmp = getenv("TMPDIR");
-       char *filename;
-
-       if (!tmp)
-               tmp = "/tmp";
-       filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX");
-       fd = mkstemp(filename);
-       if (fd < 0)
-               return NULL;
-       filename = xstrdup(filename);
-       if (f->size != xwrite(fd, f->ptr, f->size)) {
-               rm_temp_file(filename);
-               return NULL;
-       }
-       close(fd);
-       return filename;
-}
-
-static void *read_temp_file(const char *filename, unsigned long *size)
-{
-       struct stat st;
-       char *buf = NULL;
-       int fd = open(filename, O_RDONLY);
-       if (fd < 0)
-               return NULL;
-       if (!fstat(fd, &st)) {
-               *size = st.st_size;
-               buf = xmalloc(st.st_size);
-               if (st.st_size != xread(fd, buf, st.st_size)) {
-                       free(buf);
-                       buf = NULL;
-               }
-       }
-       close(fd);
-       return buf;
-}
-
 static int fill_mmfile_blob(mmfile_t *f, struct blob *obj)
 {
        void *buf;
@@ -72,22 +26,19 @@ static void free_mmfile(mmfile_t *f)
 
 static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size)
 {
-       void *res;
-       const char *t1, *t2, *t3;
-
-       t1 = write_temp_file(base);
-       t2 = write_temp_file(our);
-       t3 = write_temp_file(their);
-       res = NULL;
-       if (t1 && t2 && t3) {
-               int code = run_command("merge", t2, t1, t3, NULL);
-               if (!code || code == -1)
-                       res = read_temp_file(t2, size);
-       }
-       rm_temp_file(t1);
-       rm_temp_file(t2);
-       rm_temp_file(t3);
-       return res;
+       mmbuffer_t res;
+       xpparam_t xpp;
+       int merge_status;
+
+       memset(&xpp, 0, sizeof(xpp));
+       merge_status = xdl_merge(base, our, ".our", their, ".their",
+               &xpp, XDL_MERGE_ZEALOUS, &res);
+
+       if (merge_status < 0)
+               return NULL;
+
+       *size = res.size;
+       return res.ptr;
 }
 
 static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf)
index 646d090c58e774bdf0f412da39f4b67fd7a696c6..a9983dd78ad5cd1e364f0e00c259bdb7e6f151f0 100644 (file)
@@ -1,7 +1,3 @@
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <signal.h>
-
 #include "cache.h"
 
 static const char *pgm;
index cd2cc77bf47025a9dfc88f594961521c3ec8f22c..58989424d77467da018e7daf5761f1f85040d2f6 100644 (file)
@@ -3,13 +3,6 @@
  * Fredrik Kuivinen.
  * The thieves were Alex Riesen and Johannes Schindelin, in June/July 2006
  */
-#include <stdarg.h>
-#include <string.h>
-#include <assert.h>
-#include <sys/wait.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <time.h>
 #include "cache.h"
 #include "cache-tree.h"
 #include "commit.h"
@@ -21,6 +14,7 @@
 #include "tag.h"
 #include "unpack-trees.h"
 #include "path-list.h"
+#include "xdiff-interface.h"
 
 /*
  * A virtual commit has
@@ -73,27 +67,75 @@ struct stage_data
        unsigned processed:1;
 };
 
+struct output_buffer
+{
+       struct output_buffer *next;
+       char *str;
+};
+
 static struct path_list current_file_set = {NULL, 0, 0, 1};
 static struct path_list current_directory_set = {NULL, 0, 0, 1};
 
-static int output_indent = 0;
+static int call_depth = 0;
+static int verbosity = 2;
+static int buffer_output = 1;
+static int do_progress = 1;
+static unsigned last_percent;
+static unsigned merged_cnt;
+static unsigned total_cnt;
+static volatile sig_atomic_t progress_update;
+static struct output_buffer *output_list, *output_end;
+
+static int show (int v)
+{
+       return (!call_depth && verbosity >= v) || verbosity >= 5;
+}
 
-static void output(const char *fmt, ...)
+static void output(int v, const char *fmt, ...)
 {
        va_list args;
-       int i;
-       for (i = output_indent; i--;)
-               fputs("  ", stdout);
        va_start(args, fmt);
-       vfprintf(stdout, fmt, args);
+       if (buffer_output && show(v)) {
+               struct output_buffer *b = xmalloc(sizeof(*b));
+               nfvasprintf(&b->str, fmt, args);
+               b->next = NULL;
+               if (output_end)
+                       output_end->next = b;
+               else
+                       output_list = b;
+               output_end = b;
+       } else if (show(v)) {
+               int i;
+               for (i = call_depth; i--;)
+                       fputs("  ", stdout);
+               vfprintf(stdout, fmt, args);
+               fputc('\n', stdout);
+       }
        va_end(args);
-       fputc('\n', stdout);
+}
+
+static void flush_output()
+{
+       struct output_buffer *b, *n;
+       for (b = output_list; b; b = n) {
+               int i;
+               for (i = call_depth; i--;)
+                       fputs("  ", stdout);
+               fputs(b->str, stdout);
+               fputc('\n', stdout);
+               n = b->next;
+               free(b->str);
+               free(b);
+       }
+       output_list = NULL;
+       output_end = NULL;
 }
 
 static void output_commit_title(struct commit *commit)
 {
        int i;
-       for (i = output_indent; i--;)
+       flush_output();
+       for (i = call_depth; i--;)
                fputs("  ", stdout);
        if (commit->util)
                printf("virtual %s\n", (char *)commit->util);
@@ -116,33 +158,37 @@ static void output_commit_title(struct commit *commit)
        }
 }
 
-static const char *current_index_file = NULL;
-static const char *original_index_file;
-static const char *temporary_index_file;
-static int cache_dirty = 0;
+static void progress_interval(int signum)
+{
+       progress_update = 1;
+}
 
-static int flush_cache(void)
+static void setup_progress_signal(void)
 {
-       /* flush temporary index */
-       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
-       int fd = hold_lock_file_for_update(lock, current_index_file, 1);
-       if (write_cache(fd, active_cache, active_nr) ||
-                       close(fd) || commit_lock_file(lock))
-               die ("unable to write %s", current_index_file);
-       discard_cache();
-       cache_dirty = 0;
-       return 0;
+       struct sigaction sa;
+       struct itimerval v;
+
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = progress_interval;
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = SA_RESTART;
+       sigaction(SIGALRM, &sa, NULL);
+
+       v.it_interval.tv_sec = 1;
+       v.it_interval.tv_usec = 0;
+       v.it_value = v.it_interval;
+       setitimer(ITIMER_REAL, &v, NULL);
 }
 
-static void setup_index(int temp)
+static void display_progress()
 {
-       current_index_file = temp ? temporary_index_file: original_index_file;
-       if (cache_dirty) {
-               discard_cache();
-               cache_dirty = 0;
+       unsigned percent = total_cnt ? merged_cnt * 100 / total_cnt : 0;
+       if (progress_update || percent != last_percent) {
+               fprintf(stderr, "%4u%% (%u/%u) done\r",
+                       percent, merged_cnt, total_cnt);
+               progress_update = 0;
+               last_percent = percent;
        }
-       unlink(temporary_index_file);
-       discard_cache();
 }
 
 static struct cache_entry *make_cache_entry(unsigned int mode,
@@ -173,9 +219,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                const char *path, int stage, int refresh, int options)
 {
        struct cache_entry *ce;
-       if (!cache_dirty)
-               read_cache_from(current_index_file);
-       cache_dirty++;
        ce = make_cache_entry(mode, sha1 ? sha1 : null_sha1, path, stage, refresh);
        if (!ce)
                return error("cache_addinfo failed: %s", strerror(cache_errno));
@@ -193,26 +236,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
  */
 static int index_only = 0;
 
-static int git_read_tree(struct tree *tree)
-{
-       int rc;
-       struct object_list *trees = NULL;
-       struct unpack_trees_options opts;
-
-       if (cache_dirty)
-               die("read-tree with dirty cache");
-
-       memset(&opts, 0, sizeof(opts));
-       object_list_append(&tree->object, &trees);
-       rc = unpack_trees(trees, &opts);
-       cache_tree_free(&active_cache_tree);
-
-       if (rc == 0)
-               cache_dirty = 1;
-
-       return rc;
-}
-
 static int git_merge_trees(int index_only,
                           struct tree *common,
                           struct tree *head,
@@ -222,11 +245,6 @@ static int git_merge_trees(int index_only,
        struct object_list *trees = NULL;
        struct unpack_trees_options opts;
 
-       if (!cache_dirty) {
-               read_cache_from(current_index_file);
-               cache_dirty = 1;
-       }
-
        memset(&opts, 0, sizeof(opts));
        if (index_only)
                opts.index_only = 1;
@@ -242,39 +260,37 @@ static int git_merge_trees(int index_only,
 
        rc = unpack_trees(trees, &opts);
        cache_tree_free(&active_cache_tree);
-
-       cache_dirty = 1;
-
        return rc;
 }
 
+static int unmerged_index(void)
+{
+       int i;
+       for (i = 0; i < active_nr; i++) {
+               struct cache_entry *ce = active_cache[i];
+               if (ce_stage(ce))
+                       return 1;
+       }
+       return 0;
+}
+
 static struct tree *git_write_tree(void)
 {
        struct tree *result = NULL;
 
-       if (cache_dirty) {
-               unsigned i;
-               for (i = 0; i < active_nr; i++) {
-                       struct cache_entry *ce = active_cache[i];
-                       if (ce_stage(ce))
-                               return NULL;
-               }
-       } else
-               read_cache_from(current_index_file);
+       if (unmerged_index())
+               return NULL;
 
        if (!active_cache_tree)
                active_cache_tree = cache_tree();
 
        if (!cache_tree_fully_valid(active_cache_tree) &&
-                       cache_tree_update(active_cache_tree,
-                               active_cache, active_nr, 0, 0) < 0)
+           cache_tree_update(active_cache_tree,
+                             active_cache, active_nr, 0, 0) < 0)
                die("error building trees");
 
        result = lookup_tree(active_cache_tree->sha1);
 
-       flush_cache();
-       cache_dirty = 0;
-
        return result;
 }
 
@@ -337,14 +353,14 @@ static struct path_list *get_unmerged(void)
        int i;
 
        unmerged->strdup_paths = 1;
-       if (!cache_dirty) {
-               read_cache_from(current_index_file);
-               cache_dirty++;
-       }
-       for (i = 0; i < active_nr; i++) {
+       total_cnt += active_nr;
+
+       for (i = 0; i < active_nr; i++, merged_cnt++) {
                struct path_list_item *item;
                struct stage_data *e;
                struct cache_entry *ce = active_cache[i];
+               if (do_progress)
+                       display_progress();
                if (!ce_stage(ce))
                        continue;
 
@@ -370,7 +386,7 @@ struct rename
 };
 
 /*
- * Get information of all renames which occured between 'o_tree' and
+ * Get information of all renames which occurred between 'o_tree' and
  * 'tree'. We need the three trees in the merge ('o_tree', 'a_tree' and
  * 'b_tree') to be able to associate the correct cache entries with
  * the rename information. 'tree' is always equal to either a_tree or b_tree.
@@ -475,9 +491,6 @@ static int remove_file(int clean, const char *path, int no_wd)
        int update_working_directory = !index_only && !no_wd;
 
        if (update_cache) {
-               if (!cache_dirty)
-                       read_cache_from(current_index_file);
-               cache_dirty++;
                if (remove_file_from_cache(path))
                        return -1;
        }
@@ -523,7 +536,7 @@ static int mkdir_p(const char *path, unsigned long mode)
 static void flush_buffer(int fd, const char *buf, unsigned long size)
 {
        while (size > 0) {
-               long ret = xwrite(fd, buf, size);
+               long ret = write_in_full(fd, buf, size);
                if (ret < 0) {
                        /* Ignore epipe */
                        if (errno == EPIPE)
@@ -604,24 +617,21 @@ struct merge_file_info
                 merge:1;
 };
 
-static char *git_unpack_file(const unsigned char *sha1, char *path)
+static void fill_mm(const unsigned char *sha1, mmfile_t *mm)
 {
-       void *buf;
-       char type[20];
        unsigned long size;
-       int fd;
+       char type[20];
 
-       buf = read_sha1_file(sha1, type, &size);
-       if (!buf || strcmp(type, blob_type))
-               die("unable to read blob object %s", sha1_to_hex(sha1));
+       if (!hashcmp(sha1, null_sha1)) {
+               mm->ptr = xstrdup("");
+               mm->size = 0;
+               return;
+       }
 
-       strcpy(path, ".merge_file_XXXXXX");
-       fd = mkstemp(path);
-       if (fd < 0)
-               die("unable to create temp-file");
-       flush_buffer(fd, buf, size);
-       close(fd);
-       return path;
+       mm->ptr = read_sha1_file(sha1, type, &size);
+       if (!mm->ptr || strcmp(type, blob_type))
+               die("unable to read blob object %s", sha1_to_hex(sha1));
+       mm->size = size;
 }
 
 static struct merge_file_info merge_file(struct diff_filespec *o,
@@ -652,49 +662,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o,
                else if (sha_eq(b->sha1, o->sha1))
                        hashcpy(result.sha, a->sha1);
                else if (S_ISREG(a->mode)) {
-                       int code = 1, fd;
-                       struct stat st;
-                       char orig[PATH_MAX];
-                       char src1[PATH_MAX];
-                       char src2[PATH_MAX];
-                       const char *argv[] = {
-                               "merge", "-L", NULL, "-L", NULL, "-L", NULL,
-                               NULL, NULL, NULL,
-                               NULL
-                       };
-                       char *la, *lb, *lo;
-
-                       git_unpack_file(o->sha1, orig);
-                       git_unpack_file(a->sha1, src1);
-                       git_unpack_file(b->sha1, src2);
-
-                       argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path));
-                       argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path));
-                       argv[4] = lo = xstrdup(mkpath("orig/%s", o->path));
-                       argv[7] = src1;
-                       argv[8] = orig;
-                       argv[9] = src2,
-
-                       code = run_command_v(10, argv);
-
-                       free(la);
-                       free(lb);
-                       free(lo);
-                       if (code && code < -256) {
-                               die("Failed to execute 'merge'. merge(1) is used as the "
-                                   "file-level merge tool. Is 'merge' in your path?");
-                       }
-                       fd = open(src1, O_RDONLY);
-                       if (fd < 0 || fstat(fd, &st) < 0 ||
-                                       index_fd(result.sha, fd, &st, 1,
-                                               "blob"))
-                               die("Unable to add %s to database", src1);
-
-                       unlink(orig);
-                       unlink(src1);
-                       unlink(src2);
-
-                       result.clean = WEXITSTATUS(code) == 0;
+                       mmfile_t orig, src1, src2;
+                       mmbuffer_t result_buf;
+                       xpparam_t xpp;
+                       char *name1, *name2;
+                       int merge_status;
+
+                       name1 = xstrdup(mkpath("%s:%s", branch1, a->path));
+                       name2 = xstrdup(mkpath("%s:%s", branch2, b->path));
+
+                       fill_mm(o->sha1, &orig);
+                       fill_mm(a->sha1, &src1);
+                       fill_mm(b->sha1, &src2);
+
+                       memset(&xpp, 0, sizeof(xpp));
+                       merge_status = xdl_merge(&orig,
+                                                &src1, name1,
+                                                &src2, name2,
+                                                &xpp, XDL_MERGE_ZEALOUS,
+                                                &result_buf);
+                       free(name1);
+                       free(name2);
+                       free(orig.ptr);
+                       free(src1.ptr);
+                       free(src2.ptr);
+
+                       if ((merge_status < 0) || !result_buf.ptr)
+                               die("Failed to execute internal merge");
+
+                       if (write_sha1_file(result_buf.ptr, result_buf.size,
+                                           blob_type, result.sha))
+                               die("Unable to add %s to database",
+                                   a->path);
+
+                       free(result_buf.ptr);
+                       result.clean = (merge_status == 0);
                } else {
                        if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode)))
                                die("cannot merge modes?");
@@ -722,13 +724,13 @@ static void conflict_rename_rename(struct rename *ren1,
        const char *dst_name2 = ren2_dst;
        if (path_list_has_path(&current_directory_set, ren1_dst)) {
                dst_name1 = del[delp++] = unique_path(ren1_dst, branch1);
-               output("%s is a directory in %s adding as %s instead",
+               output(1, "%s is a directory in %s added as %s instead",
                       ren1_dst, branch2, dst_name1);
                remove_file(0, ren1_dst, 0);
        }
        if (path_list_has_path(&current_directory_set, ren2_dst)) {
                dst_name2 = del[delp++] = unique_path(ren2_dst, branch2);
-               output("%s is a directory in %s adding as %s instead",
+               output(1, "%s is a directory in %s added as %s instead",
                       ren2_dst, branch1, dst_name2);
                remove_file(0, ren2_dst, 0);
        }
@@ -742,7 +744,7 @@ static void conflict_rename_dir(struct rename *ren1,
                                const char *branch1)
 {
        char *new_path = unique_path(ren1->pair->two->path, branch1);
-       output("Renaming %s to %s instead", ren1->pair->one->path, new_path);
+       output(1, "Renamed %s to %s instead", ren1->pair->one->path, new_path);
        remove_file(0, ren1->pair->two->path, 0);
        update_file(0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);
        free(new_path);
@@ -755,7 +757,7 @@ static void conflict_rename_rename_2(struct rename *ren1,
 {
        char *new_path1 = unique_path(ren1->pair->two->path, branch1);
        char *new_path2 = unique_path(ren2->pair->two->path, branch2);
-       output("Renaming %s to %s and %s to %s instead",
+       output(1, "Renamed %s to %s and %s to %s instead",
               ren1->pair->one->path, new_path1,
               ren2->pair->one->path, new_path2);
        remove_file(0, ren1->pair->two->path, 0);
@@ -848,7 +850,7 @@ static int process_renames(struct path_list *a_renames,
                        ren2->processed = 1;
                        if (strcmp(ren1_dst, ren2_dst) != 0) {
                                clean_merge = 0;
-                               output("CONFLICT (rename/rename): "
+                               output(1, "CONFLICT (rename/rename): "
                                       "Rename %s->%s in branch %s "
                                       "rename %s->%s in %s",
                                       src, ren1_dst, branch1,
@@ -863,13 +865,13 @@ static int process_renames(struct path_list *a_renames,
                                                 branch1,
                                                 branch2);
                                if (mfi.merge || !mfi.clean)
-                                       output("Renaming %s->%s", src, ren1_dst);
+                                       output(1, "Renamed %s->%s", src, ren1_dst);
 
                                if (mfi.merge)
-                                       output("Auto-merging %s", ren1_dst);
+                                       output(2, "Auto-merged %s", ren1_dst);
 
                                if (!mfi.clean) {
-                                       output("CONFLICT (content): merge conflict in %s",
+                                       output(1, "CONFLICT (content): merge conflict in %s",
                                               ren1_dst);
                                        clean_merge = 0;
 
@@ -889,7 +891,7 @@ static int process_renames(struct path_list *a_renames,
                        struct diff_filespec src_other, dst_other;
                        int try_merge, stage = a_renames == renames1 ? 3: 2;
 
-                       remove_file(1, ren1_src, 1);
+                       remove_file(1, ren1_src, index_only || stage == 3);
 
                        hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);
                        src_other.mode = ren1->src_entry->stages[stage].mode;
@@ -900,14 +902,14 @@ static int process_renames(struct path_list *a_renames,
 
                        if (path_list_has_path(&current_directory_set, ren1_dst)) {
                                clean_merge = 0;
-                               output("CONFLICT (rename/directory): Rename %s->%s in %s "
+                               output(1, "CONFLICT (rename/directory): Renamed %s->%s in %s "
                                       " directory %s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
                                conflict_rename_dir(ren1, branch1);
                        } else if (sha_eq(src_other.sha1, null_sha1)) {
                                clean_merge = 0;
-                               output("CONFLICT (rename/delete): Rename %s->%s in %s "
+                               output(1, "CONFLICT (rename/delete): Renamed %s->%s in %s "
                                       "and deleted in %s",
                                       ren1_src, ren1_dst, branch1,
                                       branch2);
@@ -916,19 +918,19 @@ static int process_renames(struct path_list *a_renames,
                                const char *new_path;
                                clean_merge = 0;
                                try_merge = 1;
-                               output("CONFLICT (rename/add): Rename %s->%s in %s. "
+                               output(1, "CONFLICT (rename/add): Renamed %s->%s in %s. "
                                       "%s added in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren1_dst, branch2);
                                new_path = unique_path(ren1_dst, branch2);
-                               output("Adding as %s instead", new_path);
+                               output(1, "Added as %s instead", new_path);
                                update_file(0, dst_other.sha1, dst_other.mode, new_path);
                        } else if ((item = path_list_lookup(ren1_dst, renames2Dst))) {
                                ren2 = item->util;
                                clean_merge = 0;
                                ren2->processed = 1;
-                               output("CONFLICT (rename/rename): Rename %s->%s in %s. "
-                                      "Rename %s->%s in %s",
+                               output(1, "CONFLICT (rename/rename): Renamed %s->%s in %s. "
+                                      "Renamed %s->%s in %s",
                                       ren1_src, ren1_dst, branch1,
                                       ren2->pair->one->path, ren2->pair->two->path, branch2);
                                conflict_rename_rename_2(ren1, branch1, ren2, branch2);
@@ -952,11 +954,11 @@ static int process_renames(struct path_list *a_renames,
                                                a_branch, b_branch);
 
                                if (mfi.merge || !mfi.clean)
-                                       output("Renaming %s => %s", ren1_src, ren1_dst);
+                                       output(1, "Renamed %s => %s", ren1_src, ren1_dst);
                                if (mfi.merge)
-                                       output("Auto-merging %s", ren1_dst);
+                                       output(2, "Auto-merged %s", ren1_dst);
                                if (!mfi.clean) {
-                                       output("CONFLICT (rename/modify): Merge conflict in %s",
+                                       output(1, "CONFLICT (rename/modify): Merge conflict in %s",
                                               ren1_dst);
                                        clean_merge = 0;
 
@@ -971,8 +973,6 @@ static int process_renames(struct path_list *a_renames,
        path_list_clear(&a_by_dst, 0);
        path_list_clear(&b_by_dst, 0);
 
-       if (cache_dirty)
-               flush_cache();
        return clean_merge;
 }
 
@@ -1006,20 +1006,20 @@ static int process_entry(const char *path, struct stage_data *entry,
                        /* Deleted in both or deleted in one and
                         * unchanged in the other */
                        if (a_sha)
-                               output("Removing %s", path);
+                               output(2, "Removed %s", path);
                        /* do not touch working file if it did not exist */
                        remove_file(1, path, !a_sha);
                } else {
                        /* Deleted in one and changed in the other */
                        clean_merge = 0;
                        if (!a_sha) {
-                               output("CONFLICT (delete/modify): %s deleted in %s "
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
                                       "and modified in %s. Version %s of %s left in tree.",
                                       path, branch1,
                                       branch2, branch2, path);
                                update_file(0, b_sha, b_mode, path);
                        } else {
-                               output("CONFLICT (delete/modify): %s deleted in %s "
+                               output(1, "CONFLICT (delete/modify): %s deleted in %s "
                                       "and modified in %s. Version %s of %s left in tree.",
                                       path, branch2,
                                       branch1, branch1, path);
@@ -1052,48 +1052,27 @@ static int process_entry(const char *path, struct stage_data *entry,
                if (path_list_has_path(&current_directory_set, path)) {
                        const char *new_path = unique_path(path, add_branch);
                        clean_merge = 0;
-                       output("CONFLICT (%s): There is a directory with name %s in %s. "
-                              "Adding %s as %s",
+                       output(1, "CONFLICT (%s): There is a directory with name %s in %s. "
+                              "Added %s as %s",
                               conf, path, other_branch, path, new_path);
                        remove_file(0, path, 0);
                        update_file(0, sha, mode, new_path);
                } else {
-                       output("Adding %s", path);
+                       output(2, "Added %s", path);
                        update_file(1, sha, mode, path);
                }
-       } else if (!o_sha && a_sha && b_sha) {
-               /* Case C: Added in both (check for same permissions). */
-               if (sha_eq(a_sha, b_sha)) {
-                       if (a_mode != b_mode) {
-                               clean_merge = 0;
-                               output("CONFLICT: File %s added identically in both branches, "
-                                      "but permissions conflict %06o->%06o",
-                                      path, a_mode, b_mode);
-                               output("CONFLICT: adding with permission: %06o", a_mode);
-                               update_file(0, a_sha, a_mode, path);
-                       } else {
-                               /* This case is handled by git-read-tree */
-                               assert(0 && "This case must be handled by git-read-tree");
-                       }
-               } else {
-                       const char *new_path1, *new_path2;
-                       clean_merge = 0;
-                       new_path1 = unique_path(path, branch1);
-                       new_path2 = unique_path(path, branch2);
-                       output("CONFLICT (add/add): File %s added non-identically "
-                              "in both branches. Adding as %s and %s instead.",
-                              path, new_path1, new_path2);
-                       remove_file(0, path, 0);
-                       update_file(0, a_sha, a_mode, new_path1);
-                       update_file(0, b_sha, b_mode, new_path2);
-               }
-
-       } else if (o_sha && a_sha && b_sha) {
+       } else if (a_sha && b_sha) {
+               /* Case C: Added in both (check for same permissions) and */
                /* case D: Modified in both, but differently. */
+               const char *reason = "content";
                struct merge_file_info mfi;
                struct diff_filespec o, a, b;
 
-               output("Auto-merging %s", path);
+               if (!o_sha) {
+                       reason = "add/add";
+                       o_sha = (unsigned char *)null_sha1;
+               }
+               output(2, "Auto-merged %s", path);
                o.path = a.path = b.path = (char *)path;
                hashcpy(o.sha1, o_sha);
                o.mode = o_mode;
@@ -1109,7 +1088,8 @@ static int process_entry(const char *path, struct stage_data *entry,
                        update_file(1, mfi.sha, mfi.mode, path);
                else {
                        clean_merge = 0;
-                       output("CONFLICT (content): Merge conflict in %s", path);
+                       output(1, "CONFLICT (%s): Merge conflict in %s",
+                                       reason, path);
 
                        if (index_only)
                                update_file(0, mfi.sha, mfi.mode, path);
@@ -1120,9 +1100,6 @@ static int process_entry(const char *path, struct stage_data *entry,
        } else
                die("Fatal merge failure, shouldn't happen.");
 
-       if (cache_dirty)
-               flush_cache();
-
        return clean_merge;
 }
 
@@ -1135,7 +1112,7 @@ static int merge_trees(struct tree *head,
 {
        int code, clean;
        if (sha_eq(common->object.sha1, merge->object.sha1)) {
-               output("Already uptodate!");
+               output(0, "Already uptodate!");
                *result = head;
                return 1;
        }
@@ -1147,9 +1124,7 @@ static int merge_trees(struct tree *head,
                    sha1_to_hex(head->object.sha1),
                    sha1_to_hex(merge->object.sha1));
 
-       *result = git_write_tree();
-
-       if (!*result) {
+       if (unmerged_index()) {
                struct path_list *entries, *re_head, *re_merge;
                int i;
                path_list_clear(&current_file_set, 1);
@@ -1162,30 +1137,27 @@ static int merge_trees(struct tree *head,
                re_merge = get_renames(merge, common, head, merge, entries);
                clean = process_renames(re_head, re_merge,
                                branch1, branch2);
-               for (i = 0; i < entries->nr; i++) {
+               total_cnt += entries->nr;
+               for (i = 0; i < entries->nr; i++, merged_cnt++) {
                        const char *path = entries->items[i].path;
                        struct stage_data *e = entries->items[i].util;
-                       if (e->processed)
-                               continue;
-                       if (!process_entry(path, e, branch1, branch2))
+                       if (!e->processed
+                               && !process_entry(path, e, branch1, branch2))
                                clean = 0;
+                       if (do_progress)
+                               display_progress();
                }
 
                path_list_clear(re_merge, 0);
                path_list_clear(re_head, 0);
                path_list_clear(entries, 1);
 
-               if (clean || index_only)
-                       *result = git_write_tree();
-               else
-                       *result = NULL;
-       } else {
-               clean = 1;
-               printf("merging of trees %s and %s resulted in %s\n",
-                      sha1_to_hex(head->object.sha1),
-                      sha1_to_hex(merge->object.sha1),
-                      sha1_to_hex((*result)->object.sha1));
        }
+       else
+               clean = 1;
+
+       if (index_only)
+               *result = git_write_tree();
 
        return clean;
 }
@@ -1203,33 +1175,36 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)
 
 /*
  * Merge the commits h1 and h2, return the resulting virtual
- * commit object and a flag indicating the cleaness of the merge.
+ * commit object and a flag indicating the cleanness of the merge.
  */
 static int merge(struct commit *h1,
                 struct commit *h2,
                 const char *branch1,
                 const char *branch2,
-                int call_depth /* =0 */,
-                struct commit *ancestor /* =None */,
+                struct commit_list *ca,
                 struct commit **result)
 {
-       struct commit_list *ca = NULL, *iter;
+       struct commit_list *iter;
        struct commit *merged_common_ancestors;
        struct tree *mrtree;
        int clean;
 
-       output("Merging:");
-       output_commit_title(h1);
-       output_commit_title(h2);
+       if (show(4)) {
+               output(4, "Merging:");
+               output_commit_title(h1);
+               output_commit_title(h2);
+       }
 
-       if (ancestor)
-               commit_list_insert(ancestor, &ca);
-       else
-               ca = reverse_commit_list(get_merge_bases(h1, h2, 1));
+       if (!ca) {
+               ca = get_merge_bases(h1, h2, 1);
+               ca = reverse_commit_list(ca);
+       }
 
-       output("found %u common ancestor(s):", commit_list_count(ca));
-       for (iter = ca; iter; iter = iter->next)
-               output_commit_title(iter->item);
+       if (show(5)) {
+               output(5, "found %u common ancestor(s):", commit_list_count(ca));
+               for (iter = ca; iter; iter = iter->next)
+                       output_commit_title(iter->item);
+       }
 
        merged_common_ancestors = pop_commit(&ca);
        if (merged_common_ancestors == NULL) {
@@ -1238,53 +1213,71 @@ static int merge(struct commit *h1,
 
                tree->object.parsed = 1;
                tree->object.type = OBJ_TREE;
-               hash_sha1_file(NULL, 0, tree_type, tree->object.sha1);
+               pretend_sha1_file(NULL, 0, tree_type, tree->object.sha1);
                merged_common_ancestors = make_virtual_commit(tree, "ancestor");
        }
 
        for (iter = ca; iter; iter = iter->next) {
-               output_indent = call_depth + 1;
+               call_depth++;
                /*
                 * When the merge fails, the result contains files
                 * with conflict markers. The cleanness flag is
-                * ignored, it was never acutally used, as result of
-                * merge_trees has always overwritten it: the commited
+                * ignored, it was never actually used, as result of
+                * merge_trees has always overwritten it: the committed
                 * "conflicts" were already resolved.
                 */
+               discard_cache();
                merge(merged_common_ancestors, iter->item,
                      "Temporary merge branch 1",
                      "Temporary merge branch 2",
-                     call_depth + 1,
                      NULL,
                      &merged_common_ancestors);
-               output_indent = call_depth;
+               call_depth--;
 
                if (!merged_common_ancestors)
                        die("merge returned no commit");
        }
 
-       if (call_depth == 0) {
-               setup_index(0 /* $GIT_DIR/index */);
+       discard_cache();
+       if (!call_depth) {
+               read_cache();
                index_only = 0;
-       } else {
-               setup_index(1 /* temporary index */);
-               git_read_tree(h1->tree);
+       } else
                index_only = 1;
-       }
 
        clean = merge_trees(h1->tree, h2->tree, merged_common_ancestors->tree,
                            branch1, branch2, &mrtree);
 
-       if (!ancestor && (clean || index_only)) {
+       if (index_only) {
                *result = make_virtual_commit(mrtree, "merged tree");
                commit_list_insert(h1, &(*result)->parents);
                commit_list_insert(h2, &(*result)->parents->next);
-       } else
-               *result = NULL;
-
+       }
+       if (!call_depth && do_progress) {
+               /* Make sure we end at 100% */
+               if (!total_cnt)
+                       total_cnt = 1;
+               merged_cnt = total_cnt;
+               progress_update = 1;
+               display_progress();
+               fputc('\n', stderr);
+       }
+       flush_output();
        return clean;
 }
 
+static const char *better_branch_name(const char *branch)
+{
+       static char githead_env[8 + 40 + 1];
+       char *name;
+
+       if (strlen(branch) != 40)
+               return branch;
+       sprintf(githead_env, "GITHEAD_%s", branch);
+       name = getenv(githead_env);
+       return name ? name : branch;
+}
+
 static struct commit *get_ref(const char *ref)
 {
        unsigned char sha1[20];
@@ -1293,6 +1286,9 @@ static struct commit *get_ref(const char *ref)
        if (get_sha1(ref, sha1))
                die("Could not resolve ref '%s'", ref);
        object = deref_tag(parse_object(sha1), ref, strlen(ref));
+       if (object->type == OBJ_TREE)
+               return make_virtual_commit((struct tree*)object,
+                       better_branch_name(ref));
        if (object->type != OBJ_COMMIT)
                return NULL;
        if (parse_commit((struct commit *)object))
@@ -1300,21 +1296,29 @@ static struct commit *get_ref(const char *ref)
        return (struct commit *)object;
 }
 
+static int merge_config(const char *var, const char *value)
+{
+       if (!strcasecmp(var, "merge.verbosity")) {
+               verbosity = git_config_int(var, value);
+               return 0;
+       }
+       return git_default_config(var, value);
+}
+
 int main(int argc, char *argv[])
 {
-       static const char *bases[2];
+       static const char *bases[20];
        static unsigned bases_count = 0;
        int i, clean;
        const char *branch1, *branch2;
        struct commit *result, *h1, *h2;
+       struct commit_list *ca = NULL;
+       struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       int index_fd;
 
-       git_config(git_default_config); /* core.filemode */
-       original_index_file = getenv("GIT_INDEX_FILE");
-
-       if (!original_index_file)
-               original_index_file = xstrdup(git_path("index"));
-
-       temporary_index_file = xstrdup(git_path("mrg-rcrsv-tmp-idx"));
+       git_config(merge_config);
+       if (getenv("GIT_MERGE_VERBOSITY"))
+               verbosity = strtol(getenv("GIT_MERGE_VERBOSITY"), NULL, 10);
 
        if (argc < 4)
                die("Usage: %s <base>... -- <head> <remote> ...\n", argv[0]);
@@ -1327,26 +1331,39 @@ int main(int argc, char *argv[])
        }
        if (argc - i != 3) /* "--" "<head>" "<remote>" */
                die("Not handling anything other than two heads merge.");
+       if (verbosity >= 5) {
+               buffer_output = 0;
+               do_progress = 0;
+       }
+       else
+               do_progress = isatty(1);
 
        branch1 = argv[++i];
        branch2 = argv[++i];
-       printf("Merging %s with %s\n", branch1, branch2);
 
        h1 = get_ref(branch1);
        h2 = get_ref(branch2);
 
-       if (bases_count == 1) {
-               struct commit *ancestor = get_ref(bases[0]);
-               clean = merge(h1, h2, branch1, branch2, 0, ancestor, &result);
-       } else
-               clean = merge(h1, h2, branch1, branch2, 0, NULL, &result);
+       branch1 = better_branch_name(branch1);
+       branch2 = better_branch_name(branch2);
+
+       if (do_progress)
+               setup_progress_signal();
+       if (show(3))
+               printf("Merging %s with %s\n", branch1, branch2);
 
-       if (cache_dirty)
-               flush_cache();
+       index_fd = hold_lock_file_for_update(lock, get_index_file(), 1);
+
+       for (i = 0; i < bases_count; i++) {
+               struct commit *ancestor = get_ref(bases[i]);
+               ca = commit_list_insert(ancestor, &ca);
+       }
+       clean = merge(h1, h2, branch1, branch2, ca, &result);
+
+       if (active_cache_changed &&
+           (write_cache(index_fd, active_cache, active_nr) ||
+            close(index_fd) || commit_lock_file(lock)))
+                       die ("unable to write %s", get_index_file());
 
        return clean ? 0: 1;
 }
-
-/*
-vim: sw=8 noet
-*/
index c0caaee0933382f30cc932731e1e387cc9a03c12..08a9fd8dc09056c1b21cb7414f2467cffcdef077 100644 (file)
@@ -1,45 +1,45 @@
 #include "cache.h"
 #include "pack.h"
 
-static int verify_packfile(struct packed_git *p)
+static int verify_packfile(struct packed_git *p,
+               struct pack_window **w_curs)
 {
        unsigned long index_size = p->index_size;
        void *index_base = p->index_base;
        SHA_CTX ctx;
        unsigned char sha1[20];
-       unsigned long pack_size = p->pack_size;
-       void *pack_base;
-       struct pack_header *hdr;
+       unsigned long offset = 0, pack_sig = p->pack_size - 20;
        int nr_objects, err, i;
 
-       /* Header consistency check */
-       hdr = p->pack_base;
-       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
-               return error("Packfile %s signature mismatch", p->pack_name);
-       if (!pack_version_ok(hdr->hdr_version))
-               return error("Packfile version %d unsupported",
-                            ntohl(hdr->hdr_version));
-       nr_objects = ntohl(hdr->hdr_entries);
-       if (num_packed_objects(p) != nr_objects)
-               return error("Packfile claims to have %d objects, "
-                            "while idx size expects %d", nr_objects,
-                            num_packed_objects(p));
+       /* Note that the pack header checks are actually performed by
+        * use_pack when it first opens the pack file.  If anything
+        * goes wrong during those checks then the call will die out
+        * immediately.
+        */
 
        SHA1_Init(&ctx);
-       pack_base = p->pack_base;
-       SHA1_Update(&ctx, pack_base, pack_size - 20);
+       while (offset < pack_sig) {
+               unsigned int remaining;
+               unsigned char *in = use_pack(p, w_curs, offset, &remaining);
+               offset += remaining;
+               if (offset > pack_sig)
+                       remaining -= offset - pack_sig;
+               SHA1_Update(&ctx, in, remaining);
+       }
        SHA1_Final(sha1, &ctx);
-       if (hashcmp(sha1, (unsigned char *)pack_base + pack_size - 20))
+       if (hashcmp(sha1, use_pack(p, w_curs, pack_sig, NULL)))
                return error("Packfile %s SHA1 mismatch with itself",
                             p->pack_name);
        if (hashcmp(sha1, (unsigned char *)index_base + index_size - 40))
                return error("Packfile %s SHA1 mismatch with idx",
                             p->pack_name);
+       unuse_pack(w_curs);
 
        /* Make sure everything reachable from idx is valid.  Since we
         * have verified that nr_objects matches between idx and pack,
         * we do not do scan-streaming check on the pack file.
         */
+       nr_objects = num_packed_objects(p);
        for (i = err = 0; i < nr_objects; i++) {
                unsigned char sha1[20];
                void *data;
@@ -51,7 +51,7 @@ static int verify_packfile(struct packed_git *p)
                offset = find_pack_entry_one(sha1, p);
                if (!offset)
                        die("internal error pack-check find-pack-entry-one");
-               data = unpack_entry_gently(p, offset, type, &size);
+               data = unpack_entry(p, offset, type, &size);
                if (!data) {
                        err = error("cannot unpack %s from %s",
                                    sha1_to_hex(sha1), p->pack_name);
@@ -74,12 +74,10 @@ static int verify_packfile(struct packed_git *p)
 
 static void show_pack_info(struct packed_git *p)
 {
-       struct pack_header *hdr;
        int nr_objects, i;
        unsigned int chain_histogram[MAX_CHAIN];
 
-       hdr = p->pack_base;
-       nr_objects = ntohl(hdr->hdr_entries);
+       nr_objects = num_packed_objects(p);
        memset(chain_histogram, 0, sizeof(chain_histogram));
 
        for (i = 0; i < nr_objects; i++) {
@@ -142,18 +140,16 @@ int verify_pack(struct packed_git *p, int verbose)
 
        if (!ret) {
                /* Verify pack file */
-               use_packed_git(p);
-               ret = verify_packfile(p);
-               unuse_packed_git(p);
+               struct pack_window *w_curs = NULL;
+               ret = verify_packfile(p, &w_curs);
+               unuse_pack(&w_curs);
        }
 
        if (verbose) {
                if (ret)
                        printf("%s: bad\n", p->pack_name);
                else {
-                       use_packed_git(p);
                        show_pack_info(p);
-                       unuse_packed_git(p);
                        printf("%s: ok\n", p->pack_name);
                }
        }
diff --git a/pack.h b/pack.h
index 4814800f2806a245a675ea9832f894dc95b27b89..deb427edbe43710dd2b76a3af0dbd87750344103 100644 (file)
--- a/pack.h
+++ b/pack.h
 #define PACK_VERSION 2
 #define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3))
 struct pack_header {
-       unsigned int hdr_signature;
-       unsigned int hdr_version;
-       unsigned int hdr_entries;
+       uint32_t hdr_signature;
+       uint32_t hdr_version;
+       uint32_t hdr_entries;
 };
 
+/*
+ * Packed object index header
+ *
+ * struct pack_idx_header {
+ *     uint32_t idx_signature;
+ *     uint32_t idx_version;
+ * };
+ *
+ * Note: this header isn't active yet.  In future versions of git
+ * we may change the index file format.  At that time we would start
+ * the first four bytes of the new index format with this signature,
+ * as all older git binaries would find this value illegal and abort
+ * reading the file.
+ *
+ * This is the case because the number of objects in a packfile
+ * cannot exceed 1,431,660,000 as every object would need at least
+ * 3 bytes of data and the overall packfile cannot exceed 4 GiB due
+ * to the 32 bit offsets used by the index.  Clearly the signature
+ * exceeds this maximum.
+ *
+ * Very old git binaries will also compare the first 4 bytes to the
+ * next 4 bytes in the index and abort with a "non-monotonic index"
+ * error if the second 4 byte word is smaller than the first 4
+ * byte word.  This would be true in the proposed future index
+ * format as idx_signature would be greater than idx_version.
+ */
+#define PACK_IDX_SIGNATURE 0xff744f63  /* "\377tOc" */
+
 extern int verify_pack(struct packed_git *, int);
+
+#define PH_ERROR_EOF           (-1)
+#define PH_ERROR_PACK_SIGNATURE        (-2)
+#define PH_ERROR_PROTOCOL      (-3)
+extern int read_pack_header(int fd, struct pack_header *);
 #endif
diff --git a/pager.c b/pager.c
index 4587fbbdb51e50c1e3e6e4c3476cdccd4ac59cee..5f280ab52720772905cacbcba522ecc9c81bb529 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -1,5 +1,7 @@
 #include "cache.h"
 
+#include <sys/select.h>
+
 /*
  * This is split up from the rest of git so that we might do
  * something different on Windows, for example.
@@ -7,6 +9,16 @@
 
 static void run_pager(const char *pager)
 {
+       /*
+        * Work around bug in "less" by not starting it until we
+        * have real input
+        */
+       fd_set in;
+
+       FD_ZERO(&in);
+       FD_SET(0, &in);
+       select(1, &in, NULL, &in, NULL);
+
        execlp(pager, pager, NULL);
        execl("/bin/sh", "sh", "-c", pager, NULL);
 }
index e3a1d425ee2e333bce0f87b1e50fc112a8ec51a3..ed9db81fa82c812c9ffa07f5a40540dbb15da0d3 100644 (file)
@@ -9,8 +9,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <stdlib.h>
-#include <string.h>
+#include "git-compat-util.h"
 #include "delta.h"
 
 void *patch_delta(const void *src_buf, unsigned long src_size,
@@ -34,9 +33,7 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
 
        /* now the result size */
        size = get_delta_hdr_size(&data, top);
-       dst_buf = malloc(size + 1);
-       if (!dst_buf)
-               return NULL;
+       dst_buf = xmalloc(size + 1);
        dst_buf[size] = 0;
 
        out = dst_buf;
@@ -55,13 +52,13 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
                        if (cp_off + cp_size < cp_size ||
                            cp_off + cp_size > src_size ||
                            cp_size > size)
-                               goto bad;
+                               break;
                        memcpy(out, (char *) src_buf + cp_off, cp_size);
                        out += cp_size;
                        size -= cp_size;
                } else if (cmd) {
                        if (cmd > size)
-                               goto bad;
+                               break;
                        memcpy(out, data, cmd);
                        out += cmd;
                        data += cmd;
@@ -72,12 +69,14 @@ void *patch_delta(const void *src_buf, unsigned long src_size,
                         * extensions. In the mean time we must fail when
                         * encountering them (might be data corruption).
                         */
+                       error("unexpected delta opcode 0");
                        goto bad;
                }
        }
 
        /* sanity check */
        if (data != top || size != 0) {
+               error("delta replay has gone wild");
                bad:
                free(dst_buf);
                return NULL;
index f8800f8e66e39f035820c2aeb0843fb3ebf65fb3..caaa5cc57b05e1a27bb4fd8f49bb74e06b9936e3 100644 (file)
@@ -1,4 +1,3 @@
-#include <stdio.h>
 #include "cache.h"
 #include "path-list.h"
 
diff --git a/path.c b/path.c
index d2c076d7cbad3a16a002897d926cc13633be4f77..c5d25a4b903bd92930df4004d30d4dffa6054456 100644 (file)
--- a/path.c
+++ b/path.c
@@ -11,7 +11,6 @@
  * which is what it's designed for.
  */
 #include "cache.h"
-#include <pwd.h>
 
 static char bad_path[] = "/bad-path/";
 
@@ -91,10 +90,11 @@ int git_mkstemp(char *path, size_t len, const char *template)
 }
 
 
-int validate_symref(const char *path)
+int validate_headref(const char *path)
 {
        struct stat st;
        char *buf, buffer[256];
+       unsigned char sha1[20];
        int len, fd;
 
        if (lstat(path, &st) < 0)
@@ -114,20 +114,29 @@ int validate_symref(const char *path)
        fd = open(path, O_RDONLY);
        if (fd < 0)
                return -1;
-       len = read(fd, buffer, sizeof(buffer)-1);
+       len = read_in_full(fd, buffer, sizeof(buffer)-1);
        close(fd);
 
        /*
         * Is it a symbolic ref?
         */
-       if (len < 4 || memcmp("ref:", buffer, 4))
+       if (len < 4)
                return -1;
-       buf = buffer + 4;
-       len -= 4;
-       while (len && isspace(*buf))
-               buf++, len--;
-       if (len >= 5 && !memcmp("refs/", buf, 5))
+       if (!memcmp("ref:", buffer, 4)) {
+               buf = buffer + 4;
+               len -= 4;
+               while (len && isspace(*buf))
+                       buf++, len--;
+               if (len >= 5 && !memcmp("refs/", buf, 5))
+                       return 0;
+       }
+
+       /*
+        * Is this a detached HEAD?
+        */
+       if (!get_sha1_hex(buffer, sha1))
                return 0;
+
        return -1;
 }
 
@@ -242,7 +251,7 @@ char *enter_repo(char *path, int strict)
                return NULL;
 
        if (access("objects", X_OK) == 0 && access("refs", X_OK) == 0 &&
-           validate_symref("HEAD") == 0) {
+           validate_headref("HEAD") == 0) {
                putenv("GIT_DIR=.");
                check_repository_format();
                return path;
index 353da002b4c7632ed66c1b3ceacaa985ec137627..ef3c76ce526f820d59e150ba08804caca71a4a11 100644 (file)
@@ -3,8 +3,8 @@
 #include "pkt-line.h"
 
 static const char peek_remote_usage[] =
-"git-peek-remote [--exec=upload-pack] [host:]directory";
-static const char *exec = "git-upload-pack";
+"git-peek-remote [--upload-pack=<git-upload-pack>] [<host>:]<directory>";
+static const char *uploadpack = "git-upload-pack";
 
 static int peek_remote(int fd[2], unsigned flags)
 {
@@ -35,8 +35,12 @@ int main(int argc, char **argv)
                char *arg = argv[i];
 
                if (*arg == '-') {
+                       if (!strncmp("--upload-pack=", arg, 14)) {
+                               uploadpack = arg + 14;
+                               continue;
+                       }
                        if (!strncmp("--exec=", arg, 7)) {
-                               exec = arg + 7;
+                               uploadpack = arg + 7;
                                continue;
                        }
                        if (!strcmp("--tags", arg)) {
@@ -60,7 +64,7 @@ int main(int argc, char **argv)
        if (!dest || i != argc - 1)
                usage(peek_remote_usage);
 
-       pid = git_connect(fd, dest, exec);
+       pid = git_connect(fd, dest, uploadpack);
        if (pid < 0)
                return 1;
        ret = peek_remote(fd, flags);
index e990caeea731d4a6713c32581cf8de3d6af4e1b3..98b24772c7ebe838d513d8e24f3c8acff7839cb9 100644 (file)
@@ -1,4 +1,5 @@
-Makefile
+perl.mak
+perl.mak.old
 blib
 blibdirs
 pm_to_blib
index 2b26b65bfb00c60535919d7b9359a5549f9e9709..f2c156cde9c1ae62af12d2c213ac8b3a2cc71f21 100644 (file)
@@ -63,7 +63,7 @@ for doing easily operations which are not totally trivial to do over
 the generic command interface.
 
 While some commands can be executed outside of any context (e.g. 'version'
-or 'init-db'), most operations require a repository context, which in practice
+or 'init'), most operations require a repository context, which in practice
 means getting an instance of the Git object using the repository() constructor.
 (In the future, we will also get a new_repository() constructor.) All commands
 called as methods of the object are then executed in the context of the
@@ -275,7 +275,7 @@ sub command {
 
        } else {
                my @lines = <$fh>;
-               chomp @lines;
+               defined and chomp for @lines;
                try {
                        _cmd_close($fh, $ctx);
                } catch Git::Error::Command with {
@@ -354,7 +354,7 @@ sub command_input_pipe {
 =item command_close_pipe ( PIPE [, CTX ] )
 
 Close the C<PIPE> as returned from C<command_*_pipe()>, checking
-whether the command finished successfuly. The optional C<CTX> argument
+whether the command finished successfully. The optional C<CTX> argument
 is required if you want to see the command name in the error message,
 and it is the second value returned by C<command_*_pipe()> when
 called in array context. The call idiom is:
@@ -482,14 +482,14 @@ sub wc_chdir {
 
 =item config ( VARIABLE )
 
-Retrieve the configuration C<VARIABLE> in the same manner as C<repo-config>
+Retrieve the configuration C<VARIABLE> in the same manner as C<config>
 does. In scalar context requires the variable to be set only one time
 (exception is thrown otherwise), in array context returns allows the
 variable to be set multiple times and returns all the values.
 
 Must be called on a repository instance.
 
-This currently wraps command('repo-config') so it is not so fast.
+This currently wraps command('config') so it is not so fast.
 
 =cut
 
@@ -500,9 +500,9 @@ sub config {
 
        try {
                if (wantarray) {
-                       return $self->command('repo-config', '--get-all', $var);
+                       return $self->command('config', '--get-all', $var);
                } else {
-                       return $self->command_oneline('repo-config', '--get', $var);
+                       return $self->command_oneline('config', '--get', $var);
                }
        } catch Git::Error::Command with {
                my $E = shift;
@@ -736,13 +736,19 @@ sub _command_common_pipe {
        _check_valid_cmd($cmd);
 
        my $fh;
-       if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
+       if ($^O eq 'MSWin32') {
                # ActiveState Perl
                #defined $opts{STDERR} and
                #       warn 'ignoring STDERR option - running w/ ActiveState';
                $direction eq '-|' or
                        die 'input pipe for ActiveState not implemented';
-               tie ($fh, 'Git::activestate_pipe', $cmd, @args);
+               # the strange construction with *ACPIPE is just to
+               # explain the tie below that we want to bind to
+               # a handle class, not scalar. It is not known if
+               # it is something specific to ActiveState Perl or
+               # just a Perl quirk.
+               tie (*ACPIPE, 'Git::activestate_pipe', $cmd, @args);
+               $fh = *ACPIPE;
 
        } else {
                my $pid = open($fh, $direction);
@@ -809,8 +815,9 @@ sub TIEHANDLE {
        # FIXME: This is probably horrible idea and the thing will explode
        # at the moment you give it arguments that require some quoting,
        # but I have no ActiveState clue... --pasky
-       my $cmdline = join " ", @params;
-       my @data = qx{$cmdline};
+       # Let's just hope ActiveState Perl does at least the quoting
+       # correctly.
+       my @data = qx{git @params};
        bless { i => 0, data => \@data }, $class;
 }
 
diff --git a/perl/Makefile b/perl/Makefile
new file mode 100644 (file)
index 0000000..099beda
--- /dev/null
@@ -0,0 +1,39 @@
+#
+# Makefile for perl support modules and routine
+#
+makfile:=perl.mak
+
+PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
+prefix_SQ = $(subst ','\'',$(prefix))
+
+all install instlibdir: $(makfile)
+       $(MAKE) -f $(makfile) $@
+
+clean:
+       test -f $(makfile) && $(MAKE) -f $(makfile) $@ || exit 0
+       $(RM) ppport.h
+       $(RM) $(makfile)
+       $(RM) $(makfile).old
+
+ifdef NO_PERL_MAKEMAKER
+instdir_SQ = $(subst ','\'',$(prefix)/lib)
+$(makfile): ../GIT-CFLAGS Makefile
+       echo all: > $@
+       echo '  :' >> $@
+       echo install: >> $@
+       echo '  mkdir -p $(instdir_SQ)' >> $@
+       echo '  $(RM) $(instdir_SQ)/Git.pm; cp Git.pm $(instdir_SQ)' >> $@
+       echo '  $(RM) $(instdir_SQ)/Error.pm; \
+       cp private-Error.pm $(instdir_SQ)/Error.pm' >> $@
+       echo instlibdir: >> $@
+       echo '  echo $(instdir_SQ)' >> $@
+else
+$(makfile): Makefile.PL ../GIT-CFLAGS
+       '$(PERL_PATH_SQ)' $< PREFIX='$(prefix_SQ)'
+endif
+
+# this is just added comfort for calling make directly in perl dir
+# (even though GIT-CFLAGS aren't used yet. If ever)
+../GIT-CFLAGS:
+       $(MAKE) -C .. GIT-CFLAGS
+
index de73235e4c3142381c4ea01597e18b14cd65bc02..9b117fd0d736615a8c4aeda742384956373d295a 100644 (file)
@@ -20,9 +20,14 @@ if ($@) {
 my %extra;
 $extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR};
 
+# redirect stdout, otherwise the message "Writing perl.mak for Git"
+# disrupts the output for the target 'instlibdir'
+open STDOUT, ">&STDERR";
+
 WriteMakefile(
        NAME            => 'Git',
        VERSION_FROM    => 'Git.pm',
        PM              => \%pm,
+       MAKEFILE        => 'perl.mak',
        %extra
 );
index 8fff86699fc4585ef9378150d3a60edf911ecc8c..11e9cd9a02eb3f85a9150c6fb06d1fc76abd9b09 100644 (file)
@@ -781,7 +781,7 @@ that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified)
 
 This variable holds a reference to a subroutine that converts errors that
 are plain strings to objects. It is used by Error.pm to convert textual
-errors to objects, and can be overrided by the user.
+errors to objects, and can be overridden by the user.
 
 It accepts a single argument which is a hash reference to named parameters.
 Currently the only named parameter passed is C<'text'> which is the text
index c1e81f976f26726db2432f72f7356087a6e3a7d8..b4cb7e2756dcc52c17aebf4c0fc6adc7b415ef3c 100644 (file)
@@ -72,7 +72,7 @@ static void safe_read(int fd, void *buffer, unsigned size)
                if (ret < 0)
                        die("read error (%s)", strerror(errno));
                if (!ret)
-                       die("unexpected EOF");
+                       die("The remote end hung up unexpectedly");
                n += ret;
        }
 }
index 140cb53370fb23eb56b1872a2f328a0840e2820f..f132696ee72bf4a2e3d608a24322a6839f9a03a8 100644 (file)
@@ -18,7 +18,7 @@
  * %r0 - temp
  * %r3 - argument (pointer to 5 words of SHA state)
  * %r4 - argument (pointer to data to hash)
- * %r5 - Contant K in SHA round (initially number of blocks to hash)
+ * %r5 - Constant K in SHA round (initially number of blocks to hash)
  * %r6-%r10 - Working copies of SHA variables A..E (actually E..A order)
  * %r11-%r26 - Data being hashed W[].
  * %r27-%r31 - Previous copies of A..E, for final add back.
@@ -48,7 +48,7 @@
  * E += ROTL(A,5) + F(B,C,D) + W[i] + K;  B = ROTL(B,30)
  * Then the variables are renamed: (A,B,C,D,E) = (E,A,B,C,D).
  *
- * Every 20 rounds, the function F() and the contant K changes:
+ * Every 20 rounds, the function F() and the constant K changes:
  * - 20 rounds of f0(b,c,d) = "bit wise b ? c : d" =  (^b & d) + (b & c)
  * - 20 rounds of f1(b,c,d) = b^c^d = (b^d)^c
  * - 20 rounds of f2(b,c,d) = majority(b,c,d) = (b&d) + ((b^d)&c)
@@ -57,7 +57,7 @@
  * These are all scheduled for near-optimal performance on a G4.
  * The G4 is a 3-issue out-of-order machine with 3 ALUs, but it can only
  * *consider* starting the oldest 3 instructions per cycle.  So to get
- * maximum performace out of it, you have to treat it as an in-order
+ * maximum performance out of it, you have to treat it as an in-order
  * machine.  Which means interleaving the computation round t with the
  * computation of W[t+4].
  *
diff --git a/quote.c b/quote.c
index a418a0f803f91d218b66ae89a1e8615a94ff27bc..fb9e4ca253ea9bcadbcb55dcdd62be614c66758f 100644 (file)
--- a/quote.c
+++ b/quote.c
@@ -387,3 +387,37 @@ void python_quote_print(FILE *stream, const char *src)
        }
        fputc(sq, stream);
 }
+
+void tcl_quote_print(FILE *stream, const char *src)
+{
+       char c;
+
+       fputc('"', stream);
+       while ((c = *src++)) {
+               switch (c) {
+               case '[': case ']':
+               case '{': case '}':
+               case '$': case '\\': case '"':
+                       fputc('\\', stream);
+               default:
+                       fputc(c, stream);
+                       break;
+               case '\f':
+                       fputs("\\f", stream);
+                       break;
+               case '\r':
+                       fputs("\\r", stream);
+                       break;
+               case '\n':
+                       fputs("\\n", stream);
+                       break;
+               case '\t':
+                       fputs("\\t", stream);
+                       break;
+               case '\v':
+                       fputs("\\v", stream);
+                       break;
+               }
+       }
+       fputc('"', stream);
+}
diff --git a/quote.h b/quote.h
index b55e69975091193f62b293f90a776ca9f6112ab4..bdc3610df51000baeda49da1eeaec20ccb2682fb 100644 (file)
--- a/quote.h
+++ b/quote.h
@@ -55,5 +55,6 @@ extern void write_name_quoted(const char *prefix, int prefix_len,
 /* quoting as a string literal for other languages */
 extern void perl_quote_print(FILE *stream, const char *src);
 extern void python_quote_print(FILE *stream, const char *src);
+extern void tcl_quote_print(FILE *stream, const char *src);
 
 #endif
diff --git a/reachable.c b/reachable.c
new file mode 100644 (file)
index 0000000..01760d7
--- /dev/null
@@ -0,0 +1,201 @@
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "blob.h"
+#include "diff.h"
+#include "revision.h"
+#include "reachable.h"
+#include "cache-tree.h"
+
+static void process_blob(struct blob *blob,
+                        struct object_array *p,
+                        struct name_path *path,
+                        const char *name)
+{
+       struct object *obj = &blob->object;
+
+       if (obj->flags & SEEN)
+               return;
+       obj->flags |= SEEN;
+       /* Nothing to do, really .. The blob lookup was the important part */
+}
+
+static void process_tree(struct tree *tree,
+                        struct object_array *p,
+                        struct name_path *path,
+                        const char *name)
+{
+       struct object *obj = &tree->object;
+       struct tree_desc desc;
+       struct name_entry entry;
+       struct name_path me;
+
+       if (obj->flags & SEEN)
+               return;
+       obj->flags |= SEEN;
+       if (parse_tree(tree) < 0)
+               die("bad tree object %s", sha1_to_hex(obj->sha1));
+       name = xstrdup(name);
+       add_object(obj, p, path, name);
+       me.up = path;
+       me.elem = name;
+       me.elem_len = strlen(name);
+
+       desc.buf = tree->buffer;
+       desc.size = tree->size;
+
+       while (tree_entry(&desc, &entry)) {
+               if (S_ISDIR(entry.mode))
+                       process_tree(lookup_tree(entry.sha1), p, &me, entry.path);
+               else
+                       process_blob(lookup_blob(entry.sha1), p, &me, entry.path);
+       }
+       free(tree->buffer);
+       tree->buffer = NULL;
+}
+
+static void process_tag(struct tag *tag, struct object_array *p, const char *name)
+{
+       struct object *obj = &tag->object;
+       struct name_path me;
+
+       if (obj->flags & SEEN)
+               return;
+       obj->flags |= SEEN;
+
+       me.up = NULL;
+       me.elem = "tag:/";
+       me.elem_len = 5;
+
+       if (parse_tag(tag) < 0)
+               die("bad tag object %s", sha1_to_hex(obj->sha1));
+       add_object(tag->tagged, p, NULL, name);
+}
+
+static void walk_commit_list(struct rev_info *revs)
+{
+       int i;
+       struct commit *commit;
+       struct object_array objects = { 0, 0, NULL };
+
+       /* Walk all commits, process their trees */
+       while ((commit = get_revision(revs)) != NULL)
+               process_tree(commit->tree, &objects, NULL, "");
+
+       /* Then walk all the pending objects, recursively processing them too */
+       for (i = 0; i < revs->pending.nr; i++) {
+               struct object_array_entry *pending = revs->pending.objects + i;
+               struct object *obj = pending->item;
+               const char *name = pending->name;
+               if (obj->type == OBJ_TAG) {
+                       process_tag((struct tag *) obj, &objects, name);
+                       continue;
+               }
+               if (obj->type == OBJ_TREE) {
+                       process_tree((struct tree *)obj, &objects, NULL, name);
+                       continue;
+               }
+               if (obj->type == OBJ_BLOB) {
+                       process_blob((struct blob *)obj, &objects, NULL, name);
+                       continue;
+               }
+               die("unknown pending object %s (%s)", sha1_to_hex(obj->sha1), name);
+       }
+}
+
+static int add_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct object *object;
+       struct rev_info *revs = (struct rev_info *)cb_data;
+
+       object = parse_object(osha1);
+       if (object)
+               add_pending_object(revs, object, "");
+       object = parse_object(nsha1);
+       if (object)
+               add_pending_object(revs, object, "");
+       return 0;
+}
+
+static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct object *object = parse_object(sha1);
+       struct rev_info *revs = (struct rev_info *)cb_data;
+
+       if (!object)
+               die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
+       add_pending_object(revs, object, "");
+
+       return 0;
+}
+
+static int add_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       for_each_reflog_ent(path, add_one_reflog_ent, cb_data);
+       return 0;
+}
+
+static void add_one_tree(const unsigned char *sha1, struct rev_info *revs)
+{
+       struct tree *tree = lookup_tree(sha1);
+       add_pending_object(revs, &tree->object, "");
+}
+
+static void add_cache_tree(struct cache_tree *it, struct rev_info *revs)
+{
+       int i;
+
+       if (it->entry_count >= 0)
+               add_one_tree(it->sha1, revs);
+       for (i = 0; i < it->subtree_nr; i++)
+               add_cache_tree(it->down[i]->cache_tree, revs);
+}
+
+static void add_cache_refs(struct rev_info *revs)
+{
+       int i;
+
+       read_cache();
+       for (i = 0; i < active_nr; i++) {
+               lookup_blob(active_cache[i]->sha1);
+               /*
+                * We could add the blobs to the pending list, but quite
+                * frankly, we don't care. Once we've looked them up, and
+                * added them as objects, we've really done everything
+                * there is to do for a blob
+                */
+       }
+       if (active_cache_tree)
+               add_cache_tree(active_cache_tree, revs);
+}
+
+void mark_reachable_objects(struct rev_info *revs, int mark_reflog)
+{
+       /*
+        * Set up revision parsing, and mark us as being interested
+        * in all object types, not just commits.
+        */
+       revs->tag_objects = 1;
+       revs->blob_objects = 1;
+       revs->tree_objects = 1;
+
+       /* Add all refs from the index file */
+       add_cache_refs(revs);
+
+       /* Add all external refs */
+       for_each_ref(add_one_ref, revs);
+
+       /* Add all reflog info */
+       if (mark_reflog)
+               for_each_reflog(add_one_reflog, revs);
+
+       /*
+        * Set up the revision walk - this will move all commits
+        * from the pending list to the commit walking list.
+        */
+       prepare_revision_walk(revs);
+       walk_commit_list(revs);
+}
diff --git a/reachable.h b/reachable.h
new file mode 100644 (file)
index 0000000..4075181
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef REACHEABLE_H
+#define REACHEABLE_H
+
+extern void mark_reachable_objects(struct rev_info *revs, int mark_reflog);
+
+#endif
index 0f5fb5bc3350f7def1eb14e792507098118d1e66..c54a61187711087b98138b9598db6353457e4df3 100644 (file)
@@ -347,18 +347,18 @@ int add_file_to_index(const char *path, int verbose)
        ce->ce_mode = create_ce_mode(st.st_mode);
        if (!trust_executable_bit) {
                /* If there is an existing entry, pick the mode bits
-                * from it, otherwise force to 644.
+                * from it, otherwise assume unexecutable.
                 */
                int pos = cache_name_pos(path, namelen);
                if (pos >= 0)
                        ce->ce_mode = active_cache[pos]->ce_mode;
-               else
-                       ce->ce_mode = create_ce_mode(S_IFREG | 0644);
+               else if (S_ISREG(st.st_mode))
+                       ce->ce_mode = create_ce_mode(S_IFREG | 0666);
        }
 
        if (index_path(ce->sha1, path, &st, 1))
                die("unable to index file %s", path);
-       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD))
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
                die("unable to add %s to index",path);
        if (verbose)
                printf("add '%s'\n", path);
@@ -517,7 +517,7 @@ static int has_dir_name(const struct cache_entry *ce, int pos, int ok_to_replace
                pos = cache_name_pos(name, ntohs(create_ce_flags(len, stage)));
                if (pos >= 0) {
                        retval = -1;
-                       if (ok_to_replace)
+                       if (!ok_to_replace)
                                break;
                        remove_cache_entry_at(pos);
                        continue;
@@ -609,7 +609,7 @@ int add_cache_entry(struct cache_entry *ce, int option)
        if (!skip_df_check &&
            check_file_directory_conflict(ce, pos, ok_to_replace)) {
                if (!ok_to_replace)
-                       return -1;
+                       return error("'%s' appears as both a file and as a directory", ce->name);
                pos = cache_name_pos(ce->name, ntohs(ce->ce_flags));
                pos = -pos-1;
        }
@@ -793,16 +793,16 @@ int read_cache_from(const char *path)
                die("index file open failed (%s)", strerror(errno));
        }
 
-       cache_mmap = MAP_FAILED;
        if (!fstat(fd, &st)) {
                cache_mmap_size = st.st_size;
                errno = EINVAL;
                if (cache_mmap_size >= sizeof(struct cache_header) + 20)
-                       cache_mmap = mmap(NULL, cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
-       }
+                       cache_mmap = xmmap(NULL, cache_mmap_size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+               else
+                       die("index file smaller than expected");
+       } else
+               die("cannot stat the open index (%s)", strerror(errno));
        close(fd);
-       if (cache_mmap == MAP_FAILED)
-               die("index file mmap failed (%s)", strerror(errno));
 
        hdr = cache_mmap;
        if (verify_hdr(hdr, cache_mmap_size) < 0)
@@ -870,7 +870,7 @@ static int ce_write_flush(SHA_CTX *context, int fd)
        unsigned int buffered = write_buffer_len;
        if (buffered) {
                SHA1_Update(context, write_buffer, buffered);
-               if (write(fd, write_buffer, buffered) != buffered)
+               if (write_in_full(fd, write_buffer, buffered) != buffered)
                        return -1;
                write_buffer_len = 0;
        }
@@ -919,7 +919,7 @@ static int ce_flush(SHA_CTX *context, int fd)
 
        /* Flush first if not enough space for SHA1 signature */
        if (left + 20 > WRITE_BUFFER_SIZE) {
-               if (write(fd, write_buffer, left) != left)
+               if (write_in_full(fd, write_buffer, left) != left)
                        return -1;
                left = 0;
        }
@@ -927,7 +927,7 @@ static int ce_flush(SHA_CTX *context, int fd)
        /* Append the SHA1 signature at the end */
        SHA1_Final(write_buffer + left, context);
        left += 20;
-       return (write(fd, write_buffer, left) != left) ? -1 : 0;
+       return (write_in_full(fd, write_buffer, left) != left) ? -1 : 0;
 }
 
 static void ce_smudge_racily_clean_entry(struct cache_entry *ce)
@@ -1010,7 +1010,7 @@ int write_cache(int newfd, struct cache_entry **cache, int entries)
                if (data &&
                    !write_index_ext_header(&c, newfd, CACHE_EXT_TREE, sz) &&
                    !ce_write(&c, newfd, data, sz))
-                       ;
+                       free(data);
                else {
                        free(data);
                        return -1;
index d56898c9b29d3e5ea01fd5a211790141d0beac21..7311c822dd3af2c6a1cd8da47184428d5b627b3e 100644 (file)
@@ -6,34 +6,36 @@
 #include "exec_cmd.h"
 #include "commit.h"
 #include "object.h"
-#include <sys/wait.h>
 
 static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
 
 static int deny_non_fast_forwards = 0;
-static int unpack_limit = 5000;
+static int receive_unpack_limit = -1;
+static int transfer_unpack_limit = -1;
+static int unpack_limit = 100;
 static int report_status;
 
-static char capabilities[] = "report-status";
+static char capabilities[] = " report-status delete-refs ";
 static int capabilities_sent;
 
 static int receive_pack_config(const char *var, const char *value)
 {
-       git_default_config(var, value);
-
-       if (strcmp(var, "receive.denynonfastforwards") == 0)
-       {
+       if (strcmp(var, "receive.denynonfastforwards") == 0) {
                deny_non_fast_forwards = git_config_bool(var, value);
                return 0;
        }
 
-       if (strcmp(var, "receive.unpacklimit") == 0)
-       {
-               unpack_limit = git_config_int(var, value);
+       if (strcmp(var, "receive.unpacklimit") == 0) {
+               receive_unpack_limit = git_config_int(var, value);
                return 0;
        }
 
-       return 0;
+       if (strcmp(var, "transfer.unpacklimit") == 0) {
+               transfer_unpack_limit = git_config_int(var, value);
+               return 0;
+       }
+
+       return git_default_config(var, value);
 }
 
 static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
@@ -74,7 +76,9 @@ static int run_update_hook(const char *refname,
 
        if (access(update_hook, X_OK) < 0)
                return 0;
-       code = run_command(update_hook, refname, old_hex, new_hex, NULL);
+       code = run_command_opt(RUN_COMMAND_NO_STDIN
+               | RUN_COMMAND_STDOUT_TO_STDERR,
+               update_hook, refname, old_hex, new_hex, NULL);
        switch (code) {
        case 0:
                return 0;
@@ -113,12 +117,15 @@ static int update(struct command *cmd)
 
        strcpy(new_hex, sha1_to_hex(new_sha1));
        strcpy(old_hex, sha1_to_hex(old_sha1));
-       if (!has_sha1_file(new_sha1)) {
+
+       if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
                cmd->error_string = "bad pack";
                return error("unpack should have generated %s, "
                             "but I can't find it!", new_hex);
        }
-       if (deny_non_fast_forwards && !is_null_sha1(old_sha1)) {
+       if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
+           !is_null_sha1(old_sha1) &&
+           !strncmp(name, "refs/heads/", 11)) {
                struct commit *old_commit, *new_commit;
                struct commit_list *bases, *ent;
 
@@ -138,14 +145,22 @@ static int update(struct command *cmd)
                return error("hook declined to update %s", name);
        }
 
-       lock = lock_any_ref_for_update(name, old_sha1);
-       if (!lock) {
-               cmd->error_string = "failed to lock";
-               return error("failed to lock %s", name);
+       if (is_null_sha1(new_sha1)) {
+               if (delete_ref(name, old_sha1)) {
+                       cmd->error_string = "failed to delete";
+                       return error("failed to delete %s", name);
+               }
+               fprintf(stderr, "%s: %s -> deleted\n", name, old_hex);
+       }
+       else {
+               lock = lock_any_ref_for_update(name, old_sha1);
+               if (!lock) {
+                       cmd->error_string = "failed to lock";
+                       return error("failed to lock %s", name);
+               }
+               write_ref_sha1(lock, new_sha1, "push");
+               fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
        }
-       write_ref_sha1(lock, new_sha1, "push");
-
-       fprintf(stderr, "%s: %s -> %s\n", name, old_hex, new_hex);
        return 0;
 }
 
@@ -177,7 +192,8 @@ static void run_update_post_hook(struct command *cmd)
                argc++;
        }
        argv[argc] = NULL;
-       run_command_v_opt(argc, argv, RUN_COMMAND_NO_STDIO);
+       run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
+               | RUN_COMMAND_STDOUT_TO_STDERR);
 }
 
 /*
@@ -237,20 +253,22 @@ static void read_head_info(void)
 
 static const char *parse_pack_header(struct pack_header *hdr)
 {
-       char *c = (char*)hdr;
-       ssize_t remaining = sizeof(struct pack_header);
-       do {
-               ssize_t r = xread(0, c, remaining);
-               if (r <= 0)
-                       return "eof before pack header was fully read";
-               remaining -= r;
-               c += r;
-       } while (remaining > 0);
-       if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
+       switch (read_pack_header(0, hdr)) {
+       case PH_ERROR_EOF:
+               return "eof before pack header was fully read";
+
+       case PH_ERROR_PACK_SIGNATURE:
                return "protocol error (pack signature mismatch detected)";
-       if (!pack_version_ok(hdr->hdr_version))
+
+       case PH_ERROR_PROTOCOL:
                return "protocol error (pack version unsupported)";
-       return NULL;
+
+       default:
+               return "unknown error in parse_pack_header";
+
+       case 0:
+               return NULL;
+       }
 }
 
 static const char *pack_lockfile;
@@ -273,7 +291,7 @@ static const char *unpack(void)
                unpacker[0] = "unpack-objects";
                unpacker[1] = hdr_arg;
                unpacker[2] = NULL;
-               code = run_command_v_opt(1, unpacker, RUN_GIT_CMD);
+               code = run_command_v_opt(unpacker, RUN_GIT_CMD);
                switch (code) {
                case 0:
                        return NULL;
@@ -375,6 +393,16 @@ static void report(const char *unpack_status)
        packet_flush(1);
 }
 
+static int delete_only(struct command *cmd)
+{
+       while (cmd) {
+               if (!is_null_sha1(cmd->new_sha1))
+                       return 0;
+               cmd = cmd->next;
+       }
+       return 1;
+}
+
 int main(int argc, char **argv)
 {
        int i;
@@ -398,9 +426,16 @@ int main(int argc, char **argv)
        if (!enter_repo(dir, 0))
                die("'%s': unable to chdir or not a git archive", dir);
 
-       setup_ident();
+       if (is_repository_shallow())
+               die("attempt to push into a shallow repository");
+
        git_config(receive_pack_config);
 
+       if (0 <= transfer_unpack_limit)
+               unpack_limit = transfer_unpack_limit;
+       else if (0 <= receive_unpack_limit)
+               unpack_limit = receive_unpack_limit;
+
        write_head_info();
 
        /* EOF */
@@ -408,7 +443,10 @@ int main(int argc, char **argv)
 
        read_head_info();
        if (commands) {
-               const char *unpack_status = unpack();
+               const char *unpack_status = NULL;
+
+               if (!delete_only(commands))
+                       unpack_status = unpack();
                if (!unpack_status)
                        execute_commands();
                if (pack_lockfile)
diff --git a/reflog-walk.c b/reflog-walk.c
new file mode 100644 (file)
index 0000000..c983858
--- /dev/null
@@ -0,0 +1,273 @@
+#include "cache.h"
+#include "commit.h"
+#include "refs.h"
+#include "diff.h"
+#include "revision.h"
+#include "path-list.h"
+#include "reflog-walk.h"
+
+struct complete_reflogs {
+       char *ref;
+       struct reflog_info {
+               unsigned char osha1[20], nsha1[20];
+               char *email;
+               unsigned long timestamp;
+               int tz;
+               char *message;
+       } *items;
+       int nr, alloc;
+};
+
+static int read_one_reflog(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       struct complete_reflogs *array = cb_data;
+       struct reflog_info *item;
+
+       if (array->nr >= array->alloc) {
+               array->alloc = alloc_nr(array->nr + 1);
+               array->items = xrealloc(array->items, array->alloc *
+                       sizeof(struct reflog_info));
+       }
+       item = array->items + array->nr;
+       memcpy(item->osha1, osha1, 20);
+       memcpy(item->nsha1, nsha1, 20);
+       item->email = xstrdup(email);
+       item->timestamp = timestamp;
+       item->tz = tz;
+       item->message = xstrdup(message);
+       array->nr++;
+       return 0;
+}
+
+static struct complete_reflogs *read_complete_reflog(const char *ref)
+{
+       struct complete_reflogs *reflogs =
+               xcalloc(sizeof(struct complete_reflogs), 1);
+       reflogs->ref = xstrdup(ref);
+       for_each_reflog_ent(ref, read_one_reflog, reflogs);
+       if (reflogs->nr == 0) {
+               unsigned char sha1[20];
+               const char *name = resolve_ref(ref, sha1, 1, NULL);
+               if (name)
+                       for_each_reflog_ent(name, read_one_reflog, reflogs);
+       }
+       if (reflogs->nr == 0) {
+               int len = strlen(ref);
+               char *refname = xmalloc(len + 12);
+               sprintf(refname, "refs/%s", ref);
+               for_each_reflog_ent(refname, read_one_reflog, reflogs);
+               if (reflogs->nr == 0) {
+                       sprintf(refname, "refs/heads/%s", ref);
+                       for_each_reflog_ent(refname, read_one_reflog, reflogs);
+               }
+               free(refname);
+       }
+       return reflogs;
+}
+
+static int get_reflog_recno_by_time(struct complete_reflogs *array,
+       unsigned long timestamp)
+{
+       int i;
+       for (i = array->nr - 1; i >= 0; i--)
+               if (timestamp >= array->items[i].timestamp)
+                       return i;
+       return -1;
+}
+
+struct commit_info_lifo {
+       struct commit_info {
+               struct commit *commit;
+               void *util;
+       } *items;
+       int nr, alloc;
+};
+
+static struct commit_info *get_commit_info(struct commit *commit,
+               struct commit_info_lifo *lifo, int pop)
+{
+       int i;
+       for (i = 0; i < lifo->nr; i++)
+               if (lifo->items[i].commit == commit) {
+                       struct commit_info *result = &lifo->items[i];
+                       if (pop) {
+                               if (i + 1 < lifo->nr)
+                                       memmove(lifo->items + i,
+                                               lifo->items + i + 1,
+                                               (lifo->nr - i) *
+                                               sizeof(struct commit_info));
+                               lifo->nr--;
+                       }
+                       return result;
+               }
+       return NULL;
+}
+
+static void add_commit_info(struct commit *commit, void *util,
+               struct commit_info_lifo *lifo)
+{
+       struct commit_info *info;
+       if (lifo->nr >= lifo->alloc) {
+               lifo->alloc = alloc_nr(lifo->nr + 1);
+               lifo->items = xrealloc(lifo->items,
+                       lifo->alloc * sizeof(struct commit_info));
+       }
+       info = lifo->items + lifo->nr;
+       info->commit = commit;
+       info->util = util;
+       lifo->nr++;
+}
+
+struct commit_reflog {
+       int flag, recno;
+       struct complete_reflogs *reflogs;
+};
+
+struct reflog_walk_info {
+       struct commit_info_lifo reflogs;
+       struct path_list complete_reflogs;
+       struct commit_reflog *last_commit_reflog;
+};
+
+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,
+               struct commit *commit, const char *name)
+{
+       unsigned long timestamp = 0;
+       int recno = -1;
+       struct path_list_item *item;
+       struct complete_reflogs *reflogs;
+       char *branch, *at = strchr(name, '@');
+       struct commit_reflog *commit_reflog;
+
+       if (commit->object.flags & UNINTERESTING)
+               die ("Cannot walk reflogs for %s", name);
+
+       branch = xstrdup(name);
+       if (at && at[1] == '{') {
+               char *ep;
+               branch[at - name] = '\0';
+               recno = strtoul(at + 2, &ep, 10);
+               if (*ep != '}') {
+                       recno = -1;
+                       timestamp = approxidate(at + 2);
+               }
+       } else
+               recno = 0;
+
+       item = path_list_lookup(branch, &info->complete_reflogs);
+       if (item)
+               reflogs = item->util;
+       else {
+               if (*branch == '\0') {
+                       unsigned char sha1[20];
+                       const char *head = resolve_ref("HEAD", sha1, 0, NULL);
+                       if (!head)
+                               die ("No current branch");
+                       free(branch);
+                       branch = xstrdup(head);
+               }
+               reflogs = read_complete_reflog(branch);
+               if (!reflogs || reflogs->nr == 0) {
+                       unsigned char sha1[20];
+                       char *b;
+                       if (dwim_log(branch, strlen(branch), sha1, &b) == 1) {
+                               if (reflogs) {
+                                       free(reflogs->ref);
+                                       free(reflogs);
+                               }
+                               free(branch);
+                               branch = b;
+                               reflogs = read_complete_reflog(branch);
+                       }
+               }
+               if (!reflogs || reflogs->nr == 0)
+                       die("No reflogs found for '%s'", branch);
+               path_list_insert(branch, &info->complete_reflogs)->util
+                       = reflogs;
+       }
+
+       commit_reflog = xcalloc(sizeof(struct commit_reflog), 1);
+       if (recno < 0) {
+               commit_reflog->flag = 1;
+               commit_reflog->recno = get_reflog_recno_by_time(reflogs, timestamp);
+               if (commit_reflog->recno < 0) {
+                       free(branch);
+                       free(commit_reflog);
+                       return;
+               }
+       } else
+               commit_reflog->recno = reflogs->nr - recno - 1;
+       commit_reflog->reflogs = reflogs;
+
+       add_commit_info(commit, commit_reflog, &info->reflogs);
+}
+
+void fake_reflog_parent(struct reflog_walk_info *info, struct commit *commit)
+{
+       struct commit_info *commit_info =
+               get_commit_info(commit, &info->reflogs, 0);
+       struct commit_reflog *commit_reflog;
+       struct reflog_info *reflog;
+
+       info->last_commit_reflog = NULL;
+       if (!commit_info)
+               return;
+
+       commit_reflog = commit_info->util;
+       if (commit_reflog->recno < 0) {
+               commit->parents = NULL;
+               return;
+       }
+
+       reflog = &commit_reflog->reflogs->items[commit_reflog->recno];
+       info->last_commit_reflog = commit_reflog;
+       commit_reflog->recno--;
+       commit_info->commit = (struct commit *)parse_object(reflog->osha1);
+       if (!commit_info->commit) {
+               commit->parents = NULL;
+               return;
+       }
+
+       commit->parents = xcalloc(sizeof(struct commit_list), 1);
+       commit->parents->item = commit_info->commit;
+       commit->object.flags &= ~(ADDED | SEEN | SHOWN);
+}
+
+void show_reflog_message(struct reflog_walk_info* info, int oneline,
+       int relative_date)
+{
+       if (info && info->last_commit_reflog) {
+               struct commit_reflog *commit_reflog = info->last_commit_reflog;
+               struct reflog_info *info;
+
+               info = &commit_reflog->reflogs->items[commit_reflog->recno+1];
+               if (oneline) {
+                       printf("%s@{", commit_reflog->reflogs->ref);
+                       if (commit_reflog->flag || relative_date)
+                               printf("%s", show_date(info->timestamp, 0, 1));
+                       else
+                               printf("%d", commit_reflog->reflogs->nr
+                                      - 2 - commit_reflog->recno);
+                       printf("}: %s", info->message);
+               }
+               else {
+                       printf("Reflog: %s@{", commit_reflog->reflogs->ref);
+                       if (commit_reflog->flag || relative_date)
+                               printf("%s", show_date(info->timestamp,
+                                                       info->tz,
+                                                       relative_date));
+                       else
+                               printf("%d", commit_reflog->reflogs->nr
+                                      - 2 - commit_reflog->recno);
+                       printf("} (%s)\nReflog message: %s",
+                              info->email, info->message);
+               }
+       }
+}
diff --git a/reflog-walk.h b/reflog-walk.h
new file mode 100644 (file)
index 0000000..a4f7015
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef REFLOG_WALK_H
+#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,
+               struct commit *commit, const char *name);
+extern void fake_reflog_parent(struct reflog_walk_info *info,
+               struct commit *commit);
+extern void show_reflog_message(struct reflog_walk_info *info, int, int);
+
+#endif
diff --git a/refs.c b/refs.c
index 0e156c5dee1edff895c576a4491ec71f931ff492..63877037893603c385e63547c83ea8c59f7c3101 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1,12 +1,16 @@
-#include "refs.h"
 #include "cache.h"
+#include "refs.h"
+#include "object.h"
+#include "tag.h"
 
-#include <errno.h>
+/* ISSYMREF=01 and ISPACKED=02 are public interfaces */
+#define REF_KNOWS_PEELED 04
 
 struct ref_list {
        struct ref_list *next;
        unsigned char flag; /* ISSYMREF? ISPACKED? */
        unsigned char sha1[20];
+       unsigned char peeled[20];
        char name[FLEX_ARRAY];
 };
 
@@ -34,11 +38,13 @@ static const char *parse_ref_line(char *line, unsigned char *sha1)
        if (line[len] != '\n')
                return NULL;
        line[len] = 0;
+
        return line;
 }
 
 static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
-                               int flag, struct ref_list *list)
+                               int flag, struct ref_list *list,
+                               struct ref_list **new_entry)
 {
        int len;
        struct ref_list **p = &list, *entry;
@@ -50,8 +56,11 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
                        break;
 
                /* Same as existing entry? */
-               if (!cmp)
+               if (!cmp) {
+                       if (new_entry)
+                               *new_entry = entry;
                        return list;
+               }
                p = &entry->next;
        }
 
@@ -59,10 +68,13 @@ static struct ref_list *add_ref(const char *name, const unsigned char *sha1,
        len = strlen(name) + 1;
        entry = xmalloc(sizeof(struct ref_list) + len);
        hashcpy(entry->sha1, sha1);
+       hashclr(entry->peeled);
        memcpy(entry->name, name, len);
        entry->flag = flag;
        entry->next = *p;
        *p = entry;
+       if (new_entry)
+               *new_entry = entry;
        return list;
 }
 
@@ -98,25 +110,50 @@ static void invalidate_cached_refs(void)
        ca->did_loose = ca->did_packed = 0;
 }
 
+static void read_packed_refs(FILE *f, struct cached_refs *cached_refs)
+{
+       struct ref_list *list = NULL;
+       struct ref_list *last = NULL;
+       char refline[PATH_MAX];
+       int flag = REF_ISPACKED;
+
+       while (fgets(refline, sizeof(refline), f)) {
+               unsigned char sha1[20];
+               const char *name;
+               static const char header[] = "# pack-refs with:";
+
+               if (!strncmp(refline, header, sizeof(header)-1)) {
+                       const char *traits = refline + sizeof(header) - 1;
+                       if (strstr(traits, " peeled "))
+                               flag |= REF_KNOWS_PEELED;
+                       /* perhaps other traits later as well */
+                       continue;
+               }
+
+               name = parse_ref_line(refline, sha1);
+               if (name) {
+                       list = add_ref(name, sha1, flag, list, &last);
+                       continue;
+               }
+               if (last &&
+                   refline[0] == '^' &&
+                   strlen(refline) == 42 &&
+                   refline[41] == '\n' &&
+                   !get_sha1_hex(refline + 1, sha1))
+                       hashcpy(last->peeled, sha1);
+       }
+       cached_refs->packed = list;
+}
+
 static struct ref_list *get_packed_refs(void)
 {
        if (!cached_refs.did_packed) {
-               struct ref_list *refs = NULL;
                FILE *f = fopen(git_path("packed-refs"), "r");
+               cached_refs.packed = NULL;
                if (f) {
-                       struct ref_list *list = NULL;
-                       char refline[PATH_MAX];
-                       while (fgets(refline, sizeof(refline), f)) {
-                               unsigned char sha1[20];
-                               const char *name = parse_ref_line(refline, sha1);
-                               if (!name)
-                                       continue;
-                               list = add_ref(name, sha1, REF_ISPACKED, list);
-                       }
+                       read_packed_refs(f, &cached_refs);
                        fclose(f);
-                       refs = list;
                }
-               cached_refs.packed = refs;
                cached_refs.did_packed = 1;
        }
        return cached_refs.packed;
@@ -159,7 +196,7 @@ static struct ref_list *get_ref_dir(const char *base, struct ref_list *list)
                                error("%s points nowhere!", ref);
                                continue;
                        }
-                       list = add_ref(ref, sha1, flag, list);
+                       list = add_ref(ref, sha1, flag, list, NULL);
                }
                free(ref);
                closedir(dir);
@@ -247,7 +284,7 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
                fd = open(path, O_RDONLY);
                if (fd < 0)
                        return NULL;
-               len = read(fd, buffer, sizeof(buffer)-1);
+               len = read_in_full(fd, buffer, sizeof(buffer)-1);
                close(fd);
 
                /*
@@ -272,49 +309,6 @@ const char *resolve_ref(const char *ref, unsigned char *sha1, int reading, int *
        return ref;
 }
 
-int create_symref(const char *ref_target, const char *refs_heads_master)
-{
-       const char *lockpath;
-       char ref[1000];
-       int fd, len, written;
-       const char *git_HEAD = git_path("%s", ref_target);
-
-#ifndef NO_SYMLINK_HEAD
-       if (prefer_symlink_refs) {
-               unlink(git_HEAD);
-               if (!symlink(refs_heads_master, git_HEAD))
-                       return 0;
-               fprintf(stderr, "no symlink - falling back to symbolic ref\n");
-       }
-#endif
-
-       len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
-       if (sizeof(ref) <= len) {
-               error("refname too long: %s", refs_heads_master);
-               return -1;
-       }
-       lockpath = mkpath("%s.lock", git_HEAD);
-       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666); 
-       written = write(fd, ref, len);
-       close(fd);
-       if (written != len) {
-               unlink(lockpath);
-               error("Unable to write to %s", lockpath);
-               return -2;
-       }
-       if (rename(lockpath, git_HEAD) < 0) {
-               unlink(lockpath);
-               error("Unable to create %s", git_HEAD);
-               return -3;
-       }
-       if (adjust_shared_perm(git_HEAD)) {
-               unlink(lockpath);
-               error("Unable to fix permissions on %s", lockpath);
-               return -4;
-       }
-       return 0;
-}
-
 int read_ref(const char *ref, unsigned char *sha1)
 {
        if (resolve_ref(ref, sha1, 1, NULL))
@@ -336,6 +330,43 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
        return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
 }
 
+int peel_ref(const char *ref, unsigned char *sha1)
+{
+       int flag;
+       unsigned char base[20];
+       struct object *o;
+
+       if (!resolve_ref(ref, base, 1, &flag))
+               return -1;
+
+       if ((flag & REF_ISPACKED)) {
+               struct ref_list *list = get_packed_refs();
+
+               while (list) {
+                       if (!strcmp(list->name, ref)) {
+                               if (list->flag & REF_KNOWS_PEELED) {
+                                       hashcpy(sha1, list->peeled);
+                                       return 0;
+                               }
+                               /* older pack-refs did not leave peeled ones */
+                               break;
+                       }
+                       list = list->next;
+               }
+       }
+
+       /* fallback - callers should not call this for unpacked refs */
+       o = parse_object(base);
+       if (o->type == OBJ_TAG) {
+               o = deref_tag(o, ref, 0);
+               if (o) {
+                       hashcpy(sha1, o->sha1);
+                       return 0;
+               }
+       }
+       return -1;
+}
+
 static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
                           void *cb_data)
 {
@@ -458,7 +489,7 @@ int check_ref_format(const char *ref)
                level++;
                if (!ch) {
                        if (level < 2)
-                               return -1; /* at least of form "heads/blah" */
+                               return -2; /* at least of form "heads/blah" */
                        return 0;
                }
        }
@@ -534,6 +565,29 @@ static int remove_empty_directories(char *file)
        return remove_empty_dir_recursive(path, len);
 }
 
+static int is_refname_available(const char *ref, const char *oldref,
+                               struct ref_list *list, int quiet)
+{
+       int namlen = strlen(ref); /* e.g. 'foo/bar' */
+       while (list) {
+               /* list->name could be 'foo' or 'foo/bar/baz' */
+               if (!oldref || strcmp(oldref, list->name)) {
+                       int len = strlen(list->name);
+                       int cmplen = (namlen < len) ? namlen : len;
+                       const char *lead = (namlen < len) ? list->name : ref;
+                       if (!strncmp(ref, list->name, cmplen) &&
+                           lead[cmplen] == '/') {
+                               if (!quiet)
+                                       error("'%s' exists; cannot create '%s'",
+                                             list->name, ref);
+                               return 0;
+                       }
+               }
+               list = list->next;
+       }
+       return 1;
+}
+
 static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char *old_sha1, int *flag)
 {
        char *ref_file;
@@ -567,34 +621,19 @@ static struct ref_lock *lock_ref_sha1_basic(const char *ref, const unsigned char
                        orig_ref, strerror(errno));
                goto error_return;
        }
-       if (is_null_sha1(lock->old_sha1)) {
-               /* The ref did not exist and we are creating it.
-                * Make sure there is no existing ref that is packed
-                * whose name begins with our refname, nor a ref whose
-                * name is a proper prefix of our refname.
-                */
-               int namlen = strlen(ref); /* e.g. 'foo/bar' */
-               struct ref_list *list = get_packed_refs();
-               while (list) {
-                       /* list->name could be 'foo' or 'foo/bar/baz' */
-                       int len = strlen(list->name);
-                       int cmplen = (namlen < len) ? namlen : len;
-                       const char *lead = (namlen < len) ? list->name : ref;
-
-                       if (!strncmp(ref, list->name, cmplen) &&
-                           lead[cmplen] == '/') {
-                               error("'%s' exists; cannot create '%s'",
-                                     list->name, ref);
-                               goto error_return;
-                       }
-                       list = list->next;
-               }
-       }
+       /* When the ref did not exist and we are creating it,
+        * make sure there is no existing ref that is packed
+        * whose name begins with our refname, nor a ref whose
+        * name is a proper prefix of our refname.
+        */
+       if (is_null_sha1(lock->old_sha1) &&
+            !is_refname_available(ref, NULL, get_packed_refs(), 0))
+               goto error_return;
 
        lock->lk = xcalloc(1, sizeof(struct lock_file));
 
        lock->ref_name = xstrdup(ref);
-       lock->log_file = xstrdup(git_path("logs/%s", ref));
+       lock->orig_ref_name = xstrdup(orig_ref);
        ref_file = git_path("%s", ref);
        lock->force_write = lstat(ref_file, &st) && errno == ENOENT;
 
@@ -624,6 +663,8 @@ struct ref_lock *lock_ref_sha1(const char *ref, const unsigned char *old_sha1)
 
 struct ref_lock *lock_any_ref_for_update(const char *ref, const unsigned char *old_sha1)
 {
+       if (check_ref_format(ref) == -1)
+               return NULL;
        return lock_ref_sha1_basic(ref, old_sha1, NULL);
 }
 
@@ -644,7 +685,6 @@ static int repack_without_ref(const char *refname)
        }
        if (!found)
                return 0;
-       memset(&packlock, 0, sizeof(packlock));
        fd = hold_lock_file_for_update(&packlock, git_path("packed-refs"), 0);
        if (fd < 0)
                return error("cannot delete '%s' from packed refs", refname);
@@ -691,15 +731,141 @@ int delete_ref(const char *refname, unsigned char *sha1)
         */
        ret |= repack_without_ref(refname);
 
-       err = unlink(lock->log_file);
+       err = unlink(git_path("logs/%s", lock->ref_name));
        if (err && errno != ENOENT)
                fprintf(stderr, "warning: unlink(%s) failed: %s",
-                       lock->log_file, strerror(errno));
+                       git_path("logs/%s", lock->ref_name), strerror(errno));
        invalidate_cached_refs();
        unlock_ref(lock);
        return ret;
 }
 
+int rename_ref(const char *oldref, const char *newref, const char *logmsg)
+{
+       static const char renamed_ref[] = "RENAMED-REF";
+       unsigned char sha1[20], orig_sha1[20];
+       int flag = 0, logmoved = 0;
+       struct ref_lock *lock;
+       struct stat loginfo;
+       int log = !lstat(git_path("logs/%s", oldref), &loginfo);
+
+       if (S_ISLNK(loginfo.st_mode))
+               return error("reflog for %s is a symlink", oldref);
+
+       if (!resolve_ref(oldref, orig_sha1, 1, &flag))
+               return error("refname %s not found", oldref);
+
+       if (!is_refname_available(newref, oldref, get_packed_refs(), 0))
+               return 1;
+
+       if (!is_refname_available(newref, oldref, get_loose_refs(), 0))
+               return 1;
+
+       lock = lock_ref_sha1_basic(renamed_ref, NULL, NULL);
+       if (!lock)
+               return error("unable to lock %s", renamed_ref);
+       lock->force_write = 1;
+       if (write_ref_sha1(lock, orig_sha1, logmsg))
+               return error("unable to save current sha1 in %s", renamed_ref);
+
+       if (log && rename(git_path("logs/%s", oldref), git_path("tmp-renamed-log")))
+               return error("unable to move logfile logs/%s to tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+
+       if (delete_ref(oldref, orig_sha1)) {
+               error("unable to delete old %s", oldref);
+               goto rollback;
+       }
+
+       if (resolve_ref(newref, sha1, 1, &flag) && delete_ref(newref, sha1)) {
+               if (errno==EISDIR) {
+                       if (remove_empty_directories(git_path("%s", newref))) {
+                               error("Directory not empty: %s", newref);
+                               goto rollback;
+                       }
+               } else {
+                       error("unable to delete existing %s", newref);
+                       goto rollback;
+               }
+       }
+
+       if (log && safe_create_leading_directories(git_path("logs/%s", newref))) {
+               error("unable to create directory for %s", newref);
+               goto rollback;
+       }
+
+ retry:
+       if (log && rename(git_path("tmp-renamed-log"), git_path("logs/%s", newref))) {
+               if (errno==EISDIR || errno==ENOTDIR) {
+                       /*
+                        * rename(a, b) when b is an existing
+                        * directory ought to result in ISDIR, but
+                        * Solaris 5.8 gives ENOTDIR.  Sheesh.
+                        */
+                       if (remove_empty_directories(git_path("logs/%s", newref))) {
+                               error("Directory not empty: logs/%s", newref);
+                               goto rollback;
+                       }
+                       goto retry;
+               } else {
+                       error("unable to move logfile tmp-renamed-log to logs/%s: %s",
+                               newref, strerror(errno));
+                       goto rollback;
+               }
+       }
+       logmoved = log;
+
+       lock = lock_ref_sha1_basic(newref, NULL, NULL);
+       if (!lock) {
+               error("unable to lock %s for update", newref);
+               goto rollback;
+       }
+
+       lock->force_write = 1;
+       hashcpy(lock->old_sha1, orig_sha1);
+       if (write_ref_sha1(lock, orig_sha1, logmsg)) {
+               error("unable to write current sha1 into %s", newref);
+               goto rollback;
+       }
+
+       if (!strncmp(oldref, "refs/heads/", 11) &&
+                       !strncmp(newref, "refs/heads/", 11)) {
+               char oldsection[1024], newsection[1024];
+
+               snprintf(oldsection, 1024, "branch.%s", oldref + 11);
+               snprintf(newsection, 1024, "branch.%s", newref + 11);
+               if (git_config_rename_section(oldsection, newsection) < 0)
+                       return 1;
+       }
+
+       return 0;
+
+ rollback:
+       lock = lock_ref_sha1_basic(oldref, NULL, NULL);
+       if (!lock) {
+               error("unable to lock %s for rollback", oldref);
+               goto rollbacklog;
+       }
+
+       lock->force_write = 1;
+       flag = log_all_ref_updates;
+       log_all_ref_updates = 0;
+       if (write_ref_sha1(lock, orig_sha1, NULL))
+               error("unable to write current sha1 into %s", oldref);
+       log_all_ref_updates = flag;
+
+ rollbacklog:
+       if (logmoved && rename(git_path("logs/%s", newref), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from %s: %s",
+                       oldref, newref, strerror(errno));
+       if (!logmoved && log &&
+           rename(git_path("tmp-renamed-log"), git_path("logs/%s", oldref)))
+               error("unable to restore logfile %s from tmp-renamed-log: %s",
+                       oldref, strerror(errno));
+
+       return 1;
+}
+
 void unlock_ref(struct ref_lock *lock)
 {
        if (lock->lock_fd >= 0) {
@@ -709,67 +875,81 @@ void unlock_ref(struct ref_lock *lock)
                        rollback_lock_file(lock->lk);
        }
        free(lock->ref_name);
-       free(lock->log_file);
+       free(lock->orig_ref_name);
        free(lock);
 }
 
-static int log_ref_write(struct ref_lock *lock,
-       const unsigned char *sha1, const char *msg)
+static int log_ref_write(const char *ref_name, const unsigned char *old_sha1,
+                        const unsigned char *new_sha1, const char *msg)
 {
        int logfd, written, oflags = O_APPEND | O_WRONLY;
        unsigned maxlen, len;
-       char *logrec;
+       int msglen;
+       char *log_file, *logrec;
        const char *committer;
 
+       if (log_all_ref_updates < 0)
+               log_all_ref_updates = !is_bare_repository();
+
+       log_file = git_path("logs/%s", ref_name);
+
        if (log_all_ref_updates &&
-           !strncmp(lock->ref_name, "refs/heads/", 11)) {
-               if (safe_create_leading_directories(lock->log_file) < 0)
+           (!strncmp(ref_name, "refs/heads/", 11) ||
+            !strncmp(ref_name, "refs/remotes/", 13) ||
+            !strcmp(ref_name, "HEAD"))) {
+               if (safe_create_leading_directories(log_file) < 0)
                        return error("unable to create directory for %s",
-                               lock->log_file);
+                                    log_file);
                oflags |= O_CREAT;
        }
 
-       logfd = open(lock->log_file, oflags, 0666);
+       logfd = open(log_file, oflags, 0666);
        if (logfd < 0) {
                if (!(oflags & O_CREAT) && errno == ENOENT)
                        return 0;
 
                if ((oflags & O_CREAT) && errno == EISDIR) {
-                       if (remove_empty_directories(lock->log_file)) {
+                       if (remove_empty_directories(log_file)) {
                                return error("There are still logs under '%s'",
-                                            lock->log_file);
+                                            log_file);
                        }
-                       logfd = open(lock->log_file, oflags, 0666);
+                       logfd = open(log_file, oflags, 0666);
                }
 
                if (logfd < 0)
                        return error("Unable to append to %s: %s",
-                                    lock->log_file, strerror(errno));
+                                    log_file, strerror(errno));
        }
 
-       committer = git_committer_info(1);
+       msglen = 0;
        if (msg) {
-               maxlen = strlen(committer) + strlen(msg) + 2*40 + 5;
-               logrec = xmalloc(maxlen);
-               len = snprintf(logrec, maxlen, "%s %s %s\t%s\n",
-                       sha1_to_hex(lock->old_sha1),
-                       sha1_to_hex(sha1),
-                       committer,
-                       msg);
-       }
-       else {
-               maxlen = strlen(committer) + 2*40 + 4;
-               logrec = xmalloc(maxlen);
-               len = snprintf(logrec, maxlen, "%s %s %s\n",
-                       sha1_to_hex(lock->old_sha1),
-                       sha1_to_hex(sha1),
-                       committer);
-       }
-       written = len <= maxlen ? write(logfd, logrec, len) : -1;
+               /* 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);
+               }
+       }
+
+       committer = git_committer_info(-1);
+       maxlen = strlen(committer) + msglen + 100;
+       logrec = xmalloc(maxlen);
+       len = sprintf(logrec, "%s %s %s\n",
+                     sha1_to_hex(old_sha1),
+                     sha1_to_hex(new_sha1),
+                     committer);
+       if (msglen)
+               len += sprintf(logrec + len - 1, "\t%.*s\n", msglen, msg) - 1;
+       written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
        free(logrec);
        close(logfd);
        if (written != len)
-               return error("Unable to append to %s", lock->log_file);
+               return error("Unable to append to %s", log_file);
        return 0;
 }
 
@@ -784,15 +964,17 @@ int write_ref_sha1(struct ref_lock *lock,
                unlock_ref(lock);
                return 0;
        }
-       if (write(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
-           write(lock->lock_fd, &term, 1) != 1
+       if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
+           write_in_full(lock->lock_fd, &term, 1) != 1
                || close(lock->lock_fd) < 0) {
                error("Couldn't write %s", lock->lk->filename);
                unlock_ref(lock);
                return -1;
        }
        invalidate_cached_refs();
-       if (log_ref_write(lock, sha1, logmsg) < 0) {
+       if (log_ref_write(lock->ref_name, lock->old_sha1, sha1, logmsg) < 0 ||
+           (strcmp(lock->ref_name, lock->orig_ref_name) &&
+            log_ref_write(lock->orig_ref_name, lock->old_sha1, sha1, logmsg) < 0)) {
                unlock_ref(lock);
                return -1;
        }
@@ -806,14 +988,91 @@ int write_ref_sha1(struct ref_lock *lock,
        return 0;
 }
 
-int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1)
+int create_symref(const char *ref_target, const char *refs_heads_master,
+                 const char *logmsg)
+{
+       const char *lockpath;
+       char ref[1000];
+       int fd, len, written;
+       char *git_HEAD = xstrdup(git_path("%s", ref_target));
+       unsigned char old_sha1[20], new_sha1[20];
+
+       if (logmsg && read_ref(ref_target, old_sha1))
+               hashclr(old_sha1);
+
+       if (safe_create_leading_directories(git_HEAD) < 0)
+               return error("unable to create directory for %s", git_HEAD);
+
+#ifndef NO_SYMLINK_HEAD
+       if (prefer_symlink_refs) {
+               unlink(git_HEAD);
+               if (!symlink(refs_heads_master, git_HEAD))
+                       goto done;
+               fprintf(stderr, "no symlink - falling back to symbolic ref\n");
+       }
+#endif
+
+       len = snprintf(ref, sizeof(ref), "ref: %s\n", refs_heads_master);
+       if (sizeof(ref) <= len) {
+               error("refname too long: %s", refs_heads_master);
+               goto error_free_return;
+       }
+       lockpath = mkpath("%s.lock", git_HEAD);
+       fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
+       if (fd < 0) {
+               error("Unable to open %s for writing", lockpath);
+               goto error_free_return;
+       }
+       written = write_in_full(fd, ref, len);
+       close(fd);
+       if (written != len) {
+               error("Unable to write to %s", lockpath);
+               goto error_unlink_return;
+       }
+       if (rename(lockpath, git_HEAD) < 0) {
+               error("Unable to create %s", git_HEAD);
+               goto error_unlink_return;
+       }
+       if (adjust_shared_perm(git_HEAD)) {
+               error("Unable to fix permissions on %s", lockpath);
+       error_unlink_return:
+               unlink(lockpath);
+       error_free_return:
+               free(git_HEAD);
+               return -1;
+       }
+
+       done:
+       if (logmsg && !read_ref(refs_heads_master, new_sha1))
+               log_ref_write(ref_target, old_sha1, new_sha1, logmsg);
+
+       free(git_HEAD);
+       return 0;
+}
+
+static char *ref_msg(const char *line, const char *endp)
+{
+       const char *ep;
+       char *msg;
+
+       line += 82;
+       for (ep = line; ep < endp && *ep != '\n'; ep++)
+               ;
+       msg = xmalloc(ep - line + 1);
+       memcpy(msg, line, ep - line);
+       msg[ep - line] = 0;
+       return msg;
+}
+
+int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
 {
        const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
        char *tz_c;
-       int logfd, tz;
+       int logfd, tz, reccnt = 0;
        struct stat st;
        unsigned long date;
        unsigned char logged_sha1[20];
+       void *log_mapped;
 
        logfile = git_path("logs/%s", ref);
        logfd = open(logfile, O_RDONLY, 0);
@@ -822,12 +1081,14 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        fstat(logfd, &st);
        if (!st.st_size)
                die("Log %s is empty.", logfile);
-       logdata = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       log_mapped = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, logfd, 0);
+       logdata = log_mapped;
        close(logfd);
 
        lastrec = NULL;
        rec = logend = logdata + st.st_size;
        while (logdata < rec) {
+               reccnt++;
                if (logdata < rec && *(rec-1) == '\n')
                        rec--;
                lastgt = NULL;
@@ -840,13 +1101,21 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                        die("Log %s is corrupt.", logfile);
                date = strtoul(lastgt + 1, &tz_c, 10);
                if (date <= at_time || cnt == 0) {
+                       tz = strtoul(tz_c, NULL, 10);
+                       if (msg)
+                               *msg = ref_msg(rec, logend);
+                       if (cutoff_time)
+                               *cutoff_time = date;
+                       if (cutoff_tz)
+                               *cutoff_tz = tz;
+                       if (cutoff_cnt)
+                               *cutoff_cnt = reccnt - 1;
                        if (lastrec) {
                                if (get_sha1_hex(lastrec, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (get_sha1_hex(rec + 41, sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       tz = strtoul(tz_c, NULL, 10);
                                        fprintf(stderr,
                                                "warning: Log %s has gap after %s.\n",
                                                logfile, show_rfc2822_date(date, tz));
@@ -860,13 +1129,12 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
                                if (get_sha1_hex(rec + 41, logged_sha1))
                                        die("Log %s is corrupt.", logfile);
                                if (hashcmp(logged_sha1, sha1)) {
-                                       tz = strtoul(tz_c, NULL, 10);
                                        fprintf(stderr,
                                                "warning: Log %s unexpectedly ended on %s.\n",
                                                logfile, show_rfc2822_date(date, tz));
                                }
                        }
-                       munmap((void*)logdata, st.st_size);
+                       munmap(log_mapped, st.st_size);
                        return 0;
                }
                lastrec = rec;
@@ -883,8 +1151,112 @@ int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *
        tz = strtoul(tz_c, NULL, 10);
        if (get_sha1_hex(logdata, sha1))
                die("Log %s is corrupt.", logfile);
-       munmap((void*)logdata, st.st_size);
-       fprintf(stderr, "warning: Log %s only goes back to %s.\n",
-               logfile, show_rfc2822_date(date, tz));
-       return 0;
+       if (msg)
+               *msg = ref_msg(logdata, logend);
+       munmap(log_mapped, st.st_size);
+
+       if (cutoff_time)
+               *cutoff_time = date;
+       if (cutoff_tz)
+               *cutoff_tz = tz;
+       if (cutoff_cnt)
+               *cutoff_cnt = reccnt;
+       return 1;
+}
+
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data)
+{
+       const char *logfile;
+       FILE *logfp;
+       char buf[1024];
+       int ret = 0;
+
+       logfile = git_path("logs/%s", ref);
+       logfp = fopen(logfile, "r");
+       if (!logfp)
+               return -1;
+       while (fgets(buf, sizeof(buf), logfp)) {
+               unsigned char osha1[20], nsha1[20];
+               char *email_end, *message;
+               unsigned long timestamp;
+               int len, tz;
+
+               /* old SP new SP name <email> SP time TAB msg LF */
+               len = strlen(buf);
+               if (len < 83 || buf[len-1] != '\n' ||
+                   get_sha1_hex(buf, osha1) || buf[40] != ' ' ||
+                   get_sha1_hex(buf + 41, nsha1) || buf[81] != ' ' ||
+                   !(email_end = strchr(buf + 82, '>')) ||
+                   email_end[1] != ' ' ||
+                   !(timestamp = strtoul(email_end + 2, &message, 10)) ||
+                   !message || message[0] != ' ' ||
+                   (message[1] != '+' && message[1] != '-') ||
+                   !isdigit(message[2]) || !isdigit(message[3]) ||
+                   !isdigit(message[4]) || !isdigit(message[5]))
+                       continue; /* corrupt? */
+               email_end[1] = '\0';
+               tz = strtol(message + 1, NULL, 10);
+               if (message[6] != '\t')
+                       message += 6;
+               else
+                       message += 7;
+               ret = fn(osha1, nsha1, buf+82, timestamp, tz, message, cb_data);
+               if (ret)
+                       break;
+       }
+       fclose(logfp);
+       return ret;
+}
+
+static int do_for_each_reflog(const char *base, each_ref_fn fn, void *cb_data)
+{
+       DIR *dir = opendir(git_path("logs/%s", base));
+       int retval = 0;
+
+       if (dir) {
+               struct dirent *de;
+               int baselen = strlen(base);
+               char *log = xmalloc(baselen + 257);
+
+               memcpy(log, base, baselen);
+               if (baselen && base[baselen-1] != '/')
+                       log[baselen++] = '/';
+
+               while ((de = readdir(dir)) != NULL) {
+                       struct stat st;
+                       int namelen;
+
+                       if (de->d_name[0] == '.')
+                               continue;
+                       namelen = strlen(de->d_name);
+                       if (namelen > 255)
+                               continue;
+                       if (has_extension(de->d_name, ".lock"))
+                               continue;
+                       memcpy(log + baselen, de->d_name, namelen+1);
+                       if (stat(git_path("logs/%s", log), &st) < 0)
+                               continue;
+                       if (S_ISDIR(st.st_mode)) {
+                               retval = do_for_each_reflog(log, fn, cb_data);
+                       } else {
+                               unsigned char sha1[20];
+                               if (!resolve_ref(log, sha1, 0, NULL))
+                                       retval = error("bad ref for %s", log);
+                               else
+                                       retval = fn(log, sha1, 0, cb_data);
+                       }
+                       if (retval)
+                               break;
+               }
+               free(log);
+               closedir(dir);
+       }
+       else if (*base)
+               return errno;
+       return retval;
+}
+
+int for_each_reflog(each_ref_fn fn, void *cb_data)
+{
+       return do_for_each_reflog("", fn, cb_data);
 }
diff --git a/refs.h b/refs.h
index a57d43726a0fee5686e2c0d505f250c6b4adc150..acedffc0e412e1de6137d665a7c6b32f58b1c20b 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -3,19 +3,20 @@
 
 struct ref_lock {
        char *ref_name;
-       char *log_file;
+       char *orig_ref_name;
        struct lock_file *lk;
        unsigned char old_sha1[20];
        int lock_fd;
        int force_write;
 };
 
+#define REF_ISSYMREF 01
+#define REF_ISPACKED 02
+
 /*
  * Calls the specified function for each ref file until it returns nonzero,
  * and returns the value
  */
-#define REF_ISSYMREF 01
-#define REF_ISPACKED 02
 typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
 extern int head_ref(each_ref_fn, void *);
 extern int for_each_ref(each_ref_fn, void *);
@@ -23,6 +24,8 @@ extern int for_each_tag_ref(each_ref_fn, void *);
 extern int for_each_branch_ref(each_ref_fn, void *);
 extern int for_each_remote_ref(each_ref_fn, void *);
 
+extern int peel_ref(const char *, unsigned char *);
+
 /** Reads the refs file specified into sha1 **/
 extern int get_ref_sha1(const char *ref, unsigned char *sha1);
 
@@ -39,9 +42,22 @@ extern void unlock_ref(struct ref_lock *lock);
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
 /** Reads log for the value of ref during at_time. **/
-extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1);
+extern int read_ref_at(const char *ref, unsigned long at_time, int cnt, unsigned char *sha1, char **msg, unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
+
+/* iterate over reflog entries */
+typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
+int for_each_reflog_ent(const char *ref, each_reflog_ent_fn fn, void *cb_data);
+
+/*
+ * Calls the specified function for each reflog file until it returns nonzero,
+ * and returns the value
+ */
+extern int for_each_reflog(each_ref_fn, void *);
 
 /** Returns 0 if target has the right format for a ref. **/
 extern int check_ref_format(const char *target);
 
+/** rename ref, return 0 on success **/
+extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
+
 #endif /* REFS_H */
index 993bb668a205a9fb3e2f5ddd2f378f10ac7905b4..15bdaf6095ccbe690c664f80a5b25e354d068d61 100644 (file)
@@ -6,8 +6,8 @@
 #include "diff.h"
 #include "refs.h"
 #include "revision.h"
-#include <regex.h>
 #include "grep.h"
+#include "reflog-walk.h"
 
 static char *path_name(struct name_path *path, const char *name)
 {
@@ -117,6 +117,9 @@ void mark_parents_uninteresting(struct commit *commit)
 void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
 {
        add_object_array(obj, name, &revs->pending);
+       if (revs->reflog_info && obj->type == OBJ_COMMIT)
+               add_reflog_for_walk(revs->reflog_info,
+                               (struct commit *)obj, name);
 }
 
 static struct object *get_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
@@ -344,6 +347,7 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
 static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
 {
        struct commit_list *parent = commit->parents;
+       unsigned left_flag;
 
        if (commit->object.flags & ADDED)
                return;
@@ -388,6 +392,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
        if (revs->no_walk)
                return;
 
+       left_flag = (commit->object.flags & SYMMETRIC_LEFT);
        parent = commit->parents;
        while (parent) {
                struct commit *p = parent->item;
@@ -395,6 +400,7 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st
                parent = parent->next;
 
                parse_commit(p);
+               p->object.flags |= left_flag;
                if (p->object.flags & SEEN)
                        continue;
                p->object.flags |= SEEN;
@@ -462,21 +468,71 @@ static void limit_list(struct rev_info *revs)
        revs->commits = newlist;
 }
 
-static int all_flags;
-static struct rev_info *all_revs;
+struct all_refs_cb {
+       int all_flags;
+       int warned_bad_reflog;
+       struct rev_info *all_revs;
+       const char *name_for_errormsg;
+};
 
 static int handle_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
-       struct object *object = get_reference(all_revs, path, sha1, all_flags);
-       add_pending_object(all_revs, object, "");
+       struct all_refs_cb *cb = cb_data;
+       struct object *object = get_reference(cb->all_revs, path, sha1,
+                                             cb->all_flags);
+       add_pending_object(cb->all_revs, object, "");
        return 0;
 }
 
 static void handle_all(struct rev_info *revs, unsigned flags)
 {
-       all_revs = revs;
-       all_flags = flags;
-       for_each_ref(handle_one_ref, NULL);
+       struct all_refs_cb cb;
+       cb.all_revs = revs;
+       cb.all_flags = flags;
+       for_each_ref(handle_one_ref, &cb);
+}
+
+static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
+{
+       struct all_refs_cb *cb = cb_data;
+       if (!is_null_sha1(sha1)) {
+               struct object *o = parse_object(sha1);
+               if (o) {
+                       o->flags |= cb->all_flags;
+                       add_pending_object(cb->all_revs, o, "");
+               }
+               else if (!cb->warned_bad_reflog) {
+                       warn("reflog of '%s' references pruned commits",
+                               cb->name_for_errormsg);
+                       cb->warned_bad_reflog = 1;
+               }
+       }
+}
+
+static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+               const char *email, unsigned long timestamp, int tz,
+               const char *message, void *cb_data)
+{
+       handle_one_reflog_commit(osha1, cb_data);
+       handle_one_reflog_commit(nsha1, cb_data);
+       return 0;
+}
+
+static int handle_one_reflog(const char *path, const unsigned char *sha1, int flag, void *cb_data)
+{
+       struct all_refs_cb *cb = cb_data;
+       cb->warned_bad_reflog = 0;
+       cb->name_for_errormsg = path;
+       for_each_reflog_ent(path, handle_one_reflog_ent, cb_data);
+       return 0;
+}
+
+static void handle_reflog(struct rev_info *revs, unsigned flags)
+{
+       struct all_refs_cb cb;
+       cb.all_revs = revs;
+       cb.all_flags = flags;
+       for_each_reflog(handle_one_reflog, &cb);
 }
 
 static int add_parents_only(struct rev_info *revs, const char *arg, int flags)
@@ -524,6 +580,7 @@ void init_revisions(struct rev_info *revs, const char *prefix)
        revs->prefix = prefix;
        revs->max_age = -1;
        revs->min_age = -1;
+       revs->skip_count = -1;
        revs->max_count = -1;
 
        revs->prune_fn = NULL;
@@ -640,7 +697,7 @@ int handle_revision_arg(const char *arg, struct rev_info *revs,
                                add_pending_commit_list(revs, exclude,
                                                        flags_exclude);
                                free_commit_list(exclude);
-                               a->object.flags |= flags;
+                               a->object.flags |= flags | SYMMETRIC_LEFT;
                        } else
                                a->object.flags |= flags_exclude;
                        b->object.flags |= flags;
@@ -760,6 +817,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->max_count = atoi(arg + 12);
                                continue;
                        }
+                       if (!strncmp(arg, "--skip=", 7)) {
+                               revs->skip_count = atoi(arg + 7);
+                               continue;
+                       }
                        /* accept -<digit>, like traditional "head" */
                        if ((*arg == '-') && isdigit(arg[1])) {
                                revs->max_count = atoi(arg + 1);
@@ -803,6 +864,15 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                handle_all(revs, flags);
                                continue;
                        }
+                       if (!strcmp(arg, "--reflog")) {
+                               handle_reflog(revs, flags);
+                               continue;
+                       }
+                       if (!strcmp(arg, "-g") ||
+                                       !strcmp(arg, "--walk-reflogs")) {
+                               init_reflog_walk(&revs->reflog_info);
+                               continue;
+                       }
                        if (!strcmp(arg, "--not")) {
                                flags ^= UNINTERESTING;
                                continue;
@@ -850,6 +920,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                revs->boundary = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--left-right")) {
+                               revs->left_right = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--objects")) {
                                revs->tag_objects = 1;
                                revs->tree_objects = 1;
@@ -976,6 +1050,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                                all_match = 1;
                                continue;
                        }
+                       if (!strncmp(arg, "--encoding=", 11)) {
+                               arg += 11;
+                               if (strcmp(arg, "none"))
+                                       git_log_output_encoding = strdup(arg);
+                               else
+                                       git_log_output_encoding = "";
+                               continue;
+                       }
 
                        opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
                        if (opts > 0) {
@@ -1048,21 +1130,23 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 void prepare_revision_walk(struct rev_info *revs)
 {
        int nr = revs->pending.nr;
-       struct object_array_entry *list = revs->pending.objects;
+       struct object_array_entry *e, *list;
 
+       e = list = revs->pending.objects;
        revs->pending.nr = 0;
        revs->pending.alloc = 0;
        revs->pending.objects = NULL;
        while (--nr >= 0) {
-               struct commit *commit = handle_commit(revs, list->item, list->name);
+               struct commit *commit = handle_commit(revs, e->item, e->name);
                if (commit) {
                        if (!(commit->object.flags & SEEN)) {
                                commit->object.flags |= SEEN;
                                insert_by_date(commit, &revs->commits);
                        }
                }
-               list++;
+               e++;
        }
+       free(list);
 
        if (revs->no_walk)
                return;
@@ -1123,23 +1207,11 @@ static int commit_match(struct commit *commit, struct rev_info *opt)
                           commit->buffer, strlen(commit->buffer));
 }
 
-struct commit *get_revision(struct rev_info *revs)
+static struct commit *get_revision_1(struct rev_info *revs)
 {
-       struct commit_list *list = revs->commits;
-
-       if (!list)
+       if (!revs->commits)
                return NULL;
 
-       /* Check the max_count ... */
-       switch (revs->max_count) {
-       case -1:
-               break;
-       case 0:
-               return NULL;
-       default:
-               revs->max_count--;
-       }
-
        do {
                struct commit_list *entry = revs->commits;
                struct commit *commit = entry->item;
@@ -1147,6 +1219,9 @@ struct commit *get_revision(struct rev_info *revs)
                revs->commits = entry->next;
                free(entry);
 
+               if (revs->reflog_info)
+                       fake_reflog_parent(revs->reflog_info, commit);
+
                /*
                 * If we haven't done the list limiting, we need to look at
                 * the parents here. We also need to do the date-based limiting
@@ -1206,3 +1281,28 @@ struct commit *get_revision(struct rev_info *revs)
        } while (revs->commits);
        return NULL;
 }
+
+struct commit *get_revision(struct rev_info *revs)
+{
+       struct commit *c = NULL;
+
+       if (0 < revs->skip_count) {
+               while ((c = get_revision_1(revs)) != NULL) {
+                       if (revs->skip_count-- <= 0)
+                               break;
+               }
+       }
+
+       /* Check the max_count ... */
+       switch (revs->max_count) {
+       case -1:
+               break;
+       case 0:
+               return NULL;
+       default:
+               revs->max_count--;
+       }
+       if (c)
+               return c;
+       return get_revision_1(revs);
+}
index 3adab9590a14e25c2659a1933db4af456c263a5b..d93481f68f53a657fe4b174e4a12777efe6a74bb 100644 (file)
@@ -9,6 +9,7 @@
 #define BOUNDARY       (1u<<5)
 #define BOUNDARY_SHOW  (1u<<6)
 #define ADDED          (1u<<7) /* Parents already parsed and added? */
+#define SYMMETRIC_LEFT (1u<<8)
 
 struct rev_info;
 struct log_info;
@@ -40,6 +41,7 @@ struct rev_info {
                        limited:1,
                        unpacked:1, /* see also ignore_packed below */
                        boundary:1,
+                       left_right:1,
                        parents:1;
 
        /* Diff flags */
@@ -70,11 +72,13 @@ struct rev_info {
        const char      *ref_message_id;
        const char      *add_signoff;
        const char      *extra_headers;
+       const char      *log_reencode;
 
        /* Filter by commit log message */
        struct grep_opt *grep_filter;
 
        /* special limits */
+       int skip_count;
        int max_count;
        unsigned long max_age;
        unsigned long min_age;
@@ -85,6 +89,8 @@ struct rev_info {
 
        topo_sort_set_fn_t topo_setter;
        topo_sort_get_fn_t topo_getter;
+
+       struct reflog_walk_info *reflog_info;
 };
 
 #define REV_TREE_SAME          0
diff --git a/rsh.c b/rsh.c
index f34409e1bc5fcf3b6295021043339793b25a8913..5754a230e2c23ce3fea255fccd8726af76c13317 100644 (file)
--- a/rsh.c
+++ b/rsh.c
@@ -1,10 +1,6 @@
-#include <string.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
+#include "cache.h"
 #include "rsh.h"
 #include "quote.h"
-#include "cache.h"
 
 #define COMMAND_SIZE 4096
 
index 61908682b9b251ac49ae655522a3143476a888ea..cfbad74d145145944352c568064cc2f8c0d4c5cb 100644 (file)
@@ -1,21 +1,21 @@
 #include "cache.h"
 #include "run-command.h"
-#include <sys/wait.h>
 #include "exec_cmd.h"
 
-int run_command_v_opt(int argc, const char **argv, int flags)
+int run_command_v_opt(const char **argv, int flags)
 {
        pid_t pid = fork();
 
        if (pid < 0)
                return -ERR_RUN_COMMAND_FORK;
        if (!pid) {
-               if (flags & RUN_COMMAND_NO_STDIO) {
+               if (flags & RUN_COMMAND_NO_STDIN) {
                        int fd = open("/dev/null", O_RDWR);
                        dup2(fd, 0);
-                       dup2(fd, 1);
                        close(fd);
                }
+               if (flags & RUN_COMMAND_STDOUT_TO_STDERR)
+                       dup2(2, 1);
                if (flags & RUN_GIT_CMD) {
                        execv_git_cmd(argv);
                } else {
@@ -47,19 +47,17 @@ int run_command_v_opt(int argc, const char **argv, int flags)
        }
 }
 
-int run_command_v(int argc, const char **argv)
+int run_command_v(const char **argv)
 {
-       return run_command_v_opt(argc, argv, 0);
+       return run_command_v_opt(argv, 0);
 }
 
-int run_command(const char *cmd, ...)
+static int run_command_va_opt(int opt, const char *cmd, va_list param)
 {
        int argc;
        const char *argv[MAX_RUN_COMMAND_ARGS];
        const char *arg;
-       va_list param;
 
-       va_start(param, cmd);
        argv[0] = (char*) cmd;
        argc = 1;
        while (argc < MAX_RUN_COMMAND_ARGS) {
@@ -67,8 +65,29 @@ int run_command(const char *cmd, ...)
                if (!arg)
                        break;
        }
-       va_end(param);
        if (MAX_RUN_COMMAND_ARGS <= argc)
                return error("too many args to run %s", cmd);
-       return run_command_v_opt(argc, argv, 0);
+       return run_command_v_opt(argv, opt);
+}
+
+int run_command_opt(int opt, const char *cmd, ...)
+{
+       va_list params;
+       int r;
+
+       va_start(params, cmd);
+       r = run_command_va_opt(opt, cmd, params);
+       va_end(params);
+       return r;
+}
+
+int run_command(const char *cmd, ...)
+{
+       va_list params;
+       int r;
+
+       va_start(params, cmd);
+       r = run_command_va_opt(0, cmd, params);
+       va_end(params);
+       return r;
 }
index 70b477a7483ce3a4997b20ee02223eeb483bde6c..59c4476ced789441eea3f68bfc7377e7e15e9b14 100644 (file)
@@ -11,10 +11,12 @@ enum {
        ERR_RUN_COMMAND_WAITPID_NOEXIT,
 };
 
-#define RUN_COMMAND_NO_STDIO 1
+#define RUN_COMMAND_NO_STDIN 1
 #define RUN_GIT_CMD         2  /*If this is to be git sub-command */
-int run_command_v_opt(int argc, const char **argv, int opt);
-int run_command_v(int argc, const char **argv);
+#define RUN_COMMAND_STDOUT_TO_STDERR 4
+int run_command_v_opt(const char **argv, int opt);
+int run_command_v(const char **argv);
+int run_command_opt(int opt, const char *cmd, ...);
 int run_command(const char *cmd, ...);
 
 #endif
index 447666665bc43090849a66681edec663fe75000c..33e69dbe1803a8667f383c1f0517867c873dafc4 100644 (file)
 #include "exec_cmd.h"
 
 static const char send_pack_usage[] =
-"git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n"
-"  --all and explicit <head> specification are mutually exclusive.";
-static const char *exec = "git-receive-pack";
+"git-send-pack [--all] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
+"  --all and explicit <ref> specification are mutually exclusive.";
+static const char *receivepack = "git-receive-pack";
 static int verbose;
 static int send_all;
 static int force_update;
 static int use_thin_pack;
 
-static int is_zero_sha1(const unsigned char *sha1)
-{
-       int i;
-
-       for (i = 0; i < 20; i++) {
-               if (*sha1++)
-                       return 0;
-       }
-       return 1;
-}
-
-static void exec_pack_objects(void)
-{
-       static const char *args[] = {
-               "pack-objects",
-               "--all-progress",
-               "--stdout",
-               NULL
-       };
-       execv_git_cmd(args);
-       die("git-pack-objects exec failed (%s)", strerror(errno));
-}
-
-static void exec_rev_list(struct ref *refs)
-{
-       static const char *args[4];
-       int i = 0;
-
-       args[i++] = "rev-list"; /* 0 */
-       if (use_thin_pack)      /* 1 */
-               args[i++] = "--objects-edge";
-       else
-               args[i++] = "--objects";
-
-       args[i++] = "--stdin";
-
-       args[i] = NULL;
-       execv_git_cmd(args);
-       die("git-rev-list exec failed (%s)", strerror(errno));
-}
-
-/*
- * Run "rev-list --stdin | pack-objects" pipe.
- */
-static void rev_list(int fd, struct ref *refs)
-{
-       int pipe_fd[2];
-       pid_t pack_objects_pid;
-
-       if (pipe(pipe_fd) < 0)
-               die("rev-list setup: pipe failed");
-       pack_objects_pid = fork();
-       if (!pack_objects_pid) {
-               /* The child becomes pack-objects; reads from pipe
-                * and writes to the original fd
-                */
-               dup2(pipe_fd[0], 0);
-               dup2(fd, 1);
-               close(pipe_fd[0]);
-               close(pipe_fd[1]);
-               close(fd);
-               exec_pack_objects();
-               die("pack-objects setup failed");
-       }
-       if (pack_objects_pid < 0)
-               die("pack-objects fork failed");
-
-       /* We become rev-list --stdin; output goes to pipe. */
-       dup2(pipe_fd[1], 1);
-       close(pipe_fd[0]);
-       close(pipe_fd[1]);
-       close(fd);
-       exec_rev_list(refs);
-}
-
 /*
- * Create "rev-list --stdin | pack-objects" pipe and feed
- * the refs into the pipeline.
+ * Make a pack stream and spit it out into file descriptor fd
  */
-static void rev_list_generate(int fd, struct ref *refs)
+static int pack_objects(int fd, struct ref *refs)
 {
        int pipe_fd[2];
-       pid_t rev_list_generate_pid;
+       pid_t pid;
 
        if (pipe(pipe_fd) < 0)
-               die("rev-list-generate setup: pipe failed");
-       rev_list_generate_pid = fork();
-       if (!rev_list_generate_pid) {
-               /* The child becomes the "rev-list | pack-objects"
-                * pipeline.  It takes input from us, and its output
-                * goes to fd.
+               return error("send-pack: pipe failed");
+       pid = fork();
+       if (pid < 0)
+               return error("send-pack: unable to fork git-pack-objects");
+       if (!pid) {
+               /*
+                * The child becomes pack-objects --revs; we feed
+                * the revision parameters to it via its stdin and
+                * let its stdout go back to the other end.
                 */
+               static const char *args[] = {
+                       "pack-objects",
+                       "--all-progress",
+                       "--revs",
+                       "--stdout",
+                       NULL,
+                       NULL,
+               };
+               if (use_thin_pack)
+                       args[4] = "--thin";
                dup2(pipe_fd[0], 0);
                dup2(fd, 1);
                close(pipe_fd[0]);
                close(pipe_fd[1]);
                close(fd);
-               rev_list(fd, refs);
-               die("rev-list setup failed");
+               execv_git_cmd(args);
+               die("git-pack-objects exec failed (%s)", strerror(errno));
        }
-       if (rev_list_generate_pid < 0)
-               die("rev-list-generate fork failed");
 
-       /* We feed the rev parameters to them.  We do not write into
-        * fd nor read from the pipe.
+       /*
+        * We feed the pack-objects we just spawned with revision
+        * parameters by writing to the pipe.
         */
        close(pipe_fd[0]);
        close(fd);
+
        while (refs) {
                char buf[42];
 
@@ -130,38 +67,38 @@ static void rev_list_generate(int fd, struct ref *refs)
                        memcpy(buf + 1, sha1_to_hex(refs->old_sha1), 40);
                        buf[0] = '^';
                        buf[41] = '\n';
-                       write(pipe_fd[1], buf, 42);
+                       if (!write_or_whine(pipe_fd[1], buf, 42,
+                                               "send-pack: send refs"))
+                               break;
                }
                if (!is_null_sha1(refs->new_sha1)) {
                        memcpy(buf, sha1_to_hex(refs->new_sha1), 40);
                        buf[40] = '\n';
-                       write(pipe_fd[1], buf, 41);
+                       if (!write_or_whine(pipe_fd[1], buf, 41,
+                                               "send-pack: send refs"))
+                               break;
                }
                refs = refs->next;
        }
        close(pipe_fd[1]);
-       // waitpid(rev_list_generate_pid);
-       exit(0);
-}
 
-/*
- * Make a pack stream and spit it out into file descriptor fd
- */
-static void pack_objects(int fd, struct ref *refs)
-{
-       pid_t rev_list_pid;
+       for (;;) {
+               int status, code;
+               pid_t waiting = waitpid(pid, &status, 0);
 
-       rev_list_pid = fork();
-       if (!rev_list_pid) {
-               rev_list_generate(fd, refs);
-               die("rev-list setup failed");
+               if (waiting < 0) {
+                       if (errno == EINTR)
+                               continue;
+                       return error("waitpid failed (%s)", strerror(errno));
+               }
+               if ((waiting != pid) || WIFSIGNALED(status) ||
+                   !WIFEXITED(status))
+                       return error("pack-objects died with strange error");
+               code = WEXITSTATUS(status);
+               if (code)
+                       return -code;
+               return 0;
        }
-       if (rev_list_pid < 0)
-               die("rev-list fork failed");
-       /*
-        * We don't wait for the rev-list pipeline in the parent:
-        * we end up waiting for the other end instead
-        */
 }
 
 static void unmark_and_free(struct commit_list *list, unsigned int mark)
@@ -271,6 +208,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        int new_refs;
        int ret = 0;
        int ask_for_status_report = 0;
+       int allow_deleting_refs = 0;
        int expect_status_report = 0;
 
        /* No funny business with the matcher */
@@ -280,6 +218,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        /* Does the other end support the reporting? */
        if (server_supports("report-status"))
                ask_for_status_report = 1;
+       if (server_supports("delete-refs"))
+               allow_deleting_refs = 1;
 
        /* match them up */
        if (!remote_tail)
@@ -299,9 +239,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;
+
                if (!ref->peer_ref)
                        continue;
-               if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
+
+               delete_ref = is_null_sha1(ref->peer_ref->new_sha1);
+               if (delete_ref && !allow_deleting_refs) {
+                       error("remote does not support deleting refs");
+                       ret = -2;
+                       continue;
+               }
+               if (!delete_ref &&
+                   !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) {
                        if (verbose)
                                fprintf(stderr, "'%s': up-to-date\n", ref->name);
                        continue;
@@ -321,10 +271,14 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                 *
                 * (3) if both new and old are commit-ish, and new is a
                 *     descendant of old, it is OK.
+                *
+                * (4) regardless of all of the above, removing :B is
+                *     always allowed.
                 */
 
                if (!force_update &&
-                   !is_zero_sha1(ref->old_sha1) &&
+                   !delete_ref &&
+                   !is_null_sha1(ref->old_sha1) &&
                    !ref->force) {
                        if (!has_sha1_file(ref->old_sha1) ||
                            !ref_newer(ref->peer_ref->new_sha1,
@@ -347,12 +301,8 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
                        }
                }
                hashcpy(ref->new_sha1, ref->peer_ref->new_sha1);
-               if (is_zero_sha1(ref->new_sha1)) {
-                       error("cannot happen anymore");
-                       ret = -3;
-                       continue;
-               }
-               new_refs++;
+               if (!delete_ref)
+                       new_refs++;
                strcpy(old_hex, sha1_to_hex(ref->old_sha1));
                new_hex = sha1_to_hex(ref->new_sha1);
 
@@ -366,15 +316,21 @@ 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);
-               fprintf(stderr, "updating '%s'", ref->name);
-               if (strcmp(ref->name, ref->peer_ref->name))
-                       fprintf(stderr, " using '%s'", ref->peer_ref->name);
-               fprintf(stderr, "\n  from %s\n  to   %s\n", old_hex, new_hex);
+               if (delete_ref)
+                       fprintf(stderr, "deleting '%s'\n", ref->name);
+               else {
+                       fprintf(stderr, "updating '%s'", ref->name);
+                       if (strcmp(ref->name, ref->peer_ref->name))
+                               fprintf(stderr, " using '%s'",
+                                       ref->peer_ref->name);
+                       fprintf(stderr, "\n  from %s\n  to   %s\n",
+                               old_hex, new_hex);
+               }
        }
 
        packet_flush(out);
        if (new_refs)
-               pack_objects(out, remote_refs);
+               ret = pack_objects(out, remote_refs);
        close(out);
 
        if (expect_status_report) {
@@ -387,6 +343,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec)
        return ret;
 }
 
+static void verify_remote_names(int nr_heads, char **heads)
+{
+       int i;
+
+       for (i = 0; i < nr_heads; i++) {
+               const char *remote = strchr(heads[i], ':');
+
+               remote = remote ? (remote + 1) : heads[i];
+               switch (check_ref_format(remote)) {
+               case 0: /* ok */
+               case -2: /* ok but a single level -- that is fine for
+                         * a match pattern.
+                         */
+                       continue;
+               }
+               die("remote part of refspec is not a valid name in %s",
+                   heads[i]);
+       }
+}
 
 int main(int argc, char **argv)
 {
@@ -404,8 +379,12 @@ int main(int argc, char **argv)
                char *arg = *argv;
 
                if (*arg == '-') {
+                       if (!strncmp(arg, "--receive-pack=", 15)) {
+                               receivepack = arg + 15;
+                               continue;
+                       }
                        if (!strncmp(arg, "--exec=", 7)) {
-                               exec = arg + 7;
+                               receivepack = arg + 7;
                                continue;
                        }
                        if (!strcmp(arg, "--all")) {
@@ -438,7 +417,9 @@ int main(int argc, char **argv)
                usage(send_pack_usage);
        if (heads && send_all)
                usage(send_pack_usage);
-       pid = git_connect(fd, dest, exec);
+       verify_remote_names(nr_heads, heads);
+
+       pid = git_connect(fd, dest, receivepack);
        if (pid < 0)
                return 1;
        ret = send_pack(fd[0], fd[1], nr_heads, heads);
index 6cd38be3291e95e75a7d2348e825d49c8f7ad2af..f9be5a7f60c1cc5208e7c91367ecb28014106f18 100644 (file)
@@ -10,6 +10,8 @@ static FILE *info_ref_fp;
 static int add_info_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
 {
        struct object *o = parse_object(sha1);
+       if (!o)
+               return -1;
 
        fprintf(info_ref_fp, "%s        %s\n", sha1_to_hex(sha1), path);
        if (o->type == OBJ_TAG) {
diff --git a/setup.c b/setup.c
index 2afdba414a073705440f887593a1b5daa1023758..e9d3f5aab63225df7f1b495a19740408d23973a8 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -95,6 +95,8 @@ void verify_non_filename(const char *prefix, const char *arg)
        const char *name;
        struct stat st;
 
+       if (is_inside_git_dir())
+               return;
        if (*arg == '-')
                return; /* flag */
        name = prefix ? prefix_filename(prefix, strlen(prefix), arg) : arg;
@@ -131,28 +133,69 @@ const char **get_pathspec(const char *prefix, const char **pathspec)
 }
 
 /*
- * Test if it looks like we're at the top level git directory.
+ * Test if it looks like we're at a git directory.
  * We want to see:
  *
- *  - either a .git/objects/ directory _or_ the proper
+ *  - either a objects/ directory _or_ the proper
  *    GIT_OBJECT_DIRECTORY environment variable
- *  - a refs/ directory under ".git"
+ *  - a refs/ directory
  *  - either a HEAD symlink or a HEAD file that is formatted as
- *    a proper "ref:".
+ *    a proper "ref:", or a regular file HEAD that has a properly
+ *    formatted sha1 object name.
  */
-static int is_toplevel_directory(void)
+static int is_git_directory(const char *suspect)
 {
-       if (access(".git/refs/", X_OK) ||
-           access(getenv(DB_ENVIRONMENT) ?
-                  getenv(DB_ENVIRONMENT) : ".git/objects/", X_OK) ||
-           validate_symref(".git/HEAD"))
+       char path[PATH_MAX];
+       size_t len = strlen(suspect);
+
+       strcpy(path, suspect);
+       if (getenv(DB_ENVIRONMENT)) {
+               if (access(getenv(DB_ENVIRONMENT), X_OK))
+                       return 0;
+       }
+       else {
+               strcpy(path + len, "/objects");
+               if (access(path, X_OK))
+                       return 0;
+       }
+
+       strcpy(path + len, "/refs");
+       if (access(path, X_OK))
                return 0;
+
+       strcpy(path + len, "/HEAD");
+       if (validate_headref(path))
+               return 0;
+
        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;
+       }
+       return inside_git_dir;
+}
+
 const char *setup_git_directory_gently(int *nongit_ok)
 {
        static char cwd[PATH_MAX+1];
+       const char *gitdirenv;
        int len, offset;
 
        /*
@@ -160,36 +203,17 @@ const char *setup_git_directory_gently(int *nongit_ok)
         * to do any discovery, but we still do repository
         * validation.
         */
-       if (getenv(GIT_DIR_ENVIRONMENT)) {
-               char path[PATH_MAX];
-               int len = strlen(getenv(GIT_DIR_ENVIRONMENT));
-               if (sizeof(path) - 40 < len)
+       gitdirenv = getenv(GIT_DIR_ENVIRONMENT);
+       if (gitdirenv) {
+               if (PATH_MAX - 40 < strlen(gitdirenv))
                        die("'$%s' too big", GIT_DIR_ENVIRONMENT);
-               memcpy(path, getenv(GIT_DIR_ENVIRONMENT), len);
-               
-               strcpy(path + len, "/refs");
-               if (access(path, X_OK))
-                       goto bad_dir_environ;
-               strcpy(path + len, "/HEAD");
-               if (validate_symref(path))
-                       goto bad_dir_environ;
-               if (getenv(DB_ENVIRONMENT)) {
-                       if (access(getenv(DB_ENVIRONMENT), X_OK))
-                               goto bad_dir_environ;
-               }
-               else {
-                       strcpy(path + len, "/objects");
-                       if (access(path, X_OK))
-                               goto bad_dir_environ;
-               }
-               return NULL;
-       bad_dir_environ:
+               if (is_git_directory(gitdirenv))
+                       return NULL;
                if (nongit_ok) {
                        *nongit_ok = 1;
                        return NULL;
                }
-               path[len] = 0;
-               die("Not a git repository: '%s'", path);
+               die("Not a git repository: '%s'", gitdirenv);
        }
 
        if (!getcwd(cwd, sizeof(cwd)) || cwd[0] != '/')
@@ -197,11 +221,18 @@ const char *setup_git_directory_gently(int *nongit_ok)
 
        offset = len = strlen(cwd);
        for (;;) {
-               if (is_toplevel_directory())
+               if (is_git_directory(".git"))
                        break;
                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");
@@ -220,6 +251,7 @@ const char *setup_git_directory_gently(int *nongit_ok)
        offset++;
        cwd[len++] = '/';
        cwd[len] = 0;
+       inside_git_dir = !strncmp(cwd + offset, ".git/", 5);
        return cwd + offset;
 }
 
index 09456d23f8447711f783178ff4ea5af8f92828e2..8ad7fad825708b5a56a3bfd4b8b98708de0bb2dc 100644 (file)
 #endif
 #endif
 
+#ifdef NO_C99_FORMAT
+#define SZ_FMT "lu"
+#else
+#define SZ_FMT "zu"
+#endif
+
 const unsigned char null_sha1[20];
 
 static unsigned int sha1_file_open_flag = O_NOATIME;
@@ -355,10 +361,8 @@ static void read_info_alternates(const char * relative_base, int depth)
                close(fd);
                return;
        }
-       map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       map = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
-       if (map == MAP_FAILED)
-               return;
 
        link_alt_odb_entries(map, map + st.st_size, '\n', relative_base, depth);
 
@@ -397,16 +401,41 @@ static char *find_sha1_file(const unsigned char *sha1, struct stat *st)
        return NULL;
 }
 
-#define PACK_MAX_SZ (1<<26)
-static int pack_used_ctr;
-static unsigned long pack_mapped;
+static unsigned int pack_used_ctr;
+static unsigned int pack_mmap_calls;
+static unsigned int peak_pack_open_windows;
+static unsigned int pack_open_windows;
+static size_t peak_pack_mapped;
+static size_t pack_mapped;
+static size_t page_size;
 struct packed_git *packed_git;
 
+void pack_report()
+{
+       fprintf(stderr,
+               "pack_report: getpagesize()            = %10" SZ_FMT "\n"
+               "pack_report: core.packedGitWindowSize = %10" SZ_FMT "\n"
+               "pack_report: core.packedGitLimit      = %10" SZ_FMT "\n",
+               page_size,
+               packed_git_window_size,
+               packed_git_limit);
+       fprintf(stderr,
+               "pack_report: pack_used_ctr            = %10u\n"
+               "pack_report: pack_mmap_calls          = %10u\n"
+               "pack_report: pack_open_windows        = %10u / %10u\n"
+               "pack_report: pack_mapped              = "
+                       "%10" SZ_FMT " / %10" SZ_FMT "\n",
+               pack_used_ctr,
+               pack_mmap_calls,
+               pack_open_windows, peak_pack_open_windows,
+               pack_mapped, peak_pack_mapped);
+}
+
 static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
                                void **idx_map_)
 {
        void *idx_map;
-       unsigned int *index;
+       uint32_t *index;
        unsigned long idx_size;
        int nr, i;
        int fd = open(path, O_RDONLY);
@@ -418,10 +447,8 @@ static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
                return -1;
        }
        idx_size = st.st_size;
-       idx_map = mmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       idx_map = xmmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
-       if (idx_map == MAP_FAILED)
-               return -1;
 
        index = idx_map;
        *idx_map_ = idx_map;
@@ -429,12 +456,23 @@ static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
 
        /* check index map */
        if (idx_size < 4*256 + 20 + 20)
-               return error("index file too small");
+               return error("index file %s is too small", path);
+
+       /* a future index format would start with this, as older git
+        * binaries would fail the non-monotonic index check below.
+        * give a nicer warning to the user if we can.
+        */
+       if (index[0] == htonl(PACK_IDX_SIGNATURE))
+               return error("index file %s is a newer version"
+                       " and is not supported by this binary"
+                       " (try upgrading GIT to a newer version)",
+                       path);
+
        nr = 0;
        for (i = 0; i < 256; i++) {
                unsigned int n = ntohl(index[i]);
                if (n < nr)
-                       return error("non-monotonic index");
+                       return error("non-monotonic index %s", path);
                nr = n;
        }
 
@@ -446,91 +484,221 @@ static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
         *  - 20-byte SHA1 file checksum
         */
        if (idx_size != 4*256 + nr * 24 + 20 + 20)
-               return error("wrong index file size");
+               return error("wrong index file size in %s", path);
 
        return 0;
 }
 
-static int unuse_one_packed_git(void)
+static void scan_windows(struct packed_git *p,
+       struct packed_git **lru_p,
+       struct pack_window **lru_w,
+       struct pack_window **lru_l)
 {
-       struct packed_git *p, *lru = NULL;
+       struct pack_window *w, *w_l;
 
-       for (p = packed_git; p; p = p->next) {
-               if (p->pack_use_cnt || !p->pack_base)
-                       continue;
-               if (!lru || p->pack_last_used < lru->pack_last_used)
-                       lru = p;
+       for (w_l = NULL, w = p->windows; w; w = w->next) {
+               if (!w->inuse_cnt) {
+                       if (!*lru_w || w->last_used < (*lru_w)->last_used) {
+                               *lru_p = p;
+                               *lru_w = w;
+                               *lru_l = w_l;
+                       }
+               }
+               w_l = w;
        }
-       if (!lru)
-               return 0;
-       munmap(lru->pack_base, lru->pack_size);
-       lru->pack_base = NULL;
-       return 1;
 }
 
-void unuse_packed_git(struct packed_git *p)
+static int unuse_one_window(struct packed_git *current)
 {
-       p->pack_use_cnt--;
+       struct packed_git *p, *lru_p = NULL;
+       struct pack_window *lru_w = NULL, *lru_l = NULL;
+
+       if (current)
+               scan_windows(current, &lru_p, &lru_w, &lru_l);
+       for (p = packed_git; p; p = p->next)
+               scan_windows(p, &lru_p, &lru_w, &lru_l);
+       if (lru_p) {
+               munmap(lru_w->base, lru_w->len);
+               pack_mapped -= lru_w->len;
+               if (lru_l)
+                       lru_l->next = lru_w->next;
+               else {
+                       lru_p->windows = lru_w->next;
+                       if (!lru_p->windows && lru_p != current) {
+                               close(lru_p->pack_fd);
+                               lru_p->pack_fd = -1;
+                       }
+               }
+               free(lru_w);
+               pack_open_windows--;
+               return 1;
+       }
+       return 0;
 }
 
-int use_packed_git(struct packed_git *p)
+void release_pack_memory(size_t need)
 {
+       size_t cur = pack_mapped;
+       while (need >= (cur - pack_mapped) && unuse_one_window(NULL))
+               ; /* nothing */
+}
+
+void unuse_pack(struct pack_window **w_cursor)
+{
+       struct pack_window *w = *w_cursor;
+       if (w) {
+               w->inuse_cnt--;
+               *w_cursor = NULL;
+       }
+}
+
+/*
+ * Do not call this directly as this leaks p->pack_fd on error return;
+ * call open_packed_git() instead.
+ */
+static int open_packed_git_1(struct packed_git *p)
+{
+       struct stat st;
+       struct pack_header hdr;
+       unsigned char sha1[20];
+       unsigned char *idx_sha1;
+       long fd_flag;
+
+       p->pack_fd = open(p->pack_name, O_RDONLY);
+       if (p->pack_fd < 0 || fstat(p->pack_fd, &st))
+               return -1;
+
+       /* If we created the struct before we had the pack we lack size. */
        if (!p->pack_size) {
-               struct stat st;
-               /* We created the struct before we had the pack */
-               stat(p->pack_name, &st);
                if (!S_ISREG(st.st_mode))
-                       die("packfile %s not a regular file", p->pack_name);
+                       return error("packfile %s not a regular file", p->pack_name);
                p->pack_size = st.st_size;
+       } else if (p->pack_size != st.st_size)
+               return error("packfile %s size changed", p->pack_name);
+
+       /* We leave these file descriptors open with sliding mmap;
+        * there is no point keeping them open across exec(), though.
+        */
+       fd_flag = fcntl(p->pack_fd, F_GETFD, 0);
+       if (fd_flag < 0)
+               return error("cannot determine file descriptor flags");
+       fd_flag |= FD_CLOEXEC;
+       if (fcntl(p->pack_fd, F_SETFD, fd_flag) == -1)
+               return error("cannot set FD_CLOEXEC");
+
+       /* Verify we recognize this pack file format. */
+       if (read_in_full(p->pack_fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+               return error("file %s is far too short to be a packfile", p->pack_name);
+       if (hdr.hdr_signature != htonl(PACK_SIGNATURE))
+               return error("file %s is not a GIT packfile", p->pack_name);
+       if (!pack_version_ok(hdr.hdr_version))
+               return error("packfile %s is version %u and not supported"
+                       " (try upgrading GIT to a newer version)",
+                       p->pack_name, ntohl(hdr.hdr_version));
+
+       /* Verify the pack matches its index. */
+       if (num_packed_objects(p) != ntohl(hdr.hdr_entries))
+               return error("packfile %s claims to have %u objects"
+                       " while index size indicates %u objects",
+                       p->pack_name, ntohl(hdr.hdr_entries),
+                       num_packed_objects(p));
+       if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1)
+               return error("end of packfile %s is unavailable", p->pack_name);
+       if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1))
+               return error("packfile %s signature is unavailable", p->pack_name);
+       idx_sha1 = ((unsigned char *)p->index_base) + p->index_size - 40;
+       if (hashcmp(sha1, idx_sha1))
+               return error("packfile %s does not match index", p->pack_name);
+       return 0;
+}
+
+static int open_packed_git(struct packed_git *p)
+{
+       if (!open_packed_git_1(p))
+               return 0;
+       if (p->pack_fd != -1) {
+               close(p->pack_fd);
+               p->pack_fd = -1;
        }
-       if (!p->pack_base) {
-               int fd;
-               struct stat st;
-               void *map;
-               struct pack_header *hdr;
+       return -1;
+}
 
-               pack_mapped += p->pack_size;
-               while (PACK_MAX_SZ < pack_mapped && unuse_one_packed_git())
-                       ; /* nothing */
-               fd = open(p->pack_name, O_RDONLY);
-               if (fd < 0)
-                       die("packfile %s cannot be opened", p->pack_name);
-               if (fstat(fd, &st)) {
-                       close(fd);
-                       die("packfile %s cannot be opened", p->pack_name);
-               }
-               if (st.st_size != p->pack_size)
-                       die("packfile %s size mismatch.", p->pack_name);
-               map = mmap(NULL, p->pack_size, PROT_READ, MAP_PRIVATE, fd, 0);
-               close(fd);
-               if (map == MAP_FAILED)
-                       die("packfile %s cannot be mapped.", p->pack_name);
-               p->pack_base = map;
+static int in_window(struct pack_window *win, unsigned long offset)
+{
+       /* We must promise at least 20 bytes (one hash) after the
+        * offset is available from this window, otherwise the offset
+        * is not actually in this window and a different window (which
+        * has that one hash excess) must be used.  This is to support
+        * the object header and delta base parsing routines below.
+        */
+       off_t win_off = win->offset;
+       return win_off <= offset
+               && (offset + 20) <= (win_off + win->len);
+}
 
-               /* Check if we understand this pack file.  If we don't we're
-                * likely too old to handle it.
-                */
-               hdr = map;
-               if (hdr->hdr_signature != htonl(PACK_SIGNATURE))
-                       die("packfile %s isn't actually a pack.", p->pack_name);
-               if (!pack_version_ok(hdr->hdr_version))
-                       die("packfile %s is version %i and not supported"
-                               " (try upgrading GIT to a newer version)",
-                               p->pack_name, ntohl(hdr->hdr_version));
-
-               /* Check if the pack file matches with the index file.
-                * this is cheap.
-                */
-               if (hashcmp((unsigned char *)(p->index_base) +
-                           p->index_size - 40,
-                           (unsigned char *)p->pack_base +
-                           p->pack_size - 20)) {
-                       die("packfile %s does not match index.", p->pack_name);
+unsigned char* use_pack(struct packed_git *p,
+               struct pack_window **w_cursor,
+               unsigned long offset,
+               unsigned int *left)
+{
+       struct pack_window *win = *w_cursor;
+
+       if (p->pack_fd == -1 && open_packed_git(p))
+               die("packfile %s cannot be accessed", p->pack_name);
+
+       /* Since packfiles end in a hash of their content and its
+        * pointless to ask for an offset into the middle of that
+        * hash, and the in_window function above wouldn't match
+        * don't allow an offset too close to the end of the file.
+        */
+       if (offset > (p->pack_size - 20))
+               die("offset beyond end of packfile (truncated pack?)");
+
+       if (!win || !in_window(win, offset)) {
+               if (win)
+                       win->inuse_cnt--;
+               for (win = p->windows; win; win = win->next) {
+                       if (in_window(win, offset))
+                               break;
+               }
+               if (!win) {
+                       if (!page_size)
+                               page_size = getpagesize();
+                       win = xcalloc(1, sizeof(*win));
+                       win->offset = (offset / page_size) * page_size;
+                       win->len = p->pack_size - win->offset;
+                       if (win->len > packed_git_window_size)
+                               win->len = packed_git_window_size;
+                       pack_mapped += win->len;
+                       while (packed_git_limit < pack_mapped
+                               && unuse_one_window(p))
+                               ; /* nothing */
+                       win->base = xmmap(NULL, win->len,
+                               PROT_READ, MAP_PRIVATE,
+                               p->pack_fd, win->offset);
+                       if (win->base == MAP_FAILED)
+                               die("packfile %s cannot be mapped: %s",
+                                       p->pack_name,
+                                       strerror(errno));
+                       pack_mmap_calls++;
+                       pack_open_windows++;
+                       if (pack_mapped > peak_pack_mapped)
+                               peak_pack_mapped = pack_mapped;
+                       if (pack_open_windows > peak_pack_open_windows)
+                               peak_pack_open_windows = pack_open_windows;
+                       win->next = p->windows;
+                       p->windows = win;
                }
        }
-       p->pack_last_used = pack_used_ctr++;
-       p->pack_use_cnt++;
-       return 0;
+       if (win != *w_cursor) {
+               win->last_used = pack_used_ctr++;
+               win->inuse_cnt++;
+               *w_cursor = win;
+       }
+       offset -= win->offset;
+       if (left)
+               *left = win->len - offset;
+       return win->base + offset;
 }
 
 struct packed_git *add_packed_git(char *path, int path_len, int local)
@@ -559,9 +727,8 @@ struct packed_git *add_packed_git(char *path, int path_len, int local)
        p->pack_size = st.st_size;
        p->index_base = idx_map;
        p->next = NULL;
-       p->pack_base = NULL;
-       p->pack_last_used = 0;
-       p->pack_use_cnt = 0;
+       p->windows = NULL;
+       p->pack_fd = -1;
        p->pack_local = local;
        if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1))
                hashcpy(p->sha1, sha1);
@@ -592,9 +759,8 @@ struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_pa
        p->pack_size = 0;
        p->index_base = idx_map;
        p->next = NULL;
-       p->pack_base = NULL;
-       p->pack_last_used = 0;
-       p->pack_use_cnt = 0;
+       p->windows = NULL;
+       p->pack_fd = -1;
        hashcpy(p->sha1, sha1);
        return p;
 }
@@ -629,7 +795,7 @@ static void prepare_packed_git_one(char *objdir, int local)
                if (!has_extension(de->d_name, ".idx"))
                        continue;
 
-               /* we have .idx.  Is it a file we can map? */
+               /* Don't reopen a pack we already have. */
                strcpy(path + len, de->d_name);
                for (p = packed_git; p; p = p->next) {
                        if (!memcmp(path, p->pack_name, len + namelen - 4))
@@ -637,11 +803,13 @@ static void prepare_packed_git_one(char *objdir, int local)
                }
                if (p)
                        continue;
+               /* See if it really is a valid .idx file with corresponding
+                * .pack file that we can map.
+                */
                p = add_packed_git(path, len + namelen, local);
                if (!p)
                        continue;
-               p->next = packed_git;
-               packed_git = p;
+               install_packed_git(p);
        }
        closedir(dir);
 }
@@ -705,10 +873,8 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
                 */
                sha1_file_open_flag = 0;
        }
-       map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       map = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
-       if (map == MAP_FAILED)
-               return NULL;
        *size = st.st_size;
        return map;
 }
@@ -878,18 +1044,21 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned l
 }
 
 static unsigned long get_delta_base(struct packed_git *p,
+                                   struct pack_window **w_curs,
                                    unsigned long offset,
                                    enum object_type kind,
                                    unsigned long delta_obj_offset,
                                    unsigned long *base_obj_offset)
 {
-       unsigned char *base_info = (unsigned char *) p->pack_base + offset;
+       unsigned char *base_info = use_pack(p, w_curs, offset, NULL);
        unsigned long base_offset;
 
-       /* there must be at least 20 bytes left regardless of delta type */
-       if (p->pack_size <= offset + 20)
-               die("truncated pack file");
-
+       /* use_pack() assured us we have [base_info, base_info + 20)
+        * as a range that we can look at without walking off the
+        * end of the mapped window.  Its actually the hash size
+        * that is assured.  An OFS_DELTA longer than the hash size
+        * is stupid, as then a REF_DELTA would be smaller to store.
+        */
        if (kind == OBJ_OFS_DELTA) {
                unsigned used = 0;
                unsigned char c = base_info[used++];
@@ -923,6 +1092,7 @@ static int packed_object_info(struct packed_git *p, unsigned long offset,
                              char *type, unsigned long *sizep);
 
 static int packed_delta_info(struct packed_git *p,
+                            struct pack_window **w_curs,
                             unsigned long offset,
                             enum object_type kind,
                             unsigned long obj_offset,
@@ -931,7 +1101,8 @@ static int packed_delta_info(struct packed_git *p,
 {
        unsigned long base_offset;
 
-       offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
+       offset = get_delta_base(p, w_curs, offset, kind,
+               obj_offset, &base_offset);
 
        /* We choose to only get the type of the base object and
         * ignore potentially corrupt pack file that expects the delta
@@ -943,20 +1114,23 @@ static int packed_delta_info(struct packed_git *p,
 
        if (sizep) {
                const unsigned char *data;
-               unsigned char delta_head[20];
+               unsigned char delta_head[20], *in;
                unsigned long result_size;
                z_stream stream;
                int st;
 
                memset(&stream, 0, sizeof(stream));
-
-               stream.next_in = (unsigned char *) p->pack_base + offset;
-               stream.avail_in = p->pack_size - offset;
                stream.next_out = delta_head;
                stream.avail_out = sizeof(delta_head);
 
                inflateInit(&stream);
-               st = inflate(&stream, Z_FINISH);
+               do {
+                       in = use_pack(p, w_curs, offset, &stream.avail_in);
+                       stream.next_in = in;
+                       st = inflate(&stream, Z_FINISH);
+                       offset += stream.next_in - in;
+               } while ((st == Z_OK || st == Z_BUF_ERROR)
+                       && stream.total_out < sizeof(delta_head));
                inflateEnd(&stream);
                if ((st != Z_STREAM_END) &&
                    stream.total_out != sizeof(delta_head))
@@ -977,17 +1151,24 @@ static int packed_delta_info(struct packed_git *p,
        return 0;
 }
 
-static unsigned long unpack_object_header(struct packed_git *p, unsigned long offset,
-       enum object_type *type, unsigned long *sizep)
+static unsigned long unpack_object_header(struct packed_git *p,
+               struct pack_window **w_curs,
+               unsigned long offset,
+               enum object_type *type,
+               unsigned long *sizep)
 {
+       unsigned char *base;
+       unsigned int left;
        unsigned long used;
 
-       if (p->pack_size <= offset)
-               die("object offset outside of pack file");
-
-       used = unpack_object_header_gently((unsigned char *)p->pack_base +
-                                          offset,
-                                          p->pack_size - offset, type, sizep);
+       /* use_pack() assures us we have [base, base + 20) available
+        * as a range that we can look at at.  (Its actually the hash
+        * size that is assured.)  With our object header encoding
+        * the maximum deflated object size is 2^137, which is just
+        * insane, so we know won't exceed what we have been given.
+        */
+       base = use_pack(p, w_curs, offset, &left);
+       used = unpack_object_header_gently(base, left, type, sizep);
        if (!used)
                die("object offset outside of pack file");
 
@@ -1002,18 +1183,19 @@ void packed_object_info_detail(struct packed_git *p,
                               unsigned int *delta_chain_length,
                               unsigned char *base_sha1)
 {
+       struct pack_window *w_curs = NULL;
        unsigned long obj_offset, val;
        unsigned char *next_sha1;
        enum object_type kind;
 
        *delta_chain_length = 0;
        obj_offset = offset;
-       offset = unpack_object_header(p, offset, &kind, size);
+       offset = unpack_object_header(p, &w_curs, offset, &kind, size);
 
        for (;;) {
                switch (kind) {
                default:
-                       die("corrupted pack file %s containing object of kind %d",
+                       die("pack %s contains unknown object type %d",
                            p->pack_name, kind);
                case OBJ_COMMIT:
                case OBJ_TREE:
@@ -1021,25 +1203,24 @@ void packed_object_info_detail(struct packed_git *p,
                case OBJ_TAG:
                        strcpy(type, type_names[kind]);
                        *store_size = 0; /* notyet */
+                       unuse_pack(&w_curs);
                        return;
                case OBJ_OFS_DELTA:
-                       get_delta_base(p, offset, kind, obj_offset, &offset);
+                       get_delta_base(p, &w_curs, offset, kind,
+                               obj_offset, &offset);
                        if (*delta_chain_length == 0) {
                                /* TODO: find base_sha1 as pointed by offset */
                        }
                        break;
                case OBJ_REF_DELTA:
-                       if (p->pack_size <= offset + 20)
-                               die("pack file %s records an incomplete delta base",
-                                   p->pack_name);
-                       next_sha1 = (unsigned char *) p->pack_base + offset;
+                       next_sha1 = use_pack(p, &w_curs, offset, NULL);
                        if (*delta_chain_length == 0)
                                hashcpy(base_sha1, next_sha1);
                        offset = find_pack_entry_one(next_sha1, p);
                        break;
                }
                obj_offset = offset;
-               offset = unpack_object_header(p, offset, &kind, &val);
+               offset = unpack_object_header(p, &w_curs, offset, &kind, &val);
                (*delta_chain_length)++;
        }
 }
@@ -1047,23 +1228,29 @@ void packed_object_info_detail(struct packed_git *p,
 static int packed_object_info(struct packed_git *p, unsigned long offset,
                              char *type, unsigned long *sizep)
 {
+       struct pack_window *w_curs = NULL;
        unsigned long size, obj_offset = offset;
        enum object_type kind;
+       int r;
 
-       offset = unpack_object_header(p, offset, &kind, &size);
+       offset = unpack_object_header(p, &w_curs, offset, &kind, &size);
 
        switch (kind) {
        case OBJ_OFS_DELTA:
        case OBJ_REF_DELTA:
-               return packed_delta_info(p, offset, kind, obj_offset, type, sizep);
+               r = packed_delta_info(p, &w_curs, offset, kind,
+                       obj_offset, type, sizep);
+               unuse_pack(&w_curs);
+               return r;
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG:
                strcpy(type, type_names[kind]);
+               unuse_pack(&w_curs);
                break;
        default:
-               die("corrupted pack file %s containing object of kind %d",
+               die("pack %s contains unknown object type %d",
                    p->pack_name, kind);
        }
        if (sizep)
@@ -1072,23 +1259,27 @@ static int packed_object_info(struct packed_git *p, unsigned long offset,
 }
 
 static void *unpack_compressed_entry(struct packed_git *p,
+                                   struct pack_window **w_curs,
                                    unsigned long offset,
                                    unsigned long size)
 {
        int st;
        z_stream stream;
-       unsigned char *buffer;
+       unsigned char *buffer, *in;
 
        buffer = xmalloc(size + 1);
        buffer[size] = 0;
        memset(&stream, 0, sizeof(stream));
-       stream.next_in = (unsigned char*)p->pack_base + offset;
-       stream.avail_in = p->pack_size - offset;
        stream.next_out = buffer;
        stream.avail_out = size;
 
        inflateInit(&stream);
-       st = inflate(&stream, Z_FINISH);
+       do {
+               in = use_pack(p, w_curs, offset, &stream.avail_in);
+               stream.next_in = in;
+               st = inflate(&stream, Z_FINISH);
+               offset += stream.next_in - in;
+       } while (st == Z_OK || st == Z_BUF_ERROR);
        inflateEnd(&stream);
        if ((st != Z_STREAM_END) || stream.total_out != size) {
                free(buffer);
@@ -1099,6 +1290,7 @@ static void *unpack_compressed_entry(struct packed_git *p,
 }
 
 static void *unpack_delta_entry(struct packed_git *p,
+                               struct pack_window **w_curs,
                                unsigned long offset,
                                unsigned long delta_size,
                                enum object_type kind,
@@ -1109,13 +1301,14 @@ static void *unpack_delta_entry(struct packed_git *p,
        void *delta_data, *result, *base;
        unsigned long result_size, base_size, base_offset;
 
-       offset = get_delta_base(p, offset, kind, obj_offset, &base_offset);
-       base = unpack_entry_gently(p, base_offset, type, &base_size);
+       offset = get_delta_base(p, w_curs, offset, kind,
+               obj_offset, &base_offset);
+       base = unpack_entry(p, base_offset, type, &base_size);
        if (!base)
                die("failed to read delta base object at %lu from %s",
                    base_offset, p->pack_name);
 
-       delta_data = unpack_compressed_entry(p, offset, delta_size);
+       delta_data = unpack_compressed_entry(p, w_curs, offset, delta_size);
        result = patch_delta(base, base_size,
                             delta_data, delta_size,
                             &result_size);
@@ -1127,43 +1320,34 @@ static void *unpack_delta_entry(struct packed_git *p,
        return result;
 }
 
-static void *unpack_entry(struct pack_entry *entry,
-                         char *type, unsigned long *sizep)
-{
-       struct packed_git *p = entry->p;
-       void *retval;
-
-       if (use_packed_git(p))
-               die("cannot map packed file");
-       retval = unpack_entry_gently(p, entry->offset, type, sizep);
-       unuse_packed_git(p);
-       if (!retval)
-               die("corrupted pack file %s", p->pack_name);
-       return retval;
-}
-
-/* The caller is responsible for use_packed_git()/unuse_packed_git() pair */
-void *unpack_entry_gently(struct packed_git *p, unsigned long offset,
+void *unpack_entry(struct packed_git *p, unsigned long offset,
                          char *type, unsigned long *sizep)
 {
+       struct pack_window *w_curs = NULL;
        unsigned long size, obj_offset = offset;
        enum object_type kind;
+       void *retval;
 
-       offset = unpack_object_header(p, offset, &kind, &size);
+       offset = unpack_object_header(p, &w_curs, offset, &kind, &size);
        switch (kind) {
        case OBJ_OFS_DELTA:
        case OBJ_REF_DELTA:
-               return unpack_delta_entry(p, offset, size, kind, obj_offset, type, sizep);
+               retval = unpack_delta_entry(p, &w_curs, offset, size,
+                       kind, obj_offset, type, sizep);
+               break;
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG:
                strcpy(type, type_names[kind]);
                *sizep = size;
-               return unpack_compressed_entry(p, offset, size);
+               retval = unpack_compressed_entry(p, &w_curs, offset, size);
+               break;
        default:
-               return NULL;
+               die("unknown object type %i in %s", kind, p->pack_name);
        }
+       unuse_pack(&w_curs);
+       return retval;
 }
 
 int num_packed_objects(const struct packed_git *p)
@@ -1185,7 +1369,7 @@ int nth_packed_object_sha1(const struct packed_git *p, int n,
 unsigned long find_pack_entry_one(const unsigned char *sha1,
                                  struct packed_git *p)
 {
-       unsigned int *level1_ofs = p->index_base;
+       uint32_t *level1_ofs = p->index_base;
        int hi = ntohl(level1_ofs[*sha1]);
        int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
        void *index = p->index_base + 256;
@@ -1194,7 +1378,7 @@ unsigned long find_pack_entry_one(const unsigned char *sha1,
                int mi = (lo + hi) / 2;
                int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1);
                if (!cmp)
-                       return ntohl(*((unsigned int *) ((char *) index + (24 * mi))));
+                       return ntohl(*((uint32_t *)((char *)index + (24 * mi))));
                if (cmp > 0)
                        hi = mi;
                else
@@ -1239,6 +1423,18 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons
                }
                offset = find_pack_entry_one(sha1, p);
                if (offset) {
+                       /*
+                        * We are about to tell the caller where they can
+                        * locate the requested object.  We better make
+                        * sure the packfile is still here and can be
+                        * accessed before supplying that answer, as
+                        * it may have been deleted since the index
+                        * was loaded!
+                        */
+                       if (p->pack_fd == -1 && open_packed_git(p)) {
+                               error("packfile %s cannot be accessed", p->pack_name);
+                               continue;
+                       }
                        e->offset = offset;
                        e->p = p;
                        hashcpy(e->sha1, sha1);
@@ -1261,7 +1457,7 @@ struct packed_git *find_sha1_pack(const unsigned char *sha1,
        
 }
 
-int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
+static int sha1_loose_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
 {
        int status;
        unsigned long mapsize, size;
@@ -1270,20 +1466,8 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
        char hdr[128];
 
        map = map_sha1_file(sha1, &mapsize);
-       if (!map) {
-               struct pack_entry e;
-
-               if (!find_pack_entry(sha1, &e, NULL)) {
-                       reprepare_packed_git();
-                       if (!find_pack_entry(sha1, &e, NULL))
-                               return error("unable to find %s", sha1_to_hex(sha1));
-               }
-               if (use_packed_git(e.p))
-                       die("cannot map packed file");
-               status = packed_object_info(e.p, e.offset, type, sizep);
-               unuse_packed_git(e.p);
-               return status;
-       }
+       if (!map)
+               return error("unable to find %s", sha1_to_hex(sha1));
        if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
                status = error("unable to unpack %s header",
                               sha1_to_hex(sha1));
@@ -1299,25 +1483,93 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
        return status;
 }
 
-static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
+int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep)
 {
        struct pack_entry e;
 
        if (!find_pack_entry(sha1, &e, NULL)) {
-               error("cannot read sha1_file for %s", sha1_to_hex(sha1));
+               reprepare_packed_git();
+               if (!find_pack_entry(sha1, &e, NULL))
+                       return sha1_loose_object_info(sha1, type, sizep);
+       }
+       return packed_object_info(e.p, e.offset, type, sizep);
+}
+
+static void *read_packed_sha1(const unsigned char *sha1, char *type, unsigned long *size)
+{
+       struct pack_entry e;
+
+       if (!find_pack_entry(sha1, &e, NULL))
                return NULL;
+       else
+               return unpack_entry(e.p, e.offset, type, size);
+}
+
+/*
+ * This is meant to hold a *small* number of objects that you would
+ * want read_sha1_file() to be able to return, but yet you do not want
+ * to write them into the object store (e.g. a browse-only
+ * application).
+ */
+static struct cached_object {
+       unsigned char sha1[20];
+       const char *type;
+       void *buf;
+       unsigned long size;
+} *cached_objects;
+static int cached_object_nr, cached_object_alloc;
+
+static struct cached_object *find_cached_object(const unsigned char *sha1)
+{
+       int i;
+       struct cached_object *co = cached_objects;
+
+       for (i = 0; i < cached_object_nr; i++, co++) {
+               if (!hashcmp(co->sha1, sha1))
+                       return co;
+       }
+       return NULL;
+}
+
+int pretend_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1)
+{
+       struct cached_object *co;
+
+       hash_sha1_file(buf, len, type, sha1);
+       if (has_sha1_file(sha1) || find_cached_object(sha1))
+               return 0;
+       if (cached_object_alloc <= cached_object_nr) {
+               cached_object_alloc = alloc_nr(cached_object_alloc);
+               cached_objects = xrealloc(cached_objects,
+                                         sizeof(*cached_objects) *
+                                         cached_object_alloc);
        }
-       return unpack_entry(&e, type, size);
+       co = &cached_objects[cached_object_nr++];
+       co->size = len;
+       co->type = strdup(type);
+       hashcpy(co->sha1, sha1);
+       return 0;
 }
 
 void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size)
 {
        unsigned long mapsize;
        void *map, *buf;
-       struct pack_entry e;
+       struct cached_object *co;
+
+       co = find_cached_object(sha1);
+       if (co) {
+               buf = xmalloc(co->size + 1);
+               memcpy(buf, co->buf, co->size);
+               ((char*)buf)[co->size] = 0;
+               strcpy(type, co->type);
+               *size = co->size;
+               return buf;
+       }
 
-       if (find_pack_entry(sha1, &e, NULL))
-               return read_packed_sha1(sha1, type, size);
+       buf = read_packed_sha1(sha1, type, size);
+       if (buf)
+               return buf;
        map = map_sha1_file(sha1, &mapsize);
        if (map) {
                buf = unpack_sha1_file(map, mapsize, type, size);
@@ -1325,9 +1577,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size
                return buf;
        }
        reprepare_packed_git();
-       if (find_pack_entry(sha1, &e, NULL))
-               return read_packed_sha1(sha1, type, size);
-       return NULL;
+       return read_packed_sha1(sha1, type, size);
 }
 
 void *read_object_with_reference(const unsigned char *sha1,
@@ -1465,20 +1715,8 @@ int move_temp_to_file(const char *tmpfile, const char *filename)
 
 static int write_buffer(int fd, const void *buf, size_t len)
 {
-       while (len) {
-               ssize_t size;
-
-               size = write(fd, buf, len);
-               if (!size)
-                       return error("file write: disk full");
-               if (size < 0) {
-                       if (errno == EINTR || errno == EAGAIN)
-                               continue;
-                       return error("file write error (%s)", strerror(errno));
-               }
-               len -= size;
-               buf = (char *) buf + size;
-       }
+       if (write_in_full(fd, buf, len) < 0)
+               return error("file write error (%s)", strerror(errno));
        return 0;
 }
 
@@ -1627,6 +1865,8 @@ static void *repack_object(const unsigned char *sha1, unsigned long *objsize)
 
        /* need to unpack and recompress it by itself */
        unpacked = read_packed_sha1(sha1, type, &len);
+       if (!unpacked)
+               error("cannot read sha1_file for %s", sha1_to_hex(sha1));
 
        hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
 
@@ -1723,7 +1963,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                        if (ret != Z_OK)
                                break;
                }
-               size = read(fd, buffer + *bufposn, bufsize - *bufposn);
+               size = xread(fd, buffer + *bufposn, bufsize - *bufposn);
                if (size <= 0) {
                        close(local);
                        unlink(tmpfile);
@@ -1846,13 +2086,12 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
 
        buf = "";
        if (size)
-               buf = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
+               buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
-       if (buf == MAP_FAILED)
-               return -1;
 
        if (!type)
                type = blob_type;
+       /* FIXME: CRLF -> LF conversion here for blobs! We'll need the path! */
        if (write_object)
                ret = write_sha1_file(buf, size, type, sha1);
        else
@@ -1897,3 +2136,24 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
        }
        return 0;
 }
+
+int read_pack_header(int fd, struct pack_header *header)
+{
+       char *c = (char*)header;
+       ssize_t remaining = sizeof(struct pack_header);
+       do {
+               ssize_t r = xread(fd, c, remaining);
+               if (r <= 0)
+                       /* "eof before pack header was fully read" */
+                       return PH_ERROR_EOF;
+               remaining -= r;
+               c += r;
+       } while (remaining > 0);
+       if (header->hdr_signature != htonl(PACK_SIGNATURE))
+               /* "protocol error (pack signature mismatch detected)" */
+               return PH_ERROR_PACK_SIGNATURE;
+       if (!pack_version_ok(header->hdr_version))
+               /* "protocol error (pack version unsupported)" */
+               return PH_ERROR_PROTOCOL;
+       return 0;
+}
index 6d7cd78381414aa2fef31d31d46fbb24b0aaab1d..a7efa96f35c3a9aadf222f934c37515f00e68676 100644 (file)
@@ -235,24 +235,79 @@ static int ambiguous_path(const char *path, int len)
        return slash;
 }
 
+static const char *ref_fmt[] = {
+       "%.*s",
+       "refs/%.*s",
+       "refs/tags/%.*s",
+       "refs/heads/%.*s",
+       "refs/remotes/%.*s",
+       "refs/remotes/%.*s/HEAD",
+       NULL
+};
+
+int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref)
+{
+       const char **p, *r;
+       int refs_found = 0;
+
+       *ref = NULL;
+       for (p = ref_fmt; *p; p++) {
+               unsigned char sha1_from_ref[20];
+               unsigned char *this_result;
+
+               this_result = refs_found ? sha1_from_ref : sha1;
+               r = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL);
+               if (r) {
+                       if (!refs_found++)
+                               *ref = xstrdup(r);
+                       if (!warn_ambiguous_refs)
+                               break;
+               }
+       }
+       return refs_found;
+}
+
+int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
+{
+       const char **p;
+       int logs_found = 0;
+
+       *log = NULL;
+       for (p = ref_fmt; *p; p++) {
+               struct stat st;
+               unsigned char hash[20];
+               char path[PATH_MAX];
+               const char *ref, *it;
+
+               strcpy(path, mkpath(*p, len, str));
+               ref = resolve_ref(path, hash, 0, NULL);
+               if (!ref)
+                       continue;
+               if (!stat(git_path("logs/%s", path), &st) &&
+                   S_ISREG(st.st_mode))
+                       it = path;
+               else if (strcmp(ref, path) &&
+                        !stat(git_path("logs/%s", ref), &st) &&
+                        S_ISREG(st.st_mode))
+                       it = ref;
+               else
+                       continue;
+               if (!logs_found++) {
+                       *log = xstrdup(it);
+                       hashcpy(sha1, hash);
+               }
+               if (!warn_ambiguous_refs)
+                       break;
+       }
+       return logs_found;
+}
+
 static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
 {
-       static const char *fmt[] = {
-               "%.*s",
-               "refs/%.*s",
-               "refs/tags/%.*s",
-               "refs/heads/%.*s",
-               "refs/remotes/%.*s",
-               "refs/remotes/%.*s/HEAD",
-               NULL
-       };
        static const char *warning = "warning: refname '%.*s' is ambiguous.\n";
-       const char **p, *ref;
        char *real_ref = NULL;
        int refs_found = 0;
        int at, reflog_len;
-       unsigned char *this_result;
-       unsigned char sha1_from_ref[20];
 
        if (len == 40 && !get_sha1_hex(str, sha1))
                return 0;
@@ -260,7 +315,7 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        /* basic@{time or number} format to query ref-log */
        reflog_len = at = 0;
        if (str[len-1] == '}') {
-               for (at = 1; at < len - 1; at++) {
+               for (at = 0; at < len - 1; at++) {
                        if (str[at] == '@' && str[at+1] == '{') {
                                reflog_len = (len-1) - (at+2);
                                len = at;
@@ -270,19 +325,16 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
        }
 
        /* Accept only unambiguous ref paths. */
-       if (ambiguous_path(str, len))
+       if (len && ambiguous_path(str, len))
                return -1;
 
-       for (p = fmt; *p; p++) {
-               this_result = refs_found ? sha1_from_ref : sha1;
-               ref = resolve_ref(mkpath(*p, len, str), this_result, 1, NULL);
-               if (ref) {
-                       if (!refs_found++)
-                               real_ref = xstrdup(ref);
-                       if (!warn_ambiguous_refs)
-                               break;
-               }
-       }
+       if (!len && reflog_len) {
+               /* allow "@{...}" to mean the current branch reflog */
+               refs_found = dwim_ref("HEAD", 4, sha1, &real_ref);
+       } else if (reflog_len)
+               refs_found = dwim_log(str, len, sha1, &real_ref);
+       else
+               refs_found = dwim_ref(str, len, sha1, &real_ref);
 
        if (!refs_found)
                return -1;
@@ -291,9 +343,12 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                fprintf(stderr, warning, len, str);
 
        if (reflog_len) {
-               /* Is it asking for N-th entry, or approxidate? */
                int nth, i;
                unsigned long at_time;
+               unsigned long co_time;
+               int co_tz, co_cnt;
+
+               /* Is it asking for N-th entry, or approxidate? */
                for (i = nth = 0; 0 <= nth && i < reflog_len; i++) {
                        char ch = str[at+2+i];
                        if ('0' <= ch && ch <= '9')
@@ -305,7 +360,18 @@ static int get_sha1_basic(const char *str, int len, unsigned char *sha1)
                        at_time = 0;
                else
                        at_time = approxidate(str + at + 2);
-               read_ref_at(real_ref, at_time, nth, sha1);
+               if (read_ref_at(real_ref, at_time, nth, sha1, NULL,
+                               &co_time, &co_tz, &co_cnt)) {
+                       if (at_time)
+                               fprintf(stderr,
+                                       "warning: Log for '%.*s' only goes "
+                                       "back to %s.\n", len, str,
+                                       show_rfc2822_date(co_time, co_tz));
+                       else
+                               fprintf(stderr,
+                                       "warning: Log for '%.*s' only has "
+                                       "%d entries.\n", len, str, co_cnt);
+               }
        }
 
        free(real_ref);
diff --git a/shallow.c b/shallow.c
new file mode 100644 (file)
index 0000000..d178689
--- /dev/null
+++ b/shallow.c
@@ -0,0 +1,104 @@
+#include "cache.h"
+#include "commit.h"
+#include "tag.h"
+
+static int is_shallow = -1;
+
+int register_shallow(const unsigned char *sha1)
+{
+       struct commit_graft *graft =
+               xmalloc(sizeof(struct commit_graft));
+       struct commit *commit = lookup_commit(sha1);
+
+       hashcpy(graft->sha1, sha1);
+       graft->nr_parent = -1;
+       if (commit && commit->object.parsed)
+               commit->parents = NULL;
+       return register_commit_graft(graft, 0);
+}
+
+int is_repository_shallow(void)
+{
+       FILE *fp;
+       char buf[1024];
+
+       if (is_shallow >= 0)
+               return is_shallow;
+
+       fp = fopen(git_path("shallow"), "r");
+       if (!fp) {
+               is_shallow = 0;
+               return is_shallow;
+       }
+       is_shallow = 1;
+
+       while (fgets(buf, sizeof(buf), fp)) {
+               unsigned char sha1[20];
+               if (get_sha1_hex(buf, sha1))
+                       die("bad shallow line: %s", buf);
+               register_shallow(sha1);
+       }
+       fclose(fp);
+       return is_shallow;
+}
+
+struct commit_list *get_shallow_commits(struct object_array *heads, int depth,
+               int shallow_flag, int not_shallow_flag)
+{
+       int i = 0, cur_depth = 0;
+       struct commit_list *result = NULL;
+       struct object_array stack = {0, 0, NULL};
+       struct commit *commit = NULL;
+
+       while (commit || i < heads->nr || stack.nr) {
+               struct commit_list *p;
+               if (!commit) {
+                       if (i < heads->nr) {
+                               commit = (struct commit *)
+                                       deref_tag(heads->objects[i++].item, NULL, 0);
+                               if (commit->object.type != OBJ_COMMIT) {
+                                       commit = NULL;
+                                       continue;
+                               }
+                               if (!commit->util)
+                                       commit->util = xmalloc(sizeof(int));
+                               *(int *)commit->util = 0;
+                               cur_depth = 0;
+                       } else {
+                               commit = (struct commit *)
+                                       stack.objects[--stack.nr].item;
+                               cur_depth = *(int *)commit->util;
+                       }
+               }
+               parse_commit(commit);
+               commit->object.flags |= not_shallow_flag;
+               cur_depth++;
+               for (p = commit->parents, commit = NULL; p; p = p->next) {
+                       if (!p->item->util) {
+                               int *pointer = xmalloc(sizeof(int));
+                               p->item->util = pointer;
+                               *pointer =  cur_depth;
+                       } else {
+                               int *pointer = p->item->util;
+                               if (cur_depth >= *pointer)
+                                       continue;
+                               *pointer = cur_depth;
+                       }
+                       if (cur_depth < depth) {
+                               if (p->next)
+                                       add_object_array(&p->item->object,
+                                                       NULL, &stack);
+                               else {
+                                       commit = p->item;
+                                       cur_depth = *(int *)commit->util;
+                               }
+                       } else {
+                               commit_list_insert(p->item, &result);
+                               p->item->object.flags |= shallow_flag;
+                       }
+               }
+       }
+
+       return result;
+}
+
index b006c5c9802d96bc504188e10a9698a1f943477c..bdf51a7a1437efd49fb5c642ab9294c7f43698de 100644 (file)
@@ -20,22 +20,6 @@ static int fd_out;
 static unsigned char remote_version;
 static unsigned char local_version = 1;
 
-static ssize_t force_write(int fd, void *buffer, size_t length)
-{
-       ssize_t ret = 0;
-       while (ret < length) {
-               ssize_t size = write(fd, (char *) buffer + ret, length - ret);
-               if (size < 0) {
-                       return size;
-               }
-               if (size == 0) {
-                       return ret;
-               }
-               ret += size;
-       }
-       return ret;
-}
-
 static int prefetches;
 
 static struct object_list *in_transit;
@@ -53,8 +37,9 @@ void prefetch(unsigned char *sha1)
        node->item = lookup_unknown_object(sha1);
        *end_of_transit = node;
        end_of_transit = &node->next;
-       force_write(fd_out, &type, 1);
-       force_write(fd_out, sha1, 20);
+       /* XXX: what if these writes fail? */
+       write_in_full(fd_out, &type, 1);
+       write_in_full(fd_out, sha1, 20);
        prefetches++;
 }
 
@@ -82,7 +67,7 @@ int fetch(unsigned char *sha1)
                remote = conn_buf[0];
                memmove(conn_buf, conn_buf + 1, --conn_buf_posn);
        } else {
-               if (read(fd_in, &remote, 1) < 1)
+               if (xread(fd_in, &remote, 1) < 1)
                        return -1;
        }
        /* fprintf(stderr, "Got %d\n", remote); */
@@ -97,9 +82,11 @@ int fetch(unsigned char *sha1)
 static int get_version(void)
 {
        char type = 'v';
-       write(fd_out, &type, 1);
-       write(fd_out, &local_version, 1);
-       if (read(fd_in, &remote_version, 1) < 1) {
+       if (write_in_full(fd_out, &type, 1) != 1 ||
+           write_in_full(fd_out, &local_version, 1)) {
+               return error("Couldn't request version from remote end");
+       }
+       if (xread(fd_in, &remote_version, 1) < 1) {
                return error("Couldn't read version from remote end");
        }
        return 0;
@@ -109,12 +96,17 @@ int fetch_ref(char *ref, unsigned char *sha1)
 {
        signed char remote;
        char type = 'r';
-       write(fd_out, &type, 1);
-       write(fd_out, ref, strlen(ref) + 1);
-       read(fd_in, &remote, 1);
+       int length = strlen(ref) + 1;
+       if (write_in_full(fd_out, &type, 1) != 1 ||
+           write_in_full(fd_out, ref, length) != length)
+               return -1;
+
+       if (read_in_full(fd_in, &remote, 1) != 1)
+               return -1;
        if (remote < 0)
                return remote;
-       read(fd_in, sha1, 20);
+       if (read_in_full(fd_in, sha1, 20) != 20)
+               return -1;
        return 0;
 }
 
@@ -132,7 +124,6 @@ int main(int argc, char **argv)
        prog = getenv("GIT_SSH_PUSH");
        if (!prog) prog = "git-ssh-upload";
 
-       setup_ident();
        setup_git_directory();
        git_config(git_default_config);
 
index 20b15eab57e1c4f7c4b306010d7d12bff2308141..2f045727875707198dd1763d75c9e8c7406e9042 100644 (file)
@@ -12,8 +12,6 @@
 #include "rsh.h"
 #include "refs.h"
 
-#include <string.h>
-
 static unsigned char local_version = 1;
 static unsigned char remote_version;
 
@@ -23,17 +21,14 @@ static int serve_object(int fd_in, int fd_out) {
        ssize_t size;
        unsigned char sha1[20];
        signed char remote;
-       int posn = 0;
-       do {
-               size = read(fd_in, sha1 + posn, 20 - posn);
-               if (size < 0) {
-                       perror("git-ssh-upload: read ");
-                       return -1;
-               }
-               if (!size)
-                       return -1;
-               posn += size;
-       } while (posn < 20);
+
+       size = read_in_full(fd_in, sha1, 20);
+       if (size < 0) {
+               perror("git-ssh-upload: read ");
+               return -1;
+       }
+       if (!size)
+               return -1;
        
        if (verbose)
                fprintf(stderr, "Serving %s\n", sha1_to_hex(sha1));
@@ -46,7 +41,8 @@ static int serve_object(int fd_in, int fd_out) {
                remote = -1;
        }
        
-       write(fd_out, &remote, 1);
+       if (write_in_full(fd_out, &remote, 1) != 1)
+               return 0;
        
        if (remote < 0)
                return 0;
@@ -56,9 +52,9 @@ static int serve_object(int fd_in, int fd_out) {
 
 static int serve_version(int fd_in, int fd_out)
 {
-       if (read(fd_in, &remote_version, 1) < 1)
+       if (xread(fd_in, &remote_version, 1) < 1)
                return -1;
-       write(fd_out, &local_version, 1);
+       write_in_full(fd_out, &local_version, 1);
        return 0;
 }
 
@@ -69,7 +65,7 @@ static int serve_ref(int fd_in, int fd_out)
        int posn = 0;
        signed char remote = 0;
        do {
-               if (read(fd_in, ref + posn, 1) < 1)
+               if (posn >= PATH_MAX || xread(fd_in, ref + posn, 1) < 1)
                        return -1;
                posn++;
        } while (ref[posn - 1]);
@@ -79,10 +75,11 @@ static int serve_ref(int fd_in, int fd_out)
 
        if (get_ref_sha1(ref, sha1))
                remote = -1;
-       write(fd_out, &remote, 1);
+       if (write_in_full(fd_out, &remote, 1) != 1)
+               return 0;
        if (remote)
                return 0;
-       write(fd_out, sha1, 20);
+       write_in_full(fd_out, sha1, 20);
         return 0;
 }
 
@@ -91,7 +88,7 @@ static void service(int fd_in, int fd_out) {
        char type;
        int retval;
        do {
-               retval = read(fd_in, &type, 1);
+               retval = xread(fd_in, &type, 1);
                if (retval < 1) {
                        if (retval < 0)
                                perror("git-ssh-upload: read ");
index 9d9d8bed915483abbc2ebb340e0881ae4e296bd4..7f14b0fb59bd7e14979b002f441ae84ff5b0e9a2 100644 (file)
--- a/strbuf.c
+++ b/strbuf.c
@@ -1,7 +1,5 @@
-#include <stdio.h>
-#include <stdlib.h>
-#include "strbuf.h"
 #include "cache.h"
+#include "strbuf.h"
 
 void strbuf_init(struct strbuf *sb) {
        sb->buf = NULL;
index 89835093fbadf3c55cdf42f7490c6bac98ef2553..19e38508a70708b78de6467df71ed36373999d6a 100644 (file)
@@ -13,10 +13,6 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
 T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
 TSVN = $(wildcard t91[0-9][0-9]-*.sh)
 
-ifdef NO_PYTHON
-       GIT_TEST_OPTS += --no-python
-endif
-
 all: $(T) clean
 
 $(T):
@@ -27,12 +23,8 @@ clean:
 
 # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL
 full-svn-test:
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=1 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
-                                                       LC_ALL=en_US.UTF-8
-       $(MAKE) $(TSVN) GIT_SVN_NO_LIB=0 GIT_SVN_NO_OPTIMIZE_COMMITS=0 \
-                                                       LC_ALL=en_US.UTF-8
+       $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C
+       $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8
 
 .PHONY: $(T) clean
 .NOTPARALLEL:
index c5db5804df767bb55d5c9972b18faf4dc35e899c..36f251761739c6cda0053bbe26cc6331ed7be40c 100644 (file)
--- a/t/README
+++ b/t/README
@@ -18,7 +18,7 @@ The easiest way to run tests is to say "make".  This runs all
 the tests.
 
     *** t0000-basic.sh ***
-    *   ok 1: .git/objects should be empty after git-init-db in an empty repo.
+    *   ok 1: .git/objects should be empty after git-init in an empty repo.
     *   ok 2: .git/objects should have 256 subdirectories.
     *   ok 3: git-update-index without --add should fail adding.
     ...
@@ -74,6 +74,8 @@ First digit tells the family:
        5 - the pull and exporting commands
        6 - the revision tree commands (even e.g. merge-base)
        7 - the porcelainish commands concerning the working tree
+       8 - the porcelainish commands concerning forensics
+       9 - the git tools
 
 Second digit tells the particular command we are testing.
 
index b5ceba4acfd8e862cfc5a77672241e07205f70ea..87403da780814787f6c4ea15a790381a5b2395d3 100644 (file)
@@ -113,7 +113,8 @@ test_expect_success \
 
 test_expect_success \
     'some edit' \
-    'perl -p -i.orig -e "s/^1A.*\n$//; s/^3A/99/" file &&
+    'mv file file.orig &&
+    sed -e "s/^3A/99/" -e "/^1A/d" < file.orig > file &&
     GIT_AUTHOR_NAME="D" git commit -a -m "edit"'
 
 test_expect_success \
index 29a1e72c61eb6e67a70c12361dada49fd77fad49..67d08cf74083e1b4f045b5c4dfc510f1c3cae5c8 100644 (file)
@@ -11,21 +11,6 @@ GIT_DIR=$PWD/.git
 GIT_SVN_DIR=$GIT_DIR/svn/git-svn
 SVN_TREE=$GIT_SVN_DIR/svn-tree
 
-perl -e 'use SVN::Core' >/dev/null 2>&1
-if test $? -ne 0
-then
-   echo 'Perl SVN libraries not found, tests requiring those will be skipped'
-   GIT_SVN_NO_LIB=1
-fi
-
-svnadmin >/dev/null 2>&1
-if test $? -ne 1
-then
-    test_expect_success 'skipping git-svn tests, svnadmin not found' :
-    test_done
-    exit
-fi
-
 svn >/dev/null 2>&1
 if test $? -ne 1
 then
@@ -36,15 +21,30 @@ fi
 
 svnrepo=$PWD/svnrepo
 
-set -e
-
-if svnadmin create --help | grep fs-type >/dev/null
+perl -w -e "
+use SVN::Core;
+use SVN::Repos;
+\$SVN::Core::VERSION gt '1.1.0' or exit(42);
+system(qw/svnadmin create --fs-type fsfs/, '$svnrepo') == 0 or exit(41);
+" >&3 2>&4
+x=$?
+if test $x -ne 0
 then
-       svnadmin create --fs-type fsfs "$svnrepo"
-else
-       svnadmin create "$svnrepo"
+       if test $x -eq 42; then
+               err='Perl SVN libraries must be >= 1.1.0'
+       elif test $x -eq 41; then
+               err='svnadmin failed to create fsfs repository'
+       else
+               err='Perl SVN libraries not found or unusable, skipping test'
+       fi
+       test_expect_success "$err" :
+       test_done
+       exit
 fi
 
-svnrepo="file://$svnrepo/test-git-svn"
+svnrepo="file://$svnrepo"
 
 
+poke() {
+       perl -e '@x = stat($ARGV[0]); utime($x[8], $x[9] + 1, $ARGV[0])' "$1"
+}
index 6aff0b808cbf6ccf973070898976854b1f874b5b..186de7024336ca98bd5dcea6f675e1fc221d0ce0 100755 (executable)
@@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here.
 '
 
 ################################################################
-# It appears that people are getting bitten by not installing
-# 'merge' (usually part of RCS package in binary distributions)
-# or have too old python without subprocess.  Check them and error
-# out before running any tests.  Also catch the bogosity of trying
-# to run tests without building while we are at it.
+# It appears that people try to run tests without building...
 
 ../git >/dev/null
 if test $? != 1
@@ -32,29 +28,15 @@ then
        exit 1
 fi
 
-merge >/dev/null 2>/dev/null
-if test $? = 127
-then
-       echo >&2 'You do not seem to have "merge" installed.
-Please check INSTALL document.'
-       exit 1
-fi
-
 . ./test-lib.sh
 
-test "$no_python" || "$PYTHON" -c 'import subprocess' || {
-       echo >&2 'Your python seem to lack "subprocess" module.
-Please check INSTALL document.'
-       exit 1
-}
-
 ################################################################
-# init-db 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-db in an empty repo.' \
+    '.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.
@@ -290,4 +272,13 @@ test_expect_success \
         wc -l) &&
      test $numparent = 1'
 
+test_expect_success 'update-index D/F conflict' '
+       mv path0 tmp &&
+       mv path2 path0 &&
+       mv tmp path2 &&
+       git update-index --add --replace path2 path0/file2 &&
+       numpath0=$(git ls-files path0 | wc -l) &&
+       test $numpath0 = 1
+'
+
 test_done
index 018fbea450b3481bea2586769de7418dea929e29..c11420a8b6ce9104f1c2ca3b4b2a23aef4f265ff 100755 (executable)
@@ -8,23 +8,27 @@ test_description='read-tree -m -u checks working tree files'
 
 test_expect_success 'two-way setup' '
 
+       mkdir subdir &&
        echo >file1 file one &&
        echo >file2 file two &&
-       git update-index --add file1 file2 &&
+       echo >subdir/file1 file one in subdirectory &&
+       echo >subdir/file2 file two in subdirectory &&
+       git update-index --add file1 file2 subdir/file1 subdir/file2 &&
        git commit -m initial &&
 
        git branch side &&
        git tag -f branch-point &&
 
        echo file2 is not tracked on the master anymore &&
-       rm -f file2 &&
-       git update-index --remove file2 &&
-       git commit -a -m "master removes file2"
+       rm -f file2 subdir/file2 &&
+       git update-index --remove file2 subdir/file2 &&
+       git commit -a -m "master removes file2 and subdir/file2"
 '
 
 test_expect_success 'two-way not clobbering' '
 
        echo >file2 master creates untracked file2 &&
+       echo >subdir/file2 master creates untracked subdir/file2 &&
        if err=`git read-tree -m -u master side 2>&1`
        then
                echo should have complained
@@ -34,20 +38,82 @@ test_expect_success 'two-way not clobbering' '
        fi
 '
 
+echo file2 >.gitignore
+
+test_expect_success 'two-way with incorrect --exclude-per-directory (1)' '
+
+       if err=`git read-tree -m --exclude-per-directory=.gitignore master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+test_expect_success 'two-way with incorrect --exclude-per-directory (2)' '
+
+       if err=`git read-tree -m -u --exclude-per-directory=foo --exclude-per-directory=.gitignore master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+test_expect_success 'two-way clobbering a ignored file' '
+
+       git read-tree -m -u --exclude-per-directory=.gitignore master side
+'
+
+rm -f .gitignore
+
 # three-tree test
 
-test_expect_success 'three-way not complaining' '
+test_expect_success 'three-way not complaining on an untracked path in both' '
 
-       rm -f file2 &&
+       rm -f file2 subdir/file2 &&
        git checkout side &&
        echo >file3 file three &&
-       git update-index --add file3 &&
-       git commit -a -m "side adds file3" &&
+       echo >subdir/file3 file three &&
+       git update-index --add file3 subdir/file3 &&
+       git commit -a -m "side adds file3 and removes file2" &&
 
        git checkout master &&
        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
 '
 
+test_expect_success 'three-way not clobbering a working tree file' '
+
+       git reset --hard &&
+       rm -f file2 subdir/file2 file3 subdir/file3 &&
+       git checkout master &&
+       echo >file3 file three created in master, untracked &&
+       echo >subdir/file3 file three created in master, untracked &&
+       if err=`git read-tree -m -u branch-point master side 2>&1`
+       then
+               echo should have complained
+               false
+       else
+               echo "happy to see $err"
+       fi
+'
+
+echo >.gitignore file3
+
+test_expect_success 'three-way not complaining on an untracked file' '
+
+       git reset --hard &&
+       rm -f file2 subdir/file2 file3 subdir/file3 &&
+       git checkout master &&
+       echo >file3 file three created in master, untracked &&
+       echo >subdir/file3 file three created in master, untracked &&
+
+       git read-tree -m -u --exclude-per-directory=.gitignore branch-point master side
+'
+
 test_done
index 4409b87f8d9bf151883c1888ddc64c63b24d85c9..1e8f9e59dfe7e63177dea0a2e4b5c018a4756994 100755 (executable)
@@ -106,4 +106,33 @@ test_expect_success 'read-tree' '
        cmp ../one ../original.one
 '
 
+test_expect_success 'no file/rev ambiguity check inside .git' '
+       cd $HERE &&
+       git commit -a -m 1 &&
+       cd $HERE/.git &&
+       git show -s HEAD
+'
+
+test_expect_success 'no file/rev ambiguity check inside a bare repo' '
+       cd $HERE &&
+       git clone -s --bare .git foo.git &&
+       cd foo.git && GIT_DIR=. git show -s HEAD
+'
+
+# This still does not work as it should...
+: test_expect_success 'no file/rev ambiguity check inside a bare repo' '
+       cd $HERE &&
+       git clone -s --bare .git foo.git &&
+       cd foo.git && git show -s HEAD
+'
+
+test_expect_success 'detection should not be fooled by a symlink' '
+       cd $HERE &&
+       rm -fr foo.git &&
+       git clone -s .git another &&
+       ln -s another yetanother &&
+       cd yetanother/.git &&
+       git show -s HEAD
+'
+
 test_done
index 0272dd429302534846e3a7f221a520bac2907ca8..ca2c30f7aff5beda97ea598e272750c23e34296e 100755 (executable)
@@ -37,8 +37,6 @@ test_expect_success 'tree' "test 8988da15d077d4829fc51d8544c097def6644dbb = $tre
 
 output="$(echo "Initial commit" | git-commit-tree $(git-write-tree) 2>&1 > .git/refs/heads/master)"
 
-test_expect_success 'commit' "test 'Committing initial tree 8988da15d077d4829fc51d8544c097def6644dbb' = \"$output\""
-
 git-diff-index -p HEAD > diff.output
 test_expect_success 'git-diff-index -p HEAD' 'cmp diff.expect diff.output'
 
@@ -103,7 +101,9 @@ echo "Play, play, play" >>hello
 echo "Lots of fun" >>example
 git commit -m 'Some fun.' -i hello example
 
-test_expect_failure 'git resolve now fails' 'git resolve HEAD mybranch "Merge work in mybranch"'
+test_expect_failure 'git resolve now fails' '
+       git merge -m "Merge work in mybranch" mybranch
+'
 
 cat > hello << EOF
 Hello World
@@ -136,8 +136,8 @@ Updating from VARIABLE to VARIABLE
  2 files changed, 2 insertions(+), 0 deletions(-)
 EOF
 
-git resolve HEAD master "Merge upstream changes." | \
-       sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" > resolve.output
+git merge -s "Merge upstream changes." master | \
+       sed -e "1s/[0-9a-f]\{40\}/VARIABLE/g" >resolve.output
 test_expect_success 'git resolve' 'cmp resolve.expect resolve.output'
 
 cat > show-branch2.expect << EOF
index 0de2497746e31f3dce418dbd248ff5d3e7cb939a..49b5666b33283feaf8ea2b7c09faeab8016c746d 100755 (executable)
@@ -3,13 +3,13 @@
 # Copyright (c) 2005 Johannes Schindelin
 #
 
-test_description='Test git-repo-config in different settings'
+test_description='Test git-config in different settings'
 
 . ./test-lib.sh
 
 test -f .git/config && rm .git/config
 
-git-repo-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-repo-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-repo-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-repo-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-repo-config core.penguin kingpin !blue'
+       'git-config core.penguin kingpin !blue'
 
 test_expect_success 'replace with non-match (actually matching)' \
-       'git-repo-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-repo-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-repo-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-repo-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-repo-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-repo-config beta.haha)'
-git-repo-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-repo-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-repo-config --get nextsection.nonewline !for'
+       'git-config --get nextsection.nonewline !for'
 
 test_expect_success 'non-match value' \
-       'test wow = $(git-repo-config --get nextsection.nonewline !for)'
+       'test wow = $(git-config --get nextsection.nonewline !for)'
 
 test_expect_failure 'ambiguous get' \
-       'git-repo-config --get nextsection.nonewline'
+       'git-config --get nextsection.nonewline'
 
 test_expect_success 'get multivar' \
-       'git-repo-config --get-all nextsection.nonewline'
+       'git-config --get-all nextsection.nonewline'
 
-git-repo-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-repo-config nextsection.nonewline'
+test_expect_failure 'ambiguous value' 'git-config nextsection.nonewline'
 
 test_expect_failure 'ambiguous unset' \
-       'git-repo-config --unset nextsection.nonewline'
+       'git-config --unset nextsection.nonewline'
 
 test_expect_failure 'invalid unset' \
-       'git-repo-config --unset somesection.nonewline'
+       'git-config --unset somesection.nonewline'
 
-git-repo-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-repo-config inval.2key blabla'
+test_expect_failure 'invalid key' 'git-config inval.2key blabla'
 
-test_expect_success 'correct key' 'git-repo-config 123456.a123 987'
+test_expect_success 'correct key' 'git-config 123456.a123 987'
 
 test_expect_success 'hierarchical section' \
-       'git-repo-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-repo-config --list > output && cmp output expect'
+       'git-config --list > output && cmp output expect'
 
 cat > expect << EOF
 beta.noindent sillyValue
@@ -263,7 +263,17 @@ nextsection.nonewline wow2 for me
 EOF
 
 test_expect_success '--get-regexp' \
-       'git-repo-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"
+
+cat > expect << EOF
+wow2 for me
+wow4 for you
+EOF
+
+test_expect_success '--add' \
+       'git-config --get-all nextsection.nonewline > output && cmp output expect'
 
 cat > .git/config << EOF
 [novalue]
@@ -271,9 +281,9 @@ cat > .git/config << EOF
 EOF
 
 test_expect_success 'get variable with no value' \
-       'git-repo-config --get novalue.variable ^$'
+       'git-config --get novalue.variable ^$'
 
-git-repo-config > output 2>&1
+git-config > output 2>&1
 
 test_expect_success 'no arguments, but no crash' \
        "test $? = 129 && grep usage output"
@@ -283,7 +293,7 @@ cat > .git/config << EOF
        c = d
 EOF
 
-git-repo-config a.x y
+git-config a.x y
 
 cat > expect << EOF
 [a.b]
@@ -294,8 +304,8 @@ EOF
 
 test_expect_success 'new section is partial match of another' 'cmp .git/config expect'
 
-git-repo-config b.x y
-git-repo-config a.b c
+git-config b.x y
+git-config a.b c
 
 cat > expect << EOF
 [a.b]
@@ -318,11 +328,11 @@ cat > expect << EOF
 ein.bahn=strasse
 EOF
 
-GIT_CONFIG=other-config git-repo-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-repo-config anwohner.park ausweis
+GIT_CONFIG=other-config git-config anwohner.park ausweis
 
 cat > expect << EOF
 [ein]
@@ -333,5 +343,106 @@ EOF
 
 test_expect_success '--set in alternative GIT_CONFIG' 'cmp other-config expect'
 
+cat > .git/config << EOF
+# Hallo
+       #Bello
+[branch "eins"]
+       x = 1
+[branch.eins]
+       y = 1
+       [branch "1 234 blabl/a"]
+weird
+EOF
+
+test_expect_success "rename section" \
+       "git-config --rename-section branch.eins branch.zwei"
+
+cat > expect << EOF
+# Hallo
+       #Bello
+[branch "zwei"]
+       x = 1
+[branch "zwei"]
+       y = 1
+       [branch "1 234 blabl/a"]
+weird
+EOF
+
+test_expect_success "rename succeeded" "diff -u expect .git/config"
+
+test_expect_failure "rename non-existing section" \
+       'git-config --rename-section branch."world domination" branch.drei'
+
+test_expect_success "rename succeeded" "diff -u expect .git/config"
+
+test_expect_success "rename another section" \
+       'git-config --rename-section branch."1 234 blabl/a" branch.drei'
+
+cat > expect << EOF
+# Hallo
+       #Bello
+[branch "zwei"]
+       x = 1
+[branch "zwei"]
+       y = 1
+[branch "drei"]
+weird
+EOF
+
+test_expect_success "rename succeeded" "diff -u expect .git/config"
+
+test_expect_success numbers '
+
+       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) &&
+       test z1048576 = "z$m"
+'
+
+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"
+
+cat > expect << EOF
+[quote]
+       leading = " test"
+       ending = "test "
+       semicolon = "test;test"
+       hash = "test#test"
+EOF
+
+test_expect_success 'quoting' 'cmp .git/config expect'
+
+test_expect_failure 'key with newline' 'git config key.with\\\
+newline 123'
+
+test_expect_success 'value with newline' 'git config key.sub value.with\\\
+newline'
+
+cat > .git/config <<\EOF
+[section]
+       ; comment \
+       continued = cont\
+inued
+       noncont   = not continued ; \
+       quotecont = "cont;\
+inued"
+EOF
+
+cat > expect <<\EOF
+section.continued=continued
+section.noncont=not continued
+section.quotecont=cont;inued
+EOF
+
+git config --list > result
+
+test_expect_success 'value continued on next line' 'cmp result expect'
+
 test_done
 
index 6a917f2ff48661a5d70fdcbcc700cbf659004818..d0aba2c2ae1fbd5f5282de1a0ee7ed8b87b80faa 100755 (executable)
@@ -63,8 +63,8 @@ test_expect_failure \
        "test $B"' = $(cat .git/'"$m"')'
 rm -f .git/$m
 
-mkdir -p .git/logs/refs/heads
-touch .git/logs/refs/heads/master
+: a repository with working tree always has reflog these days...
+: >.git/logs/refs/heads/master
 test_expect_success \
        "create $m (logged by touch)" \
        'GIT_COMMITTER_DATE="2005-05-26 23:30" \
@@ -93,8 +93,8 @@ rm -rf .git/$m .git/logs expect
 
 test_expect_success \
        'enable core.logAllRefUpdates' \
-       'git-repo-config core.logAllRefUpdates true &&
-        test true = $(git-repo-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)" \
@@ -138,19 +138,19 @@ test_expect_success \
        'rm -f o e
         git-rev-parse --verify "master@{May 25 2005}" >o 2>e &&
         test '"$C"' = $(cat o) &&
-        test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
+        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 &&
         test '"$C"' = $(cat o) &&
-        echo test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
+        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 &&
         test '"$C"' = $(cat o) &&
-        test "warning: Log .git/logs/'"$m only goes back to $ed"'." = "$(cat e)"'
+        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
diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh
new file mode 100755 (executable)
index 0000000..e5bbc38
--- /dev/null
@@ -0,0 +1,178 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Junio C Hamano
+#
+
+test_description='Test prune and reflog expiration'
+. ./test-lib.sh
+
+check_have () {
+       gaah= &&
+       for N in "$@"
+       do
+               eval "o=\$$N" && git cat-file -t $o || {
+                       echo Gaah $N
+                       gaah=$N
+                       break
+               }
+       done &&
+       test -z "$gaah"
+}
+
+check_fsck () {
+       output=$(git fsck --full)
+       case "$1" in
+       '')
+               test -z "$output" ;;
+       *)
+               echo "$output" | grep "$1" ;;
+       esac
+}
+
+corrupt () {
+       aa=${1%??????????????????????????????????????} zz=${1#??}
+       mv .git/objects/$aa/$zz .git/$aa$zz
+}
+
+recover () {
+       aa=${1%??????????????????????????????????????} zz=${1#??}
+       mkdir -p .git/objects/$aa
+       mv .git/$aa$zz .git/objects/$aa/$zz
+}
+
+check_dont_have () {
+       gaah= &&
+       for N in "$@"
+       do
+               eval "o=\$$N"
+               git cat-file -t $o && {
+                       echo Gaah $N
+                       gaah=$N
+                       break
+               }
+       done
+       test -z "$gaah"
+}
+
+test_expect_success setup '
+       mkdir -p A/B &&
+       echo rat >C &&
+       echo ox >A/D &&
+       echo tiger >A/B/E &&
+       git add . &&
+
+       test_tick && git commit -m rabbit &&
+       H=`git rev-parse --verify HEAD` &&
+       A=`git rev-parse --verify HEAD:A` &&
+       B=`git rev-parse --verify HEAD:A/B` &&
+       C=`git rev-parse --verify HEAD:C` &&
+       D=`git rev-parse --verify HEAD:A/D` &&
+       E=`git rev-parse --verify HEAD:A/B/E` &&
+       check_fsck &&
+
+       chmod +x C &&
+       ( test "`git config --bool core.filemode`" != false ||
+         echo executable >>C ) &&
+       git add C &&
+       test_tick && git commit -m dragon &&
+       L=`git rev-parse --verify HEAD` &&
+       check_fsck &&
+
+       rm -f C A/B/E &&
+       echo snake >F &&
+       echo horse >A/G &&
+       git add F A/G &&
+       test_tick && git commit -a -m sheep &&
+       F=`git rev-parse --verify HEAD:F` &&
+       G=`git rev-parse --verify HEAD:A/G` &&
+       I=`git rev-parse --verify HEAD:A` &&
+       J=`git rev-parse --verify HEAD` &&
+       check_fsck &&
+
+       rm -f A/G &&
+       test_tick && git commit -a -m monkey &&
+       K=`git rev-parse --verify HEAD` &&
+       check_fsck &&
+
+       check_have A B C D E F G H I J K L &&
+
+       git prune &&
+
+       check_have A B C D E F G H I J K L &&
+
+       check_fsck &&
+
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 4
+'
+
+test_expect_success rewind '
+       test_tick && git reset --hard HEAD~2 &&
+       test -f C &&
+       test -f A/B/E &&
+       ! test -f F &&
+       ! test -f A/G &&
+
+       check_have A B C D E F G H I J K L &&
+
+       git prune &&
+
+       check_have A B C D E F G H I J K L &&
+
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 5
+'
+
+test_expect_success 'corrupt and check' '
+
+       corrupt $F &&
+       check_fsck "missing blob $F"
+
+'
+
+test_expect_success 'reflog expire --dry-run should not touch reflog' '
+
+       git reflog expire --dry-run \
+               --expire=$(($test_tick - 10000)) \
+               --expire-unreachable=$(($test_tick - 10000)) \
+               --stale-fix \
+               --all &&
+
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 5 &&
+
+       check_fsck "missing blob $F"
+'
+
+test_expect_success 'reflog expire' '
+
+       git reflog expire --verbose \
+               --expire=$(($test_tick - 10000)) \
+               --expire-unreachable=$(($test_tick - 10000)) \
+               --stale-fix \
+               --all &&
+
+       loglen=$(wc -l <.git/logs/refs/heads/master) &&
+       test $loglen = 2 &&
+
+       check_fsck "dangling commit $K"
+'
+
+test_expect_success 'prune and fsck' '
+
+       git prune &&
+       check_fsck &&
+
+       check_have A B C D E H L &&
+       check_dont_have F G I J K
+
+'
+
+test_expect_success 'recover and check' '
+
+       recover $F &&
+       check_fsck "dangling blob $F"
+
+'
+
+test_done
index acb54b6a079b2e790c3bf6eb58007188af7ef1a1..5565c27033ab89959f2a39dfa9abaea99e40432b 100755 (executable)
@@ -48,7 +48,7 @@ test_expect_success \
         test ! -f .git/logs/refs/heads/d/e/f'
 
 cat >expect <<EOF
-0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     checkout: Created from master^0
+0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     checkout: Created from master
 EOF
 test_expect_success \
     'git checkout -b g/h/i -l should create a branch and a log' \
@@ -70,4 +70,51 @@ test_expect_success \
         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 &&
+        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
+        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'
+
+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-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 &&
+        test -f .git/logs/refs/heads/s/s &&
+        git-branch -l s/t &&
+        test -f .git/logs/refs/heads/s/t &&
+        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_expect_failure \
+    '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'
+
 test_done
index b1e9f2eed22b9938a6dbba89a98f6dcc9f2dded1..f0c7e22b36c66234e2a46bac659506afb454dfa7 100755 (executable)
@@ -34,7 +34,7 @@ test_expect_success \
     'see if a branch still exists when packed' \
     'git-branch b &&
      git-pack-refs --all &&
-     rm .git/refs/heads/b &&
+     rm -f .git/refs/heads/b &&
      echo "$SHA1 refs/heads/b" >expect &&
      git-show-ref b >result &&
      diff expect result'
@@ -96,4 +96,13 @@ test_expect_success \
      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 &&
+       diff all-of-them again
+'
+
 test_done
diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh
new file mode 100755 (executable)
index 0000000..552af1c
--- /dev/null
@@ -0,0 +1,62 @@
+#!/bin/sh
+
+test_description='test cherry-pick and revert with renames
+
+  --
+   + rename2: renames oops to opos
+  +  rename1: renames oops to spoo
+  +  added:   adds extra line to oops
+  ++ initial: has lines in oops
+
+'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+       for l in a b c d e f g h i j k l m n o
+       do
+               echo $l$l$l$l$l$l$l$l$l
+       done >oops &&
+
+       test_tick &&
+       git add oops &&
+       git commit -m initial &&
+       git tag initial &&
+
+       test_tick &&
+       echo "Add extra line at the end" >>oops &&
+       git commit -a -m added &&
+       git tag added &&
+
+       test_tick &&
+       git mv oops spoo &&
+       git commit -m rename1 &&
+       git tag rename1 &&
+
+       test_tick &&
+       git checkout -b side initial &&
+       git mv oops opos &&
+       git commit -m rename2 &&
+       git tag rename2
+'
+
+test_expect_success 'cherry-pick after renaming branch' '
+
+       git checkout rename2 &&
+       EDITOR=: VISUAL=: git cherry-pick added &&
+       test -f opos &&
+       grep "Add extra line at the end" opos
+
+'
+
+test_expect_success 'revert after renaming branch' '
+
+       git checkout rename1 &&
+       EDITOR=: VISUAL=: git revert added &&
+       test -f spoo &&
+       ! grep "Add extra line at the end" spoo
+
+'
+
+test_done
index 201d1642da672492d9975df3ee3c78d7011e4321..e31cf93a00ab377355734b3d88d536d36fe734e1 100755 (executable)
@@ -43,19 +43,19 @@ test_expect_success \
 
 test_expect_success \
     'Test that git-rm foo succeeds' \
-    'git-rm foo'
+    '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_expect_success \
-    'Pre-check that bar exists and is in index before "git-rm -f bar"' \
+    '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 -f bar" succeeds' \
-    'git-rm -f 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"' \
@@ -84,4 +84,74 @@ 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'
 
+# Now, failure cases.
+test_expect_success 'Re-add foo and baz' '
+       git add foo baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'Modify foo -- rm should refuse' '
+       echo >>foo &&
+       ! git rm foo baz &&
+       test -f foo &&
+       test -f baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'Modified foo -- rm -f should work' '
+       git rm -f foo baz &&
+       test ! -f foo &&
+       test ! -f baz &&
+       ! git ls-files --error-unmatch foo &&
+       ! git ls-files --error-unmatch bar
+'
+
+test_expect_success 'Re-add foo and baz for HEAD tests' '
+       echo frotz >foo &&
+       git checkout HEAD -- baz &&
+       git add foo baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'foo is different in index from HEAD -- rm should refuse' '
+       ! git rm foo baz &&
+       test -f foo &&
+       test -f baz &&
+       git ls-files --error-unmatch foo baz
+'
+
+test_expect_success 'but with -f it should work.' '
+       git rm -f foo baz &&
+       test ! -f foo &&
+       test ! -f baz &&
+       ! git ls-files --error-unmatch foo
+       ! git ls-files --error-unmatch baz
+'
+
+test_expect_success 'Recursive test setup' '
+       mkdir -p frotz &&
+       echo qfwfq >frotz/nitfol &&
+       git add frotz &&
+       git commit -m "subdir test"
+'
+
+test_expect_success 'Recursive without -r fails' '
+       ! git rm frotz &&
+       test -d frotz &&
+       test -f frotz/nitfol
+'
+
+test_expect_success 'Recursive with -r but dirty' '
+       echo qfwfq >>frotz/nitfol
+       ! git rm -r frotz &&
+       test -d frotz &&
+       test -f frotz/nitfol
+'
+
+test_expect_success 'Recursive with -r -f' '
+       git rm -f -r frotz &&
+       ! test -f frotz/nitfol &&
+       ! test -d frotz
+'
+
 test_done
index c20e4c29fcc864dff80e5ae73a8ad7ede1f794f0..caaab26c2f3f2d04581a6c6918ab36f97a51f975 100755 (executable)
@@ -21,24 +21,67 @@ test_expect_success \
 
 test_expect_success \
        'git-add: Test that executable bit is not used if core.filemode=0' \
-       'git repo-config core.filemode 0 &&
+       'git config core.filemode 0 &&
         echo foo >xfoo1 &&
         chmod 755 xfoo1 &&
         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-update-index --add: Test that executable bit is not used...' \
-       'git repo-config core.filemode 0 &&
+       'git config core.filemode 0 &&
         echo foo >xfoo2 &&
         chmod 755 xfoo2 &&
         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-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
+        120000" "*xfoo3) echo ok;;
+        *) echo fail; git-ls-files --stage xfoo3; (exit 1);;
+        esac'
+
+test_expect_success '.gitignore test setup' '
+       echo "*.ig" >.gitignore &&
+       mkdir c.if d.ig &&
+       >a.ig && >b.if &&
+       >c.if/c.if && >c.if/c.ig &&
+       >d.ig/d.if && >d.ig/d.ig
+'
+
+test_expect_success '.gitignore is honored' '
+       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"
+'
+
+test_expect_success 'error out when attempting to add ignored ones without -f' '
+       ! 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
+'
+
+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
+'
+
 test_done
index 5b23b7769d8d178482bb7a08e27efd9e02d425a2..7c7e4335d6e941a79d85c11502178b36331785a2 100755 (executable)
@@ -88,7 +88,7 @@ check_verify_failure '"type" line label check'
 #  5. type line eol check
 
 echo "object 779e9b33986b1c2670fff52c5067603117b3e895" >tag.sig
-echo -n "type tagsssssssssssssssssssssssssssssss" >>tag.sig
+printf "type tagsssssssssssssssssssssssssssssss" >>tag.sig
 
 cat >expect.pat <<EOF
 ^error: char48: .*"[\]n"$
@@ -172,7 +172,7 @@ EOF
 check_verify_failure 'verify tag-name check'
 
 ############################################################
-# 11. tagger line lable check #1
+# 11. tagger line label check #1
 
 cat >tag.sig <<EOF
 object $head
@@ -187,7 +187,7 @@ EOF
 check_verify_failure '"tagger" line label check #1'
 
 ############################################################
-# 12. tagger line lable check #2
+# 12. tagger line label check #2
 
 cat >tag.sig <<EOF
 object $head
diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh
new file mode 100755 (executable)
index 0000000..e54fe0f
--- /dev/null
@@ -0,0 +1,122 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+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 &&
+       diff -u 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-tag C0
+'
+
+test_expect_success 'no encoding header for base case' '
+       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-checkout -b $H C0 &&
+               echo $H >F &&
+               git-commit -a -F ../t3900/$H.txt
+       '
+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") &&
+               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)
+       then
+               echo Oops, should have failed.
+               false
+       else
+               test z = "z$Z"
+       fi &&
+       git-config i18n.commitencoding utf-8
+'
+
+test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
+       compare_with ISO-8859-1 ../t3900/1-UTF-8.txt
+'
+
+for H in EUCJP ISO-2022-JP
+do
+       test_expect_success "$H should be shown in UTF-8 now" '
+               compare_with '$H' ../t3900/2-UTF-8.txt
+       '
+done
+
+test_expect_success 'config to add customization' '
+       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
+'
+
+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' &&
+               compare_with '$H' ../t3900/'$H'.txt
+       '
+done
+
+test_expect_success 'config to tweak customization' '
+       git-config i18n.logoutputencoding utf-8
+'
+
+test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' '
+       compare_with ISO-8859-1 ../t3900/1-UTF-8.txt
+'
+
+for H in EUCJP ISO-2022-JP
+do
+       test_expect_success "$H should be shown in UTF-8 now" '
+               compare_with '$H' ../t3900/2-UTF-8.txt
+       '
+done
+
+for J in EUCJP ISO-2022-JP
+do
+       git-config i18n.logoutputencoding $J
+       for H in EUCJP ISO-2022-JP
+       do
+               test_expect_success "$H should be shown in $J now" '
+                       compare_with '$H' ../t3900/'$J'.txt
+               '
+       done
+done
+
+for H in ISO-8859-1 EUCJP ISO-2022-JP
+do
+       test_expect_success "No conversion with $H" '
+               compare_with "--encoding=none '$H'" ../t3900/'$H'.txt
+       '
+done
+
+test_done
diff --git a/t/t3900/1-UTF-8.txt b/t/t3900/1-UTF-8.txt
new file mode 100644 (file)
index 0000000..ee31e19
--- /dev/null
@@ -0,0 +1,3 @@
+ÄËÑÏÖ
+
+Ábçdèfg
diff --git a/t/t3900/2-UTF-8.txt b/t/t3900/2-UTF-8.txt
new file mode 100644 (file)
index 0000000..63f4f8f
--- /dev/null
@@ -0,0 +1,4 @@
+はれひほふ
+
+しているのが、いるので。
+濱浜ほれぷりぽれまびぐりろへ。
diff --git a/t/t3900/EUCJP.txt b/t/t3900/EUCJP.txt
new file mode 100644 (file)
index 0000000..546f2aa
--- /dev/null
@@ -0,0 +1,4 @@
+¤Ï¤ì¤Ò¤Û¤Õ
+
+¤·¤Æ¤¤¤ë¤Î¤¬¡¢¤¤¤ë¤Î¤Ç¡£
+ßÀÉͤۤì¤×¤ê¤Ý¤ì¤Þ¤Ó¤°¤ê¤í¤Ø¡£
diff --git a/t/t3900/ISO-2022-JP.txt b/t/t3900/ISO-2022-JP.txt
new file mode 100644 (file)
index 0000000..74b5330
--- /dev/null
@@ -0,0 +1,4 @@
+\e$B$O$l$R$[$U\e(B
+
+\e$B$7$F$$$k$N$,!"$$$k$N$G!#\e(B
+\e$B_@IM$[$l$W$j$]$l$^$S$0$j$m$X!#\e(B
diff --git a/t/t3900/ISO-8859-1.txt b/t/t3900/ISO-8859-1.txt
new file mode 100644 (file)
index 0000000..7cbef0e
--- /dev/null
@@ -0,0 +1,3 @@
+ÄËÑÏÖ
+
+Ábçdèfg
diff --git a/t/t3901-8859-1.txt b/t/t3901-8859-1.txt
new file mode 100755 (executable)
index 0000000..38c21a6
--- /dev/null
@@ -0,0 +1,4 @@
+: to be sourced in t3901 -- this is latin-1
+GIT_AUTHOR_NAME="Áéí óú" &&
+GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME &&
+export GIT_AUTHOR_NAME GIT_COMMITTER_NAME
diff --git a/t/t3901-i18n-patch.sh b/t/t3901-i18n-patch.sh
new file mode 100755 (executable)
index 0000000..a881797
--- /dev/null
@@ -0,0 +1,255 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Junio C Hamano
+#
+
+test_description='i18n settings and format-patch | am pipe'
+
+. ./test-lib.sh
+
+check_encoding () {
+       # Make sure characters are not corrupted
+       cnt="$1" header="$2" i=1 j=0 bad=0
+       while test "$i" -le $cnt
+       do
+               git format-patch --encoding=UTF-8 --stdout HEAD~$i..HEAD~$j |
+               grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" &&
+               git-cat-file commit HEAD~$j |
+               case "$header" in
+               8859)
+                       grep "^encoding ISO-8859-1" ;;
+               *)
+                       ! grep "^encoding ISO-8859-1" ;;
+               esac || {
+                       bad=1
+                       break
+               }
+               j=$i
+               i=$(($i+1))
+       done
+       (exit $bad)
+}
+
+test_expect_success setup '
+       git-config i18n.commitencoding UTF-8 &&
+
+       # use UTF-8 in author and committer name to match the
+       # i18n.commitencoding settings
+       . ../t3901-utf8.txt &&
+
+       test_tick &&
+       echo "$GIT_AUTHOR_NAME" >mine &&
+       git add mine &&
+       git commit -s -m "Initial commit" &&
+
+       test_tick &&
+       echo Hello world >mine &&
+       git add mine &&
+       git commit -s -m "Second on main" &&
+
+       # the first commit on the side branch is UTF-8
+       test_tick &&
+       git checkout -b side master^ &&
+       echo Another file >yours &&
+       git add yours &&
+       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 &&
+       # use author and committer name in ISO-8859-1 to match it.
+       . ../t3901-8859-1.txt &&
+       test_tick &&
+       echo Yet another >theirs &&
+       git add theirs &&
+       git commit -s -m "Third on side" &&
+
+       # Back to default
+       git-config i18n.commitencoding UTF-8
+'
+
+test_expect_success 'format-patch output (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 &&
+       grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l1 &&
+       grep "^From: =?ISO-8859-1?q?=C1=E9=ED_=F3=FA?=" out-l1 &&
+       grep "^Content-Type: text/plain; charset=ISO-8859-1" out-l2 &&
+       grep "^From: =?ISO-8859-1?q?=C1=E9=ED_=F3=FA?=" out-l2
+'
+
+test_expect_success 'format-patch output (UTF-8)' '
+       git config i18n.logoutputencoding UTF-8 &&
+
+       git format-patch --stdout master..HEAD^ >out-u1 &&
+       git format-patch --stdout HEAD^ >out-u2 &&
+       grep "^Content-Type: text/plain; charset=UTF-8" out-u1 &&
+       grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-u1 &&
+       grep "^Content-Type: text/plain; charset=UTF-8" out-u2 &&
+       grep "^From: =?UTF-8?q?=C3=81=C3=A9=C3=AD_=C3=B3=C3=BA?=" out-u2
+'
+
+test_expect_success 'rebase (U/U)' '
+       # We want the result of rebase in 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
+       # patch and the log.
+
+       git config i18n.logoutputencoding UTF-8 &&
+
+       # The result will be committed by GIT_COMMITTER_NAME --
+       # we want UTF-8 encoded name.
+       . ../t3901-utf8.txt &&
+       git checkout -b test &&
+       git-rebase master &&
+
+       check_encoding 2
+'
+
+test_expect_success 'rebase (U/L)' '
+       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.logoutputencoding ISO-8859-1 &&
+       . ../t3901-utf8.txt &&
+
+       git reset --hard side &&
+       git-rebase master &&
+
+       check_encoding 2
+'
+
+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.logoutputencoding ISO-8859-1 &&
+       . ../t3901-8859-1.txt &&
+
+       git reset --hard side &&
+       git-rebase master &&
+
+       check_encoding 2 8859
+'
+
+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.logoutputencoding UTF-8 &&
+       . ../t3901-8859-1.txt &&
+
+       git reset --hard side &&
+       git-rebase master &&
+
+       check_encoding 2 8859
+'
+
+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.logoutputencoding UTF-8 &&
+       . ../t3901-utf8.txt &&
+
+       git reset --hard master &&
+       git cherry-pick side^ &&
+       git cherry-pick side &&
+       EDITOR=: VISUAL=: git revert HEAD &&
+
+       check_encoding 3
+'
+
+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.logoutputencoding ISO-8859-1 &&
+       . ../t3901-8859-1.txt &&
+
+       git reset --hard master &&
+       git cherry-pick side^ &&
+       git cherry-pick side &&
+       EDITOR=: VISUAL=: git revert HEAD &&
+
+       check_encoding 3 8859
+'
+
+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.logoutputencoding ISO-8859-1 &&
+       . ../t3901-utf8.txt &&
+
+       git reset --hard master &&
+       git cherry-pick side^ &&
+       git cherry-pick side &&
+       EDITOR=: VISUAL=: git revert HEAD &&
+
+       check_encoding 3
+'
+
+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.logoutputencoding UTF-8 &&
+       . ../t3901-8859-1.txt &&
+
+       git reset --hard master &&
+       git cherry-pick side^ &&
+       git cherry-pick side &&
+       EDITOR=: VISUAL=: git revert HEAD &&
+
+       check_encoding 3 8859
+'
+
+test_expect_success 'rebase --merge (U/U)' '
+       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.logoutputencoding UTF-8 &&
+       . ../t3901-utf8.txt &&
+
+       git reset --hard side &&
+       git-rebase --merge master &&
+
+       check_encoding 2
+'
+
+test_expect_success 'rebase --merge (U/L)' '
+       git-config i18n.commitencoding UTF-8 &&
+       git config i18n.logoutputencoding ISO-8859-1 &&
+       . ../t3901-utf8.txt &&
+
+       git reset --hard side &&
+       git-rebase --merge master &&
+
+       check_encoding 2
+'
+
+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.logoutputencoding ISO-8859-1 &&
+       . ../t3901-8859-1.txt &&
+
+       git reset --hard side &&
+       git-rebase --merge master &&
+
+       check_encoding 2 8859
+'
+
+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.logoutputencoding UTF-8 &&
+       . ../t3901-8859-1.txt &&
+
+       git reset --hard side &&
+       git-rebase --merge master &&
+
+       check_encoding 2 8859
+'
+
+test_done
diff --git a/t/t3901-utf8.txt b/t/t3901-utf8.txt
new file mode 100755 (executable)
index 0000000..5f5205c
--- /dev/null
@@ -0,0 +1,4 @@
+: to be sourced in t3901 -- this is utf8
+GIT_AUTHOR_NAME="Áéí óú" &&
+GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME &&
+export GIT_AUTHOR_NAME GIT_COMMITTER_NAME
index 67b9681d36e187a5717acbf9fe00a7f5dfd5bb65..9c58d77cc22a062e571116e2dee2599ac13cb464 100755 (executable)
@@ -28,7 +28,7 @@ test_expect_success \
     'git-diff-files -p >current'
 
 # that's as far as it comes
-if [ "$(git repo-config --get core.filemode)" = false ]
+if [ "$(git config --get core.filemode)" = false ]
 then
        say 'filemode disabled on the filesystem'
        test_done
index 8ad69d11155f3a3df9a5508a07ccd78ba7e5d747..ca342f48a198c2e98e4459273e8350bb7aed6328 100755 (executable)
@@ -15,7 +15,7 @@ test_expect_success \
      tree=`git-write-tree` &&
      echo $tree'
 
-if [ "$(git repo-config --get core.filemode)" = false ]
+if [ "$(git config --get core.filemode)" = false ]
 then
        say 'filemode disabled on the filesystem, using update-index --chmod=+x'
        test_expect_success \
index 71c454356fbbcd5f67bdfc0b6ef7785fa8b712c1..3d85ceaae9e695d7b03d09cb082f1eef4f8406f2 100755 (executable)
@@ -73,6 +73,7 @@ test_expect_success setup '
        for i in 1 2; do echo $i; done >>dir/sub &&
        git update-index file0 dir/sub &&
 
+       git config log.showroot false &&
        git commit --amend &&
        git show-branch
 '
index b4745e10010c1cbaa708b3f6bc49e3e97e051d90..e5ddd6fcbb7611e86f19b36cf04dec9d23d74fdc 100644 (file)
@@ -19,6 +19,7 @@ This is the second commit.
  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"
@@ -77,6 +78,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file1   |    3 +++
  2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
 --------------g-i-t--v-e-r-s-i-o-n
 Content-Type: text/x-patch;
  name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
@@ -126,6 +128,7 @@ Content-Transfer-Encoding: 8bit
  file0   |    3 +++
  file3   |    4 ++++
  3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
 --------------g-i-t--v-e-r-s-i-o-n
 Content-Type: text/x-patch;
  name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
index a9d1cd368bf10738a35b6cc61c5b215186b9066b..d0dd19b6239f7bae2087535566a7a7c626b70cce 100644 (file)
@@ -19,6 +19,7 @@ This is the second commit.
  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"
@@ -77,6 +78,7 @@ Content-Transfer-Encoding: 8bit
  dir/sub |    2 ++
  file1   |    3 +++
  2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
 --------------g-i-t--v-e-r-s-i-o-n
 Content-Type: text/x-patch;
  name="9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0.diff"
index 57b9d0bdc1abc39d27baf4b71ead6b1654c8a2fa..67a95c5cba8fd256d9e57d7d254b2e9bd58a227e 100644 (file)
@@ -17,6 +17,7 @@ Content-Transfer-Encoding: 8bit
  file0   |    3 +++
  file3   |    4 ++++
  3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
 --------------g-i-t--v-e-r-s-i-o-n
 Content-Type: text/x-patch;
  name="c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a.diff"
index c33302e92f694810eaa1866a1e0174d1526ec91b..8b88ca49276695aead0083ad49c53715874e11dc 100644 (file)
@@ -10,6 +10,7 @@ This is the second commit.
  file0   |    3 +++
  file2   |    3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -53,6 +54,7 @@ Subject: [PATCH] Third
  dir/sub |    2 ++
  file1   |    3 +++
  2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
@@ -87,6 +89,7 @@ Subject: [PATCH] Side
  file0   |    3 +++
  file3   |    4 ++++
  3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 03d0f9693c08167c5f8ca5d84a69c55ee134cd96..47a4b88637d4dc8650f0480278ae67b907eb5080 100644 (file)
@@ -10,6 +10,7 @@ This is the second commit.
  file0   |    3 +++
  file2   |    3 ---
  3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..8422d40 100644
@@ -53,6 +54,7 @@ Subject: [PATCH] Third
  dir/sub |    2 ++
  file1   |    3 +++
  2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
 
 diff --git a/dir/sub b/dir/sub
 index 8422d40..cead32e 100644
index d10a46523ba269b9012ac14fb9220cf78ebcd4b4..e7650884755b993661ef14fbc812edd7c2710702 100644 (file)
@@ -9,6 +9,7 @@ Subject: [PATCH] Side
  file0   |    3 +++
  file3   |    4 ++++
  3 files changed, 9 insertions(+), 0 deletions(-)
+ create mode 100644 file3
 
 diff --git a/dir/sub b/dir/sub
 index 35d242b..7289e35 100644
index 1bc5b7a41295426dfdd0b75aaf7b2ec4226f257a..adf4993bacf05b4ccb0b447910dda6a2c453bc83 100755 (executable)
@@ -109,12 +109,10 @@ index d99af23..8b32fb5 100644
 +      whitespace at beginning
  whitespace change
 -whitespace in the middle
--whitespace at end
 +white space in the middle
-+whitespace at end  
+ whitespace at end
  unchanged line
--CR at endQ
-+CR at end
+ CR at endQ
 EOF
 git-diff -b > out
 test_expect_success 'another test, with -b' 'diff -u expect out'
diff --git a/t/t4016-diff-quote.sh b/t/t4016-diff-quote.sh
new file mode 100755 (executable)
index 0000000..edde8f5
--- /dev/null
@@ -0,0 +1,66 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Junio C Hamano
+#
+
+test_description='Quoting paths in diff output.
+'
+
+. ./test-lib.sh
+
+P0='pathname'
+P1='pathname   with HT'
+P2='pathname with SP'
+P3='pathname
+with LF'
+
+test_expect_success setup '
+       echo P0.0 >"$P0.0" &&
+       echo P0.1 >"$P0.1" &&
+       echo P0.2 >"$P0.2" &&
+       echo P0.3 >"$P0.3" &&
+       echo P1.0 >"$P1.0" &&
+       echo P1.2 >"$P1.2" &&
+       echo P1.3 >"$P1.3" &&
+       git add . &&
+       git commit -m initial &&
+       git mv "$P0.0" "R$P0.0" &&
+       git mv "$P0.1" "R$P1.0" &&
+       git mv "$P0.2" "R$P2.0" &&
+       git mv "$P0.3" "R$P3.0" &&
+       git mv "$P1.0" "R$P0.1" &&
+       git mv "$P1.2" "R$P2.1" &&
+       git mv "$P1.3" "R$P3.1" &&
+       :
+'
+
+cat >expect <<\EOF
+ rename pathname.1 => "Rpathname\twith HT.0" (100%)
+ rename pathname.3 => "Rpathname\nwith LF.0" (100%)
+ rename "pathname\twith HT.3" => "Rpathname\nwith LF.1" (100%)
+ rename pathname.2 => Rpathname with SP.0 (100%)
+ rename "pathname\twith HT.2" => Rpathname with SP.1 (100%)
+ rename pathname.0 => Rpathname.0 (100%)
+ rename "pathname\twith HT.0" => Rpathname.1 (100%)
+EOF
+test_expect_success 'git diff --summary -M HEAD' '
+       git diff --summary -M HEAD >actual &&
+       diff -u expect actual
+'
+
+cat >expect <<\EOF
+ pathname.1 => "Rpathname\twith HT.0"            |    0 
+ pathname.3 => "Rpathname\nwith LF.0"            |    0 
+ "pathname\twith HT.3" => "Rpathname\nwith LF.1" |    0 
+ pathname.2 => Rpathname with SP.0               |    0 
+ "pathname\twith HT.2" => Rpathname with SP.1    |    0 
+ pathname.0 => Rpathname.0                       |    0 
+ "pathname\twith HT.0" => Rpathname.1            |    0 
+ 7 files changed, 0 insertions(+), 0 deletions(-)
+EOF
+test_expect_success 'git diff --stat -M HEAD' '
+       git diff --stat -M HEAD >actual &&
+       diff -u expect actual
+'
+
+test_done
index 22da6a00ccbe910d796dc84666caa2acfa9b8389..b4662b03647bd2f36c07ffac24adc6b5df5eef76 100755 (executable)
@@ -31,7 +31,7 @@ test_expect_success setup \
 test_expect_success apply \
     'git-apply --index --stat --summary --apply test-patch'
 
-if [ "$(git repo-config --get core.filemode)" = false ]
+if [ "$(git config --get core.filemode)" = false ]
 then
        say 'filemode disabled on the filesystem'
 else
index 74f5c2a5755c8d29045498031c0bc3721a00b9c7..aa2c869e0e7ffcfba2e4349cf5af0e2badcb5516 100755 (executable)
@@ -50,12 +50,12 @@ test_expect_success 'setup separate repository lacking postimage' '
 
        git tar-tree initial initial | tar xf - &&
        (
-               cd initial && git init-db && git add .
+               cd initial && git init && git add .
        ) &&
 
        git tar-tree second second | tar xf - &&
        (
-               cd second && git init-db && git add .
+               cd second && git init && git add .
        )
 
 '
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
new file mode 100755 (executable)
index 0000000..c571a1b
--- /dev/null
@@ -0,0 +1,151 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+#
+
+test_description='git-rerere
+'
+
+. ./test-lib.sh
+
+cat > a1 << EOF
+Whether 'tis nobler in the mind to suffer
+The slings and arrows of outrageous fortune,
+Or to take arms against a sea of troubles,
+And by opposing end them? To die: to sleep;
+No more; and by a sleep to say we end
+The heart-ache and the thousand natural shocks
+That flesh is heir to, 'tis a consummation
+Devoutly to be wish'd.
+EOF
+
+git add a1
+git commit -q -a -m initial
+
+git checkout -b first
+cat >> a1 << EOF
+To die, to sleep;
+To sleep: perchance to dream: ay, there's the rub;
+For in that sleep of death what dreams may come
+When we have shuffled off this mortal coil,
+Must give us pause: there's the respect
+That makes calamity of so long life;
+EOF
+git commit -q -a -m first
+
+git checkout -b second master
+git show first:a1 | sed 's/To die, t/To die! T/' > a1
+git commit -q -a -m second
+
+# activate rerere
+mkdir .git/rr-cache
+
+test_expect_failure 'conflicting merge' 'git pull . first'
+
+sha1=4f58849a60b4f969a2848966b6d02893b783e8fb
+rr=.git/rr-cache/$sha1
+test_expect_success 'recorded preimage' "grep ======= $rr/preimage"
+
+test_expect_success 'no postimage or thisimage yet' \
+       "test ! -f $rr/postimage -a ! -f $rr/thisimage"
+
+git show first:a1 > a1
+
+cat > expect << EOF
+--- a/a1
++++ b/a1
+@@ -6,11 +6,7 @@
+ The heart-ache and the thousand natural shocks
+ That flesh is heir to, 'tis a consummation
+ Devoutly to be wish'd.
+-<<<<<<<
+-To die! To sleep;
+-=======
+ To die, to sleep;
+->>>>>>>
+ To sleep: perchance to dream: ay, there's the rub;
+ For in that sleep of death what dreams may come
+ When we have shuffled off this mortal coil,
+EOF
+
+git rerere diff > out
+
+test_expect_success 'rerere diff' 'diff -u expect out'
+
+cat > expect << EOF
+a1
+EOF
+
+git rerere status > out
+
+test_expect_success 'rerere status' 'diff -u expect out'
+
+test_expect_success 'commit succeeds' \
+       "git commit -q -a -m 'prefer first over second'"
+
+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'
+
+git show first:a1 | sed 's/To die: t/To die! T/' > expect
+test_expect_success 'rerere kicked in' "! grep ======= a1"
+
+test_expect_success 'rerere prefers first change' 'diff -u a1 expect'
+
+rm $rr/postimage
+echo "$sha1    a1" | tr '\012' '\0' > .git/rr-cache/MERGE_RR
+
+test_expect_success 'rerere clear' 'git rerere clear'
+
+test_expect_success 'clear removed the directory' "test ! -d $rr"
+
+mkdir $rr
+echo Hello > $rr/preimage
+echo World > $rr/postimage
+
+sha2=4000000000000000000000000000000000000000
+rr2=.git/rr-cache/$sha2
+mkdir $rr2
+echo Hello > $rr2/preimage
+
+case "$(date -d @11111111 +%s 2>/dev/null)" in
+11111111)
+       # 'date' must be able to take arbitrary input with @11111111 notation.
+       # for this test to succeed.  We should fix this part using more
+       # portable script someday.
+
+       now=$(date +%s)
+       almost_15_days_ago=$(($now+60-15*86400))
+       just_over_15_days_ago=$(($now-1-15*86400))
+       almost_60_days_ago=$(($now+60-60*86400))
+       just_over_60_days_ago=$(($now-1-60*86400))
+       predate1="$(date -d "@$almost_60_days_ago" +%Y%m%d%H%M.%S)"
+       predate2="$(date -d "@$almost_15_days_ago" +%Y%m%d%H%M.%S)"
+       postdate1="$(date -d "@$just_over_60_days_ago" +%Y%m%d%H%M.%S)"
+       postdate2="$(date -d "@$just_over_15_days_ago" +%Y%m%d%H%M.%S)"
+
+       touch -m -t "$predate1" $rr/preimage
+       touch -m -t "$predate2" $rr2/preimage
+
+       test_expect_success 'garbage collection (part1)' 'git rerere gc'
+
+       test_expect_success 'young records still live' \
+               "test -f $rr/preimage -a -f $rr2/preimage"
+
+       touch -m -t "$postdate1" $rr/preimage
+       touch -m -t "$postdate2" $rr2/preimage
+
+       test_expect_success 'garbage collection (part2)' 'git rerere gc'
+
+       test_expect_success 'old records rest in peace' \
+               "test ! -f $rr/preimage -a ! -f $rr2/preimage"
+       ;;
+esac
+
+test_done
+
+
index cf08e9279c1b2dc61cb53cf1e4940143cb3bf05c..ac835fe4317b7a37d77205487066d8f10bd71422 100755 (executable)
@@ -10,7 +10,7 @@ commit id embedding:
 
   The contents of the repository is compared to the extracted tar
   archive.  The repository contains simple text files, symlinks and a
-  binary file (/bin/sh).  Only pathes shorter than 99 characters are
+  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
index de45ac4e0fcea5b7acab245af9876b58ec1ccea3..f51154745586b246ecb30caee3f2150441911e1d 100755 (executable)
@@ -44,7 +44,7 @@ test_expect_success \
     'unpack without delta' \
     "GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     git-init-db &&
+     git-init &&
      git-unpack-objects -n <test-1-${packname_1}.pack &&
      git-unpack-objects <test-1-${packname_1}.pack"
 
@@ -75,7 +75,7 @@ test_expect_success \
     'unpack with delta' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     git-init-db &&
+     git-init &&
      git-unpack-objects -n <test-2-${packname_2}.pack &&
      git-unpack-objects <test-2-${packname_2}.pack'
 
@@ -100,7 +100,7 @@ test_expect_success \
     'use packed objects' \
     'GIT_OBJECT_DIRECTORY=.git2/objects &&
      export GIT_OBJECT_DIRECTORY &&
-     git-init-db &&
+     git-init &&
      cp test-1-${packname_1}.pack test-1-${packname_1}.idx .git2/objects/pack && {
         git-diff-tree --root -p $commit &&
         while read object
diff --git a/t/t5301-sliding-window.sh b/t/t5301-sliding-window.sh
new file mode 100755 (executable)
index 0000000..a6dbb04
--- /dev/null
@@ -0,0 +1,60 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn Pearce
+#
+
+test_description='mmap sliding window tests'
+. ./test-lib.sh
+
+test_expect_success \
+    'setup' \
+    'rm -f .git/index*
+     for i in a b c
+     do
+         echo $i >$i &&
+         dd if=/dev/urandom bs=32k count=1 >>$i &&
+         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 &&
+     git-repack -a -d &&
+     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"'
+
+test_expect_success \
+    'verify-pack -v, packedGitWindowSize == 1 page' \
+    '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"'
+
+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-repack -a -d &&
+     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"'
+
+test_done
index 8afb89971752fe4a91b9b7c62b4b0a6b69a8272b..7d93d0d7c90de94b0972d8fa5cd6d747c3fd2f56 100755 (executable)
@@ -8,38 +8,63 @@ test_description='See why rewinding head breaks send-pack
 '
 . ./test-lib.sh
 
-touch cpio-test
-test_expect_success 'working cpio' 'echo cpio-test | cpio -o > /dev/null'
-
-cnt='1'
+cnt=64
 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) &&
        zero=$commit &&
        parent=$zero &&
-       for i in $cnt
+       i=0 &&
+       while test $i -le $cnt
        do
-           sleep 1 &&
+           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 &&
            parent=$commit || return 1
        done &&
        git-update-ref HEAD "$commit" &&
-       git-clone -l ./. victim &&
+       git-clone ./. victim &&
        cd victim &&
        git-log &&
        cd .. &&
        git-update-ref HEAD "$zero" &&
        parent=$zero &&
-       for i in $cnt
+       i=0 &&
+       while test $i -le $cnt
        do
-           sleep 1 &&
+           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 &&
            parent=$commit || return 1
        done &&
        git-update-ref HEAD "$commit" &&
        echo Rebase &&
        git-log'
 
+test_expect_success 'pack the source repository' '
+       git repack -a -d &&
+       git prune
+'
+
+test_expect_success 'pack the destination repository' '
+       cd victim &&
+       git repack -a -d &&
+       git prune &&
+       cd ..
+'
+
 test_expect_success \
         'pushing rewound head should not barf but require --force' ' 
        # should not fail but refuse to update.
@@ -64,6 +89,16 @@ test_expect_success \
        cmp victim/.git/refs/heads/master .git/refs/heads/master
 '
 
+test_expect_success \
+        'push can be used to delete a ref' '
+       cd victim &&
+       git branch extra master &&
+       cd .. &&
+       test -f victim/.git/refs/heads/extra &&
+       git-send-pack ./victim/.git/ :extra master &&
+       ! test -f victim/.git/refs/heads/extra
+'
+
 unset GIT_CONFIG GIT_CONFIG_LOCAL
 HOME=`pwd`/no-such-directory
 export HOME ;# this way we force the victim/.git/config to be used.
@@ -71,7 +106,7 @@ 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-repo-config receive.denyNonFastforwards true &&
+       git-config receive.denyNonFastforwards true &&
        cd .. &&
        git-update-ref refs/heads/master master^ &&
        git-send-pack --force ./victim/.git/ master &&
diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh
new file mode 100755 (executable)
index 0000000..0514056
--- /dev/null
@@ -0,0 +1,81 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Shawn O. Pearce
+#
+
+test_description='Test the update hook infrastructure.'
+. ./test-lib.sh
+
+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-ref HEAD $commit0 &&
+       git-clone ./. victim &&
+       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 HEAD $commit1
+'
+
+cat >victim/.git/hooks/update <<'EOF'
+#!/bin/sh
+echo "$@" >$GIT_DIR/update.args
+read x; printf "$x" >$GIT_DIR/update.stdin
+echo STDOUT update
+echo STDERR update >&2
+EOF
+chmod u+x victim/.git/hooks/update
+
+cat >victim/.git/hooks/post-update <<'EOF'
+#!/bin/sh
+echo "$@" >$GIT_DIR/post-update.args
+read x; printf "$x" >$GIT_DIR/post-update.stdin
+echo STDOUT post-update
+echo STDERR post-update >&2
+EOF
+chmod u+x victim/.git/hooks/post-update
+
+test_expect_success push '
+       git-send-pack ./victim/.git/ master >send.out 2>send.err
+'
+
+test_expect_success 'hooks ran' '
+       test -f victim/.git/update.args &&
+       test -f victim/.git/update.stdin &&
+       test -f victim/.git/post-update.args &&
+       test -f victim/.git/post-update.stdin
+'
+
+test_expect_success 'update hook arguments' '
+       echo refs/heads/master $commit0 $commit1 |
+       diff -u - victim/.git/update.args
+'
+
+test_expect_success 'post-update hook arguments' '
+       echo refs/heads/master |
+       diff -u - victim/.git/post-update.args
+'
+
+test_expect_failure 'update hook stdin is /dev/null' '
+       test -s victim/.git/update.stdin
+'
+
+test_expect_failure 'post-update hook stdin is /dev/null' '
+       test -s victim/.git/post-update.stdin
+'
+
+test_expect_failure 'send-pack produced no output' '
+       test -s send.out
+'
+
+test_expect_success 'send-pack stderr contains hook messages' '
+       grep "STDOUT update" send.err &&
+       grep "STDERR update" send.err &&
+       grep "STDOUT post-update" send.err &&
+       grep "STDERR post-update" send.err
+'
+
+test_done
index f7625a6f4634da4b78c90defdc0a302c8ea4e231..48e3d1705f5e4bc7f206692276b4e3e1fe1ddf66 100755 (executable)
@@ -63,13 +63,13 @@ pull_to_client () {
        case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
        git-symbolic-ref HEAD refs/heads/`echo $heads | sed -e 's/^\(.\).*$/\1/'`
 
-       test_expect_success "fsck" 'git-fsck-objects --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-objects --full'
+        git-fsck --full'
 
        test_expect_success "new object count after $number pull" \
        'idx=`echo pack-*.idx` &&
@@ -97,7 +97,8 @@ pull_to_client () {
 (
        mkdir client &&
        cd client &&
-       git-init-db 2>> log2.txt
+       git-init 2>> log2.txt &&
+       git config transfer.unpacklimit 0
 )
 
 add A1
@@ -128,4 +129,54 @@ 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"
+
+(cd shallow; git-count-objects -v) > count.shallow
+
+test_expect_success "clone shallow object count" \
+       "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\""
+
+count_output () {
+       sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/: 0$/d' "$1"
+}
+
+test_expect_success "clone shallow object count (part 2)" '
+       test -z "$(count_output count.shallow)"
+'
+
+test_expect_success "fsck in shallow repo" \
+       "(cd shallow; git-fsck --full)"
+
+#test_done; exit
+
+add B66 $B65
+add B67 $B66
+
+test_expect_success "pull in shallow repo" \
+       "(cd shallow; git pull .. B)"
+
+(cd shallow; git-count-objects -v) > count.shallow
+test_expect_success "clone shallow object count" \
+       "test \"count: 6\" = \"$(grep count count.shallow)\""
+
+add B68 $B67
+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
+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
+test_expect_success "clone shallow object count" \
+       "test \"count: 18\" = \"$(grep count count.shallow)\""
+
+test_expect_failure "pull in shallow repo with missing merge base" \
+       "(cd shallow; git pull --depth 4 .. A)"
+
 test_done
index a11ab0ad41a006f9f20d8ec6172d34e6c7f92d41..50c64856f0fd35fd14007e0603a1cf58ce702cec 100755 (executable)
@@ -22,16 +22,15 @@ test_expect_success "clone and setup child repos" '
        cd .. &&
        git clone . two &&
        cd two &&
-       git repo-config branch.master.remote one &&
-       {
-               echo "URL: ../one/.git/"
-               echo "Pull: refs/heads/master:refs/heads/one"
-       } >.git/remotes/one
+       git config branch.master.remote one &&
+       git config remote.one.url ../one/.git/ &&
+       git config remote.one.fetch refs/heads/master:refs/heads/one &&
        cd .. &&
        git clone . three &&
        cd three &&
-       git repo-config branch.master.remote two &&
-       git repo-config branch.master.merge refs/heads/one &&
+       git config branch.master.remote two &&
+       git config branch.master.merge refs/heads/one &&
+       mkdir -p .git/remotes &&
        {
                echo "URL: ../two/.git/"
                echo "Pull: refs/heads/master:refs/heads/two"
@@ -74,7 +73,7 @@ test_expect_success 'fetch following tags' '
 
        mkdir four &&
        cd four &&
-       git init-db &&
+       git init &&
 
        git fetch .. :track &&
        git show-ref --verify refs/tags/anno &&
index f841574573545fa333e00b6815ba0df7d6931273..7eb37838bb788dfb68361bca95860fd532276deb 100755 (executable)
@@ -17,7 +17,7 @@ test_expect_success setup '
 test_expect_success 'pulling into void' '
        mkdir cloned &&
        cd cloned &&
-       git init-db &&
+       git init &&
        git pull ..
 '
 
index 041be04f5ceed683f2b4959b119cde4e055ed6e4..1776b377f3c787977b145980f05aa74da5038657 100755 (executable)
@@ -36,7 +36,7 @@ test_expect_success \
     'git-clone foo bar'
 
 test_expect_success \
-    'successfull clone must leave the directory' \
+    'successful clone must leave the directory' \
     'cd bar'
 
 test_done
index dd9caad1c2a34a6721aafa1840916a5a7471ece8..6d432525934980050f5c0d1683a1bc5e220b6e18 100755 (executable)
@@ -26,7 +26,7 @@ git prune'
 
 cd "$base_dir"
 
-test_expect_success 'cloning with reference' \
+test_expect_success 'cloning with reference (-l -s)' \
 'git clone -l -s --reference B A C'
 
 cd "$base_dir"
@@ -50,6 +50,28 @@ diff expected current'
 
 cd "$base_dir"
 
+test_expect_success 'cloning with reference (no -l -s)' \
+'git clone --reference B A D'
+
+cd "$base_dir"
+
+test_expect_success 'existence of info/alternates' \
+'test `wc -l <D/.git/objects/info/alternates` = 1'
+
+cd "$base_dir"
+
+test_expect_success 'pulling from reference' \
+'cd D && git pull ../B'
+
+cd "$base_dir"
+
+test_expect_success 'that reference gets used' \
+'cd D && echo "0 objects, 0 kilobytes" > expected &&
+git count-objects > current &&
+diff expected current'
+
+cd "$base_dir"
+
 test_expect_success 'updating origin' \
 'cd A &&
 echo third > file3 &&
@@ -75,4 +97,20 @@ diff expected current'
 
 cd "$base_dir"
 
+test_expect_success 'pulling changes from origin' \
+'cd D &&
+git pull origin'
+
+cd "$base_dir"
+
+# the 5 local objects are expected; file3 blob, commit in A to add it
+# and its tree, and 2 are our tree and the merge commit.
+test_expect_success 'check objects expected to exist locally' \
+'cd D &&
+echo "5 objects" > expected &&
+git count-objects | cut -d, -f1 > current &&
+diff expected current'
+
+cd "$base_dir"
+
 test_done
index b9f6d9636313c29cb9d9553ca908e623fdefd179..2f8e97cb7ea4b8bdf56d5795d512534d504d25c0 100755 (executable)
@@ -17,7 +17,7 @@ reachable_via() {
 }
 
 test_valid_repo() {
-       git fsck-objects --full > fsck.log &&
+       git fsck --full > fsck.log &&
        test `wc -l < fsck.log` = 0
 }
 
diff --git a/t/t6005-rev-list-count.sh b/t/t6005-rev-list-count.sh
new file mode 100755 (executable)
index 0000000..334fccf
--- /dev/null
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='git-rev-list --max-count and --skip test'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+    for n in 1 2 3 4 5 ; do \
+        echo $n > a ; \
+        git add a ; \
+        git commit -m "$n" ; \
+    done
+'
+
+test_expect_success 'no options' '
+    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_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_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_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_done
diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh
new file mode 100644 (file)
index 0000000..f3cd3db
--- /dev/null
@@ -0,0 +1,138 @@
+#!/bin/sh
+
+test_description='RCS merge replacement: merge-file'
+. ./test-lib.sh
+
+cat > orig.txt << 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
+
+cat > new1.txt << 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.
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam tu mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+EOF
+
+cat > new2.txt << 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
+
+cat > new3.txt << 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
+
+cat > new4.txt << 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,
+EOF
+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"
+
+cp new1.txt test2.txt
+test_expect_success "merge without conflict (missing LF at EOF)" \
+       "git-merge-file test2.txt orig.txt new2.txt"
+
+test_expect_success "merge result added missing LF" \
+       "diff -u test.txt test2.txt"
+
+cp test.txt backup.txt
+test_expect_failure "merge with conflicts" \
+       "git-merge-file test.txt orig.txt new3.txt"
+
+cat > expect.txt << EOF
+<<<<<<< test.txt
+Dominus regit me, et nihil mihi deerit.
+=======
+DOMINUS regit me,
+et nihil mihi deerit.
+>>>>>>> new3.txt
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam tu mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+EOF
+
+test_expect_success "expected conflict markers" "diff -u 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"
+
+cat > expect.txt << EOF
+<<<<<<< 1
+Dominus regit me, et nihil mihi deerit.
+=======
+DOMINUS regit me,
+et nihil mihi deerit.
+>>>>>>> new3.txt
+In loco pascuae ibi me collocavit,
+super aquam refectionis educavit me;
+animam meam convertit,
+deduxit me super semitas jusitiae,
+propter nomen suum.
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam tu mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+EOF
+
+test_expect_success "expected conflict markers, with -L" \
+       "diff -u test.txt expect.txt"
+
+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"
+
+cat > expect << 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.
+<<<<<<< orig.txt
+=======
+Nam et si ambulavero in medio umbrae mortis,
+non timebo mala, quoniam TU mecum es:
+virga tua et baculus tuus ipsa me consolata sunt.
+>>>>>>> new5.txt
+EOF
+
+test_expect_success "expected conflict markers" "diff -u expect out"
+
+test_done
+
diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh
new file mode 100755 (executable)
index 0000000..65be95f
--- /dev/null
@@ -0,0 +1,139 @@
+#!/bin/sh
+
+test_description='Merge-recursive merging renames'
+. ./test-lib.sh
+
+test_expect_success setup \
+'
+cat >A <<\EOF &&
+a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+c cccccccccccccccccccccccccccccccccccccccccccccccc
+d dddddddddddddddddddddddddddddddddddddddddddddddd
+e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+f ffffffffffffffffffffffffffffffffffffffffffffffff
+g gggggggggggggggggggggggggggggggggggggggggggggggg
+h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
+k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
+l llllllllllllllllllllllllllllllllllllllllllllllll
+m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+o oooooooooooooooooooooooooooooooooooooooooooooooo
+EOF
+
+cat >M <<\EOF &&
+A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
+B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
+C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
+D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
+E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
+F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG
+H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
+I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII
+J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ
+K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK
+L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL
+M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
+N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN
+O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO
+EOF
+
+git add A M &&
+git commit -m "initial has A and M" &&
+git branch white &&
+git branch red &&
+git branch blue &&
+
+git checkout white &&
+sed -e "/^g /s/.*/g : white changes a line/" <A >B &&
+sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N &&
+rm -f A M &&
+git update-index --add --remove A B M N &&
+git commit -m "white renames A->B, M->N" &&
+
+git checkout red &&
+echo created by red >R &&
+git update-index --add R &&
+git commit -m "red creates R" &&
+
+git checkout blue &&
+sed -e "/^o /s/.*/g : blue changes a line/" <A >B &&
+rm -f A &&
+mv B A &&
+git update-index A &&
+git commit -m "blue modify A" &&
+
+git checkout master'
+
+# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae
+test_expect_success 'merge white into red (A->B,M->N)' \
+'
+       git checkout -b red-white red &&
+       git merge white &&
+       git write-tree >/dev/null || {
+               echo "BAD: merge did not complete"
+               return 1
+       }
+
+       test -f B || {
+               echo "BAD: B does not exist in working directory"
+               return 1
+       }
+       test -f N || {
+               echo "BAD: N does not exist in working directory"
+               return 1
+       }
+       test -f R || {
+               echo "BAD: R does not exist in working directory"
+               return 1
+       }
+
+       test -f A && {
+               echo "BAD: A still exists in working directory"
+               return 1
+       }
+       test -f M && {
+               echo "BAD: M still exists in working directory"
+               return 1
+       }
+       return 0
+'
+
+# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9
+test_expect_success 'merge blue into white (A->B, mod A, A untracked)' \
+'
+       git checkout -b white-blue white &&
+       echo dirty >A &&
+       git merge blue &&
+       git write-tree >/dev/null || {
+               echo "BAD: merge did not complete"
+               return 1
+       }
+
+       test -f A || {
+               echo "BAD: A does not exist in working directory"
+               return 1
+       }
+       test `cat A` = dirty || {
+               echo "BAD: A content is wrong"
+               return 1
+       }
+       test -f B || {
+               echo "BAD: B does not exist in working directory"
+               return 1
+       }
+       test -f N || {
+               echo "BAD: N does not exist in working directory"
+               return 1
+       }
+       test -f M && {
+               echo "BAD: M still exists in working directory"
+               return 1
+       }
+       return 0
+'
+
+test_done
diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh
new file mode 100644 (file)
index 0000000..31b9625
--- /dev/null
@@ -0,0 +1,84 @@
+#!/bin/sh
+
+test_description='Test merge without common ancestors'
+. ./test-lib.sh
+
+# This scenario is based on a real-world repository of Shawn Pearce.
+
+# 1 - A - D - F
+#   \   X   /
+#     B   X
+#       X   \
+# 2 - C - E - G
+
+GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100"
+export GIT_COMMITTER_DATE
+
+test_expect_success "setup tests" '
+echo 1 > a1 &&
+git add a1 &&
+GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 &&
+
+git checkout -b A master &&
+echo A > a1 &&
+GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 &&
+
+git checkout -b B master &&
+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 &&
+echo D > a1 &&
+git update-index a1 &&
+GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D &&
+
+git symbolic-ref HEAD refs/heads/other &&
+echo 2 > a1 &&
+GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 &&
+
+git checkout -b C &&
+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 &&
+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 &&
+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 &&
+echo F > a1 &&
+git update-index a1 &&
+GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F
+'
+
+test_expect_failure "combined merge conflicts" "git merge -m final G"
+
+cat > expect << EOF
+<<<<<<< HEAD:a1
+F
+=======
+G
+>>>>>>> G:a1
+EOF
+
+test_expect_success "result contains a conflict" "diff -u expect a1"
+
+git ls-files --stage > out
+cat > expect << EOF
+100644 da056ce14a2241509897fa68bb2b3b6e6194ef9e 1      a1
+100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2      a1
+100644 fd7923529855d0b274795ae3349c5e0438333979 3      a1
+EOF
+
+test_expect_success "virtual trees were processed" "diff -u expect out"
+
+test_done
diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh
new file mode 100755 (executable)
index 0000000..3e9edda
--- /dev/null
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+test_description='test describe
+
+                       B
+        .--------------o----o----o----x
+       /                   /    /
+ o----o----o----o----o----.    /
+       \        A    c        /
+        .------------o---o---o
+                     D   e
+'
+. ./test-lib.sh
+
+check_describe () {
+       expect="$1"
+       shift
+       R=$(git describe "$@") &&
+       test_expect_success "describe $*" '
+       case "$R" in
+       $expect)        echo happy ;;
+       *)      echo "Oops - $R is not $expect";
+               false ;;
+       esac
+       '
+}
+
+test_expect_success setup '
+
+       test_tick &&
+       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) &&
+
+       test_tick &&
+       echo three >file && git-add file && git-commit -m third &&
+
+       test_tick &&
+       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 &&
+       test_tick &&
+       git-tag c &&
+
+       git reset --hard $two &&
+       test_tick &&
+       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) &&
+
+       git reset --hard $two &&
+       test_tick &&
+       echo D >another && git-add another && git-commit -m D &&
+       test_tick &&
+       git-tag -a -m D D &&
+
+       test_tick &&
+       echo DD >another && git commit -a -m another &&
+
+       test_tick &&
+       git-tag e &&
+
+       test_tick &&
+       echo DDD >another && git commit -a -m "yet another" &&
+
+       test_tick &&
+       git-merge -m Merged $merged &&
+
+       test_tick &&
+       echo X >file && echo X >side && git-add file side &&
+       git-commit -m x
+
+'
+
+check_describe A-* HEAD
+check_describe A-* HEAD^
+check_describe D-* HEAD^^
+check_describe A-* HEAD^^2
+check_describe B HEAD^^2^
+
+check_describe A-* --tags HEAD
+check_describe A-* --tags HEAD^
+check_describe D-* --tags HEAD^^
+check_describe A-* --tags HEAD^^2
+check_describe B --tags HEAD^^2^
+
+test_done
index 63e49f310cdc0bf99cb1578c29f2a3da4059da75..ea140236162894ed2e5691b57ba8c5cf98e5627d 100755 (executable)
@@ -108,7 +108,7 @@ EOF
 
 test_expect_success 'merge-msg test #3' '
 
-       git repo-config merge.summary true &&
+       git config merge.summary true &&
 
        git checkout master &&
        setdate &&
@@ -138,7 +138,7 @@ EOF
 
 test_expect_success 'merge-msg test #4' '
 
-       git repo-config merge.summary true &&
+       git config merge.summary true &&
 
        git checkout master &&
        setdate &&
@@ -150,7 +150,7 @@ test_expect_success 'merge-msg test #4' '
 
 test_expect_success 'merge-msg test #5' '
 
-       git repo-config merge.summary yes &&
+       git config merge.summary yes &&
 
        git checkout master &&
        setdate &&
index 23a1eff3bba4505d449bd5fdffb3daf7f9b23ddf..344033249cc1ea3f7066d4d6007ade6cc1a2c5de 100755 (executable)
@@ -88,7 +88,7 @@ test_expect_success \
 
 test_expect_success "Michael Cassar's test case" '
        rm -fr .git papers partA &&
-       git init-db &&
+       git init &&
        mkdir -p papers/unsorted papers/all-papers partA &&
        echo a > papers/unsorted/Thesis.pdf &&
        echo b > partA/outline.txt &&
@@ -105,4 +105,17 @@ test_expect_success "Michael Cassar's test case" '
        }
 '
 
+rm -fr papers partA path?
+
+test_expect_success "Sergey Vlasov's test case" '
+       rm -fr .git &&
+       git init &&
+       mkdir ab &&
+       date >ab.c &&
+       date >ab/d &&
+       git add ab.c ab &&
+       git commit -m 'initial' &&
+       git mv ab a
+'
+
 test_done
index 085d4a096b90e076afef70a7e8b7746f58a49060..867bbd26cbbacbe03ef76cadb6ff34976c324da5 100755 (executable)
@@ -14,15 +14,23 @@ fill () {
        done
 }
 
+
 test_expect_success setup '
 
-       fill 1 2 3 4 5 >one &&
+       fill 1 2 3 4 5 6 7 8 >one &&
        fill a b c d e >two &&
        git add one two &&
        git commit -m "Initial A one, A two" &&
 
-       git checkout -b side &&
-       fill 1 2 3 >one &&
+       git checkout -b renamer &&
+       rm -f one &&
+       fill 1 3 4 5 6 7 8 >uno &&
+       git add uno &&
+       fill a b c d e f >two &&
+       git commit -a -m "Renamer R one->uno, M two" &&
+
+       git checkout -b side master &&
+       fill 1 2 3 4 5 6 7 >one &&
        fill A B C D E >three &&
        rm -f two &&
        git update-index --add --remove one two three &&
@@ -42,7 +50,7 @@ test_expect_success "checkout from non-existing branch" '
 
 test_expect_success "checkout with dirty tree without -m" '
 
-       fill 0 1 2 3 4 5 >one &&
+       fill 0 1 2 3 4 5 6 7 8 >one &&
        if git checkout side
        then
                echo Not happy
@@ -58,12 +66,10 @@ test_expect_success "checkout -m with dirty tree" '
        git checkout -f master &&
        git clean &&
 
-       fill 0 1 2 3 4 5 >one &&
+       fill 0 1 2 3 4 5 6 7 8 >one &&
        git checkout -m side &&
 
-       fill "  master" "* side" >expect.branch &&
-       git branch >current.branch &&
-       diff expect.branch current.branch &&
+       test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
 
        fill "M one" "A three" "D       two" >expect.master &&
        git diff --name-status master >current.master &&
@@ -78,4 +84,49 @@ test_expect_success "checkout -m with dirty tree" '
        diff expect.index current.index
 '
 
+test_expect_success "checkout -m with dirty tree, renamed" '
+
+       git checkout -f master && git clean &&
+
+       fill 1 2 3 4 5 7 8 >one &&
+       if git checkout renamer
+       then
+               echo Not happy
+               false
+       else
+               echo "happy - failed correctly"
+       fi &&
+
+       git checkout -m renamer &&
+       fill 1 3 4 5 7 8 >expect &&
+       diff expect uno &&
+       ! test -f one &&
+       git diff --cached >current &&
+       ! test -s current
+
+'
+
+test_expect_success 'checkout -m with merge conflict' '
+
+       git checkout -f master && git clean &&
+
+       fill 1 T 3 4 5 6 S 8 >one &&
+       if git checkout renamer
+       then
+               echo Not happy
+               false
+       else
+               echo "happy - failed correctly"
+       fi &&
+
+       git checkout -m renamer &&
+
+       git diff master:one :3:uno |
+       sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
+       fill d2 aT d7 aS >expect &&
+       diff current expect &&
+       git diff --cached two >current &&
+       ! test -s current
+'
+
 test_done
index 34a3ccd31cee8e82fd8a9867637cf610efae3e4c..040da92756787371d584f33b53b54a3fc84f55b1 100755 (executable)
@@ -19,180 +19,168 @@ esac
 
 echo 'define NO_SVN_TESTS to skip git-svn tests'
 
-mkdir import
-cd import
-
-echo foo > foo
-if test -z "$NO_SYMLINK"
-then
-       ln -s foo foo.link
-fi
-mkdir -p dir/a/b/c/d/e
-echo 'deep dir' > dir/a/b/c/d/e/file
-mkdir -p bar
-echo 'zzz' > bar/zzz
-echo '#!/bin/sh' > exec.sh
-chmod +x exec.sh
-svn import -m 'import for git-svn' . "$svnrepo" >/dev/null
-
-cd ..
-rm -rf import
-
 test_expect_success \
-    'initialize git-svn' \
-    "git-svn init $svnrepo"
+    'initialize git-svn' "
+       mkdir import &&
+       cd import &&
+       echo foo > foo &&
+       ln -s foo foo.link
+       mkdir -p dir/a/b/c/d/e &&
+       echo 'deep dir' > dir/a/b/c/d/e/file &&
+       mkdir bar &&
+       echo 'zzz' > bar/zzz &&
+       echo '#!/bin/sh' > exec.sh &&
+       chmod +x exec.sh &&
+       svn import -m 'import for git-svn' . $svnrepo >/dev/null &&
+       cd .. &&
+       rm -rf import &&
+       git-svn init $svnrepo"
 
 test_expect_success \
     'import an SVN revision into git' \
     'git-svn fetch'
 
-test_expect_success "checkout from svn" "svn co $svnrepo $SVN_TREE"
+test_expect_success "checkout from svn" "svn co $svnrepo '$SVN_TREE'"
 
 name='try a deep --rmdir with a commit'
-git checkout -f -b mybranch remotes/git-svn
-mv dir/a/b/c/d/e/file dir/file
-cp dir/file file
-git update-index --add --remove dir/a/b/c/d/e/file dir/file file
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch &&
-     svn up $SVN_TREE &&
-     test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
+test_expect_success "$name" "
+       git checkout -f -b mybranch remotes/git-svn &&
+       mv dir/a/b/c/d/e/file dir/file &&
+       cp dir/file file &&
+       git update-index --add --remove dir/a/b/c/d/e/file dir/file file &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch &&
+       svn up '$SVN_TREE' &&
+       test -d '$SVN_TREE'/dir && test ! -d '$SVN_TREE'/dir/a"
 
 
 name='detect node change from file to directory #1'
-mkdir dir/new_file
-mv dir/file dir/new_file/file
-mv dir/new_file dir/file
-git update-index --remove dir/file
-git update-index --add dir/file/file
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \
-    || true
+test_expect_failure "$name" "
+       mkdir dir/new_file &&
+       mv dir/file dir/new_file/file &&
+       mv dir/new_file dir/file &&
+       git update-index --remove dir/file &&
+       git update-index --add dir/file/file &&
+       git commit -m '$name'  &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch" || true
 
 
 name='detect node change from directory to file #1'
-rm -rf dir $GIT_DIR/index
-git checkout -f -b mybranch2 remotes/git-svn
-mv bar/zzz zzz
-rm -rf bar
-mv zzz bar
-git update-index --remove -- bar/zzz
-git update-index --add -- bar
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
-    || true
+test_expect_failure "$name" "
+       rm -rf dir '$GIT_DIR'/index &&
+       git checkout -f -b mybranch2 remotes/git-svn &&
+       mv bar/zzz zzz &&
+       rm -rf bar &&
+       mv zzz bar &&
+       git update-index --remove -- bar/zzz &&
+       git update-index --add -- bar &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch2" || true
 
 
 name='detect node change from file to directory #2'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch3 remotes/git-svn
-rm bar/zzz
-git-update-index --remove bar/zzz
-mkdir bar/zzz
-echo yyy > bar/zzz/yyy
-git-update-index --add bar/zzz/yyy
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
-    || true
+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 &&
+       mkdir bar/zzz &&
+       echo yyy > 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
 
 
 name='detect node change from directory to file #2'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch4 remotes/git-svn
-rm -rf dir
-git update-index --remove -- dir/file
-touch dir
-echo asdf > dir
-git update-index --add -- dir
-git commit -m "$name"
-
-test_expect_failure "$name" \
-    'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
-    || true
+test_expect_failure "$name" "
+       rm -f '$GIT_DIR'/index &&
+       git checkout -f -b mybranch4 remotes/git-svn &&
+       rm -rf dir &&
+       git update-index --remove -- dir/file &&
+       touch dir &&
+       echo asdf > dir &&
+       git update-index --add -- dir &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch4" || true
 
 
 name='remove executable bit from a file'
-rm -f $GIT_DIR/index
-git checkout -f -b mybranch5 remotes/git-svn
-chmod -x exec.sh
-git update-index exec.sh
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-     svn up $SVN_TREE &&
-     test ! -x $SVN_TREE/exec.sh"
+test_expect_success "$name" "
+       rm -f '$GIT_DIR'/index &&
+       git checkout -f -b mybranch5 remotes/git-svn &&
+       chmod -x exec.sh &&
+       git update-index exec.sh &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch5 &&
+       svn up '$SVN_TREE' &&
+       test ! -x '$SVN_TREE'/exec.sh"
 
 
 name='add executable bit back file'
-chmod +x exec.sh
-git update-index exec.sh
-git commit -m "$name"
-
-test_expect_success "$name" \
-    "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-     svn up $SVN_TREE &&
-     test -x $SVN_TREE/exec.sh"
-
-
-
-if test -z "$NO_SYMLINK"
-then
-       name='executable file becomes a symlink to bar/zzz (file)'
-       rm exec.sh
-       ln -s bar/zzz exec.sh
-       git update-index exec.sh
-       git commit -m "$name"
-
-       test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -L $SVN_TREE/exec.sh"
-
-       name='new symlink is added to a file that was also just made executable'
-       chmod +x bar/zzz
-       ln -s bar/zzz exec-2.sh
-       git update-index --add bar/zzz exec-2.sh
-       git commit -m "$name"
-
-       test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -x $SVN_TREE/bar/zzz &&
-            test -L $SVN_TREE/exec-2.sh"
-
-       name='modify a symlink to become a file'
-       echo git help > help || true
-       rm exec-2.sh
-       cp help exec-2.sh
-       git update-index exec-2.sh
-       git commit -m "$name"
-
-       test_expect_success "$name" \
-           "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
-            svn up $SVN_TREE &&
-            test -f $SVN_TREE/exec-2.sh &&
-            test ! -L $SVN_TREE/exec-2.sh &&
-            diff -u help $SVN_TREE/exec-2.sh"
-fi
-
+test_expect_success "$name" "
+       chmod +x exec.sh &&
+       git update-index exec.sh &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch5 &&
+       svn up '$SVN_TREE' &&
+       test -x '$SVN_TREE'/exec.sh"
+
+
+name='executable file becomes a symlink to bar/zzz (file)'
+test_expect_success "$name" "
+       rm exec.sh &&
+       ln -s bar/zzz exec.sh &&
+       git update-index exec.sh &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch5 &&
+       svn up '$SVN_TREE' &&
+       test -L '$SVN_TREE'/exec.sh"
+
+name='new symlink is added to a file that was also just made executable'
+
+test_expect_success "$name" "
+       chmod +x bar/zzz &&
+       ln -s bar/zzz exec-2.sh &&
+       git update-index --add bar/zzz exec-2.sh &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch5 &&
+       svn up '$SVN_TREE' &&
+       test -x '$SVN_TREE'/bar/zzz &&
+       test -L '$SVN_TREE'/exec-2.sh"
+
+name='modify a symlink to become a file'
+test_expect_success "$name" "
+       echo git help > help || true &&
+       rm exec-2.sh &&
+       cp help exec-2.sh &&
+       git update-index exec-2.sh &&
+       git commit -m '$name' &&
+       git-svn set-tree --find-copies-harder --rmdir \
+               remotes/git-svn..mybranch5 &&
+       svn up '$SVN_TREE' &&
+       test -f '$SVN_TREE'/exec-2.sh &&
+       test ! -L '$SVN_TREE'/exec-2.sh &&
+       diff -u help $SVN_TREE/exec-2.sh"
 
 if test "$have_utf8" = t
 then
        name="commit with UTF-8 message: locale: $GIT_SVN_LC_ALL"
-       echo '# hello' >> exec-2.sh
-       git update-index exec-2.sh
-       git commit -m 'éï∏'
-       export LC_ALL="$GIT_SVN_LC_ALL"
-       test_expect_success "$name" "git-svn commit HEAD"
+       LC_ALL="$GIT_SVN_LC_ALL"
+       export LC_ALL
+       test_expect_success "$name" "
+               echo '# hello' >> exec-2.sh &&
+               git update-index exec-2.sh &&
+               git commit -m 'éï∏' &&
+               git-svn set-tree HEAD"
        unset LC_ALL
 else
        echo "UTF-8 locale not set, test skipped ($GIT_SVN_LC_ALL)"
@@ -207,12 +195,6 @@ test_expect_success "$name" \
      git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
      diff -u a b"
 
-if test -n "$NO_SYMLINK"
-then
-       test_done
-       exit 0
-fi
-
 name='check imported tree checksums expected tree checksums'
 rm -f expected
 if test "$have_utf8" = t
@@ -228,6 +210,9 @@ tree 56a30b966619b863674f5978696f4a3594f2fca9
 tree d667270a1f7b109f5eb3aaea21ede14b56bfdd6e
 tree 8f51f74cf0163afc9ad68a4b1537288c4558b5a4
 EOF
+
+echo tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 >> expected
+
 test_expect_success "$name" "diff -u a expected"
 
 test_done
index a5a235f100709f503887e9a69c5b510e0b04534f..e8133d81cb12f22cf4445a63d21d5e595c84fcb0 100755 (executable)
@@ -56,14 +56,14 @@ test_expect_success 'checkout working copy from svn' "svn co $svnrepo test_wc"
 test_expect_success 'setup some commits to svn' \
        'cd test_wc &&
                echo Greetings >> kw.c &&
+               poke kw.c &&
                svn commit -m "Not yet an Id" &&
-               svn up &&
                echo Hello world >> kw.c &&
+               poke kw.c &&
                svn commit -m "Modified file, but still not yet an Id" &&
-               svn up &&
                svn propset svn:keywords Id kw.c &&
+               poke kw.c &&
                svn commit -m "Propset Id" &&
-               svn up &&
        cd ..'
 
 test_expect_success 'initialize git-svn' "git-svn init $svnrepo"
@@ -73,8 +73,8 @@ name='test svn:keywords ignoring'
 test_expect_success "$name" \
        'git checkout -b mybranch remotes/git-svn &&
        echo Hi again >> kw.c &&
-       git commit -a -m "test keywoards ignoring" &&
-       git-svn commit remotes/git-svn..mybranch &&
+       git commit -a -m "test keywords ignoring" &&
+       git-svn set-tree remotes/git-svn..mybranch &&
        git pull . remotes/git-svn'
 
 expect='/* $Id$ */'
@@ -87,7 +87,6 @@ test_expect_success "propset CR on crlf files" \
                svn propset svn:eol-style CR crlf &&
                svn propset svn:eol-style CR ne_crlf &&
                svn commit -m "propset CR on crlf files" &&
-               svn up &&
         cd ..'
 
 test_expect_success 'fetch and pull latest from svn and checkout a new wc' \
@@ -111,8 +110,7 @@ cd test_wc
         svn propset svn:eol-style CRLF ne_cr &&
         svn propset svn:keywords Id cr &&
         svn propset svn:keywords Id ne_cr &&
-        svn commit -m "propset CRLF on cr files" &&
-        svn up'
+        svn commit -m "propset CRLF on cr files"'
 cd ..
 test_expect_success 'fetch and pull latest from svn' \
        'git-svn fetch && git pull . remotes/git-svn'
index d693d183c8e41a4c1e948e690f42a55c985543b3..4e0808380fea78061e37bc4308f0d7ffeb1cbf5f 100755 (executable)
@@ -1,3 +1,4 @@
+#!/bin/sh
 test_description='git-svn rmdir'
 . ./lib-git-svn.sh
 
@@ -21,7 +22,7 @@ test_expect_success 'mirror via git-svn' "
 test_expect_success 'Try a commit on rmdir' "
        git rm -f deeply/nested/directory/number/2/another &&
        git commit -a -m 'remove another' &&
-       git-svn commit --rmdir HEAD &&
+       git-svn set-tree --rmdir HEAD &&
        svn ls -R $svnrepo | grep ^deeply/nested/directory/number/1
        "
 
index cc62d4ece888a3be5c237a00c35699ab152cbdf5..183ae3b1c2f0511810c18418c109a2e1412fa8a8 100755 (executable)
@@ -1,6 +1,9 @@
+#!/bin/sh
 test_description='git-svn graft-branches'
 . ./lib-git-svn.sh
 
+svnrepo="$svnrepo/test-git-svn"
+
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
@@ -13,26 +16,21 @@ test_expect_success 'initialize repo' "
        svn co $svnrepo wc &&
        cd wc &&
        echo feedme >> branches/a/readme &&
+       poke branches/a/readme &&
        svn commit -m hungry &&
-       svn up &&
        cd trunk &&
        svn merge -r3:4 $svnrepo/branches/a &&
        svn commit -m 'merge with a' &&
        cd ../.. &&
-       svn log -v $svnrepo &&
-       git-svn init -i trunk $svnrepo/trunk &&
-       git-svn init -i a $svnrepo/branches/a &&
-       git-svn init -i tags/a $svnrepo/tags/a &&
-       git-svn fetch -i tags/a &&
-       git-svn fetch -i a &&
-       git-svn fetch -i trunk
+       git-svn multi-init $svnrepo -T trunk -b branches -t tags &&
+       git-svn multi-fetch
        "
 
 r1=`git-rev-list remotes/trunk | tail -n1`
 r2=`git-rev-list remotes/tags/a | tail -n1`
 r3=`git-rev-list remotes/a | tail -n1`
-r4=`git-rev-list remotes/a | head -n1`
-r5=`git-rev-list remotes/trunk | head -n1`
+r4=`git-rev-parse remotes/a`
+r5=`git-rev-parse remotes/trunk`
 
 test_expect_success 'test graft-branches regexes and copies' "
        test -n "$r1" &&
index 01488ff78acd8324a04a3f0c5e4788e60148a704..405b5553688d6612177b8730de3e882161e73e85 100755 (executable)
@@ -6,13 +6,6 @@
 test_description='git-svn --follow-parent fetching'
 . ./lib-git-svn.sh
 
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: --follow-parent needs SVN libraries'
-       test_done
-       exit 0
-fi
-
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
@@ -23,11 +16,13 @@ test_expect_success 'initialize repo' "
        svn co $svnrepo wc &&
        cd wc &&
        echo world >> trunk/readme &&
+       poke trunk/readme &&
        svn commit -m 'another commit' &&
        svn up &&
        svn mv -m 'rename to thunk' trunk thunk &&
        svn up &&
        echo goodbye >> thunk/readme &&
+       poke thunk/readme &&
        svn commit -m 'bye now' &&
        cd ..
        "
index 746c8277d06a7d9d147df16d6c16efce65bf34a2..6323c7e3ace0dda555ee67b384a197a26bf89dd6 100755 (executable)
@@ -4,13 +4,6 @@
 test_description='git-svn commit-diff'
 . ./lib-git-svn.sh
 
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: commit-diff needs SVN libraries'
-       test_done
-       exit 0
-fi
-
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
index 58698b3f29966593d1f7063b04e8d07322636572..6f132f2419462c20748b1a6fcb2df36a53ab0f89 100755 (executable)
@@ -4,13 +4,6 @@
 test_description='git-svn commit-diff clobber'
 . ./lib-git-svn.sh
 
-if test -n "$GIT_SVN_NO_LIB" && test "$GIT_SVN_NO_LIB" -ne 0
-then
-       echo 'Skipping: commit-diff clobber needs SVN libraries'
-       test_done
-       exit 0
-fi
-
 test_expect_success 'initialize repo' "
        mkdir import &&
        cd import &&
@@ -25,6 +18,7 @@ test_expect_success 'commit change from svn side' "
        svn co $svnrepo t.svn &&
        cd t.svn &&
        echo second line from svn >> file &&
+       poke file &&
        svn commit -m 'second line from svn' &&
        cd .. &&
        rm -rf t.svn
@@ -52,6 +46,7 @@ test_expect_failure 'dcommit fails to commit because of conflict' "
        svn co $svnrepo t.svn &&
        cd t.svn &&
        echo fourth line from svn >> file &&
+       poke file &&
        svn commit -m 'fourth line from svn' &&
        cd .. &&
        rm -rf t.svn &&
index 6e566d44093c5b6fc4a6a891b0ae9473e672b56d..4efa0c926c45befa92be90f746f03807a591a893 100755 (executable)
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 #
 # Copyright (c) Robin Rosenberg
 #
@@ -14,16 +14,18 @@ then
     exit
 fi
 
-export CVSROOT=$(pwd)/cvsroot
-export CVSWORK=$(pwd)/cvswork
+CVSROOT=$(pwd)/cvsroot
+CVSWORK=$(pwd)/cvswork
+GIT_DIR=$(pwd)/.git
+export CVSROOT CVSWORK GIT_DIR
+
 rm -rf "$CVSROOT" "$CVSWORK"
 mkdir "$CVSROOT" &&
 cvs init &&
 cvs -Q co -d "$CVSWORK" . &&
-export GIT_DIR=$(pwd)/.git &&
 echo >empty &&
 git add empty &&
-git commit -a -m "Initial" 2>/dev/null ||
+git commit -q -a -m "Initial" 2>/dev/null ||
 exit 1
 
 test_expect_success \
@@ -89,18 +91,17 @@ test_expect_success \
      ! git cvsexportcommit -c $id
      )'
 
-# Should fail, but only on the git-cvsexportcommit stage
-test_expect_success \
-    'Fail to remove binary file more than one generation old' \
-    'git reset --hard HEAD^ &&
-     cat F/newfile6.png >>D/newfile4.png &&
-     git commit -a -m "generation 2 (again)" &&
-     rm -f D/newfile4.png &&
-     git commit -a -m "generation 3" &&
-     id=$(git rev-list --max-count=1 HEAD) &&
-     (cd "$CVSWORK" &&
-     ! git cvsexportcommit -c $id
-     )'
+#test_expect_success \
+#    'Fail to remove binary file more than one generation old' \
+#    'git reset --hard HEAD^ &&
+#     cat F/newfile6.png >>D/newfile4.png &&
+#     git commit -a -m "generation 2 (again)" &&
+#     rm -f D/newfile4.png &&
+#     git commit -a -m "generation 3" &&
+#     id=$(git rev-list --max-count=1 HEAD) &&
+#     (cd "$CVSWORK" &&
+#     ! git cvsexportcommit -c $id
+#     )'
 
 # We reuse the state from two tests back here
 
@@ -108,7 +109,7 @@ test_expect_success \
 # fail with gnu patch, so cvsexportcommit must handle that.
 test_expect_success \
     'Remove only binary files' \
-    'git reset --hard HEAD^^^ &&
+    'git reset --hard HEAD^^ &&
      rm -f D/newfile4.png &&
      git commit -a -m "test: remove only a binary file" &&
      id=$(git rev-list --max-count=1 HEAD) &&
@@ -142,4 +143,93 @@ test_expect_success \
      diff F/newfile6.png ../F/newfile6.png
      )'
 
+test_expect_success \
+     'New file with spaces in file name' \
+     'mkdir "G g" &&
+      echo ok then >"G g/with spaces.txt" &&
+      git add "G g/with spaces.txt" && \
+      cp ../test9200a.png "G g/with spaces.png" && \
+      git add "G g/with spaces.png" &&
+      git commit -a -m "With spaces" &&
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (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/"
+      )'
+
+test_expect_success \
+     'Update file with spaces in file name' \
+     'echo Ok then >>"G g/with spaces.txt" &&
+      cat ../test9200a.png >>"G g/with spaces.png" && \
+      git add "G g/with spaces.png" &&
+      git commit -a -m "Update with spaces" &&
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (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/"
+      )'
+
+# Some filesystems mangle pathnames with UTF-8 characters --
+# check and skip
+if p="Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö" &&
+       mkdir -p "tst/$p" &&
+       date >"tst/$p/day" &&
+       found=$(find tst -type f -print) &&
+       test "z$found" = "ztst/$p/day" &&
+       rm -fr tst
+then
+
+# This test contains UTF-8 characters
+test_expect_success \
+     'File with non-ascii file name' \
+     'mkdir -p Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö &&
+      echo Foo >Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
+      git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.txt &&
+      cp ../test9200a.png Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+      git add Å/goo/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z/å/ä/ö/gårdetsågårdet.png &&
+      git commit -a -m "Går det så går det" && \
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (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/"
+      )'
+
+fi
+
+rm -fr tst
+
+test_expect_success \
+     'Mismatching patch should fail' \
+     'date >>"E/newfile5.txt" &&
+      git add "E/newfile5.txt" &&
+      git commit -a -m "Update one" &&
+      date >>"E/newfile5.txt" &&
+      git add "E/newfile5.txt" &&
+      git commit -a -m "Update two" &&
+      id=$(git rev-list --max-count=1 HEAD) &&
+      (cd "$CVSWORK" &&
+      ! git-cvsexportcommit -c $id
+      )'
+
+case "$(git repo-config --bool core.filemode)" in
+false)
+       ;;
+*)
+test_expect_success \
+     'Retain execute bit' \
+     'mkdir G &&
+      echo executeon >G/on &&
+      chmod +x G/on &&
+      echo executeoff >G/off &&
+      git add G/on &&
+      git add G/off &&
+      git commit -a -m "Execute test" &&
+      (cd "$CVSWORK" &&
+      git-cvsexportcommit -c HEAD
+      test -x G/on &&
+      ! test -x G/off
+      )'
+       ;;
+esac
+
 test_done
diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh
new file mode 100755 (executable)
index 0000000..970d683
--- /dev/null
@@ -0,0 +1,496 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Shawn Pearce
+#
+
+test_description='test git-fast-import utility'
+. ./test-lib.sh
+. ../diff-lib.sh ;# test-lib chdir's into trash
+
+file2_data='file2
+second line of EOF'
+
+file3_data='EOF
+in 3rd file
+ END'
+
+file4_data=abcd
+file4_len=4
+
+file5_data='an inline file.
+  we should see it later.'
+
+file6_data='#!/bin/sh
+echo "$@"'
+
+###
+### series A
+###
+
+test_tick
+cat >input <<INPUT_END
+blob
+mark :2
+data <<EOF
+$file2_data
+EOF
+
+blob
+mark :3
+data <<END
+$file3_data
+END
+
+blob
+mark :4
+data $file4_len
+$file4_data
+commit refs/heads/master
+mark :5
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+initial
+COMMIT
+
+M 644 :2 file2
+M 644 :3 file3
+M 755 :4 file4
+
+INPUT_END
+test_expect_success \
+    'A: create pack from stdin' \
+    'git-fast-import --export-marks=marks.out <input &&
+        git-whatchanged master'
+test_expect_success \
+       'A: verify pack' \
+       '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
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+initial
+EOF
+test_expect_success \
+       'A: verify commit' \
+       'git-cat-file commit master | sed 1d >actual &&
+       diff -u expect actual'
+
+cat >expect <<EOF
+100644 blob file2
+100644 blob file3
+100755 blob file4
+EOF
+test_expect_success \
+       'A: verify tree' \
+       'git-cat-file -p master^{tree} | sed "s/ [0-9a-f]*      / /" >actual &&
+        diff -u expect actual'
+
+echo "$file2_data" >expect
+test_expect_success \
+       'A: verify file2' \
+       'git-cat-file blob master:file2 >actual && diff -u expect actual'
+
+echo "$file3_data" >expect
+test_expect_success \
+       'A: verify file3' \
+       'git-cat-file blob master:file3 >actual && diff -u expect actual'
+
+printf "$file4_data" >expect
+test_expect_success \
+       'A: verify file4' \
+       'git-cat-file blob master:file4 >actual && diff -u 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`
+EOF
+test_expect_success \
+       'A: verify marks output' \
+       'diff -u expect marks.out'
+
+###
+### series B
+###
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/branch
+mark :1
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+corrupt
+COMMIT
+
+from refs/heads/master
+M 755 0000000000000000000000000000000000000001 zero1
+
+INPUT_END
+test_expect_failure \
+    'B: fail on invalid blob sha1' \
+    'git-fast-import <input'
+rm -f .git/objects/pack_* .git/objects/index_*
+
+###
+### series C
+###
+
+newf=`echo hi newf | git-hash-object -w --stdin`
+oldf=`git-rev-parse --verify master:file2`
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/branch
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+second
+COMMIT
+
+from refs/heads/master
+M 644 $oldf file2/oldf
+M 755 $newf file2/newf
+D file3
+
+INPUT_END
+test_expect_success \
+    'C: incremental import create pack from stdin' \
+    'git-fast-import <input &&
+        git-whatchanged branch'
+test_expect_success \
+       'C: verify pack' \
+       '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`'
+
+cat >expect <<EOF
+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
+
+second
+EOF
+test_expect_success \
+       'C: verify commit' \
+       'git-cat-file commit branch | sed 1d >actual &&
+        diff -u expect actual'
+
+cat >expect <<EOF
+:000000 100755 0000000000000000000000000000000000000000 f1fb5da718392694d0076d677d6d0e364c79b0bc A     file2/newf
+:100644 100644 7123f7f44e39be127c5eb701e5968176ee9d78b1 7123f7f44e39be127c5eb701e5968176ee9d78b1 R100  file2   file2/oldf
+:100644 000000 0d92e9f3374ae2947c23aa477cbc68ce598135f1 0000000000000000000000000000000000000000 D     file3
+EOF
+git-diff-tree -M -r master branch >actual
+test_expect_success \
+       'C: validate rename result' \
+       'compare_diff_raw expect actual'
+
+###
+### series D
+###
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/branch
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third
+COMMIT
+
+from refs/heads/branch^0
+M 644 inline newdir/interesting
+data <<EOF
+$file5_data
+EOF
+
+M 755 inline newdir/exec.sh
+data <<EOF
+$file6_data
+EOF
+
+INPUT_END
+test_expect_success \
+    'D: inline data in commit' \
+    'git-fast-import <input &&
+        git-whatchanged branch'
+test_expect_success \
+       'D: verify pack' \
+       '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
+test_expect_success \
+       'D: validate new files added' \
+       'compare_diff_raw expect actual'
+
+echo "$file5_data" >expect
+test_expect_success \
+       'D: verify file5' \
+       'git-cat-file blob branch:newdir/interesting >actual &&
+        diff -u expect actual'
+
+echo "$file6_data" >expect
+test_expect_success \
+       'D: verify file6' \
+       'git-cat-file blob branch:newdir/exec.sh >actual &&
+        diff -u expect actual'
+
+###
+### series E
+###
+
+cat >input <<INPUT_END
+commit refs/heads/branch
+author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> Tue Feb 6 11:22:18 2007 -0500
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> Tue Feb 6 12:35:02 2007 -0500
+data <<COMMIT
+RFC 2822 type date
+COMMIT
+
+from refs/heads/branch^0
+
+INPUT_END
+test_expect_failure \
+    'E: rfc2822 date, --date-format=raw' \
+    'git-fast-import --date-format=raw <input'
+test_expect_success \
+    'E: rfc2822 date, --date-format=rfc2822' \
+    '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'
+
+cat >expect <<EOF
+author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> 1170778938 -0500
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1170783302 -0500
+
+RFC 2822 type date
+EOF
+test_expect_success \
+       'E: verify commit' \
+       'git-cat-file commit branch | sed 1,2d >actual &&
+       diff -u expect actual'
+
+###
+### series F
+###
+
+old_branch=`git-rev-parse --verify branch^0`
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/branch
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+losing things already?
+COMMIT
+
+from refs/heads/branch~1
+
+reset refs/heads/other
+from refs/heads/branch
+
+INPUT_END
+test_expect_success \
+    'F: non-fast-forward update skips' \
+    'if git-fast-import <input
+        then
+               echo BAD gfi did not fail
+               return 1
+        else
+               if test $old_branch = `git-rev-parse --verify branch^0`
+               then
+                       : branch unaffected and failure returned
+                       return 0
+               else
+                       echo BAD gfi changed branch $old_branch
+                       return 1
+               fi
+        fi
+       '
+test_expect_success \
+       'F: verify pack' \
+       '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`
+author $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+
+losing things already?
+EOF
+test_expect_success \
+       'F: verify other commit' \
+       'git-cat-file commit other >actual &&
+       diff -u expect actual'
+
+###
+### series G
+###
+
+old_branch=`git-rev-parse --verify branch^0`
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/branch
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+losing things already?
+COMMIT
+
+from refs/heads/branch~1
+
+INPUT_END
+test_expect_success \
+    'G: non-fast-forward update forced' \
+    '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'
+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}`'
+
+###
+### series H
+###
+
+test_tick
+cat >input <<INPUT_END
+commit refs/heads/H
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+third
+COMMIT
+
+from refs/heads/branch^0
+M 644 inline i-will-die
+data <<EOF
+this file will never exist.
+EOF
+
+deleteall
+M 644 inline h/e/l/lo
+data <<EOF
+$file5_data
+EOF
+
+INPUT_END
+test_expect_success \
+    'H: deletall, add 1' \
+    'git-fast-import <input &&
+        git-whatchanged H'
+test_expect_success \
+       'H: verify pack' \
+       'for p in .git/objects/pack/*.pack;do git-verify-pack $p||exit;done'
+
+cat >expect <<EOF
+:100755 000000 f1fb5da718392694d0076d677d6d0e364c79b0bc 0000000000000000000000000000000000000000 D     file2/newf
+:100644 000000 7123f7f44e39be127c5eb701e5968176ee9d78b1 0000000000000000000000000000000000000000 D     file2/oldf
+:100755 000000 85df50785d62d3b05ab03d9cbf7e4a0b49449730 0000000000000000000000000000000000000000 D     file4
+: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
+test_expect_success \
+       'H: validate old files removed, new files added' \
+       'compare_diff_raw expect actual'
+
+echo "$file5_data" >expect
+test_expect_success \
+       'H: verify file' \
+       'git-cat-file blob H:h/e/l/lo >actual &&
+        diff -u expect actual'
+
+###
+### series I
+###
+
+cat >input <<INPUT_END
+commit refs/heads/export-boundary
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+we have a border.  its only 40 characters wide.
+COMMIT
+
+from refs/heads/branch
+
+INPUT_END
+test_expect_success \
+    'I: export-pack-edges' \
+    'git-fast-import --export-pack-edges=edges.list <input'
+
+cat >expect <<EOF
+.git/objects/pack/pack-.pack: `git-rev-parse --verify export-boundary`
+EOF
+test_expect_success \
+       'I: verify edge list' \
+       'sed -e s/pack-.*pack/pack-.pack/ edges.list >actual &&
+        diff -u expect actual'
+
+###
+### series J
+###
+
+cat >input <<INPUT_END
+commit refs/heads/J
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+create J
+COMMIT
+
+from refs/heads/branch
+
+reset refs/heads/J
+
+commit refs/heads/J
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+initialize J
+COMMIT
+
+INPUT_END
+test_expect_success \
+    'J: reset existing branch creates empty commit' \
+    'git-fast-import <input'
+test_expect_success \
+       'J: branch has 1 commit, empty tree' \
+       'test 1 = `git-rev-list J | wc -l` &&
+        test 0 = `git ls-tree J | wc -l`'
+
+###
+### series K
+###
+
+cat >input <<INPUT_END
+commit refs/heads/K
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+create K
+COMMIT
+
+from refs/heads/branch
+
+commit refs/heads/K
+committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
+data <<COMMIT
+redo K
+COMMIT
+
+from refs/heads/branch^1
+
+INPUT_END
+test_expect_success \
+    'K: reinit branch with from' \
+    '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_done
index 3895f167093c6a237ad8337ebf8b3c56ee3700ad..37822fc13df73a8cb53428acc767947bb5844c9e 100755 (executable)
@@ -30,6 +30,8 @@ unset GIT_INDEX_FILE
 unset GIT_OBJECT_DIRECTORY
 unset SHA1_FILE_DIRECTORIES
 unset SHA1_FILE_DIRECTORY
+GIT_MERGE_VERBOSITY=5
+export GIT_MERGE_VERBOSITY
 export GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME
 export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME
 export EDITOR VISUAL
@@ -76,7 +78,8 @@ do
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t; shift ;;
        --no-python)
-               no_python=t; shift ;;
+               # noop now...
+               shift ;;
        *)
                break ;;
        esac
@@ -95,6 +98,17 @@ test_count=0
 
 trap 'echo >&5 "FATAL: Unexpected exit with code $?"; exit 1' exit
 
+test_tick () {
+       if test -z "${test_tick+set}"
+       then
+               test_tick=1112911993
+       else
+               test_tick=$(($test_tick + 60))
+       fi
+       GIT_COMMITTER_DATE="$test_tick -0700"
+       GIT_AUTHOR_DATE="$test_tick -0700"
+       export GIT_COMMITTER_DATE GIT_AUTHOR_DATE
+}
 
 # You are not expected to call test_ok_ and test_failure_ directly, use
 # the text_expect_* functions instead.
@@ -124,16 +138,43 @@ test_run_ () {
        return 0
 }
 
+test_skip () {
+       this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
+       this_test="$this_test.$(expr "$test_count" + 1)"
+       to_skip=
+       for skp in $GIT_SKIP_TESTS
+       do
+               case "$this_test" in
+               $skp)
+                       to_skip=t
+               esac
+       done
+       case "$to_skip" in
+       t)
+               say >&3 "skipping test: $@"
+               test_count=$(expr "$test_count" + 1)
+               say "skip $test_count: $1"
+               : true
+               ;;
+       *)
+               false
+               ;;
+       esac
+}
+
 test_expect_failure () {
        test "$#" = 2 ||
        error "bug in the test script: not 2 parameters to test-expect-failure"
-       say >&3 "expecting failure: $2"
-       test_run_ "$2"
-       if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ]
+       if ! test_skip "$@"
        then
-               test_ok_ "$1"
-       else
-               test_failure_ "$@"
+               say >&3 "expecting failure: $2"
+               test_run_ "$2"
+               if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ]
+               then
+                       test_ok_ "$1"
+               else
+                       test_failure_ "$@"
+               fi
        fi
        echo >&3 ""
 }
@@ -141,13 +182,16 @@ test_expect_failure () {
 test_expect_success () {
        test "$#" = 2 ||
        error "bug in the test script: not 2 parameters to test-expect-success"
-       say >&3 "expecting success: $2"
-       test_run_ "$2"
-       if [ "$?" = 0 -a "$eval_ret" = 0 ]
+       if ! test_skip "$@"
        then
-               test_ok_ "$1"
-       else
-               test_failure_ "$@"
+               say >&3 "expecting success: $2"
+               test_run_ "$2"
+               if [ "$?" = 0 -a "$eval_ret" = 0 ]
+               then
+                       test_ok_ "$1"
+               else
+                       test_failure_ "$@"
+               fi
        fi
        echo >&3 ""
 }
@@ -155,13 +199,16 @@ test_expect_success () {
 test_expect_code () {
        test "$#" = 3 ||
        error "bug in the test script: not 3 parameters to test-expect-code"
-       say >&3 "expecting exit code $1: $3"
-       test_run_ "$3"
-       if [ "$?" = 0 -a "$eval_ret" = "$1" ]
+       if ! test_skip "$@"
        then
-               test_ok_ "$2"
-       else
-               test_failure_ "$@"
+               say >&3 "expecting exit code $1: $3"
+               test_run_ "$3"
+               if [ "$?" = 0 -a "$eval_ret" = "$1" ]
+               then
+                       test_ok_ "$2"
+               else
+                       test_failure_ "$@"
+               fi
        fi
        echo >&3 ""
 }
@@ -175,8 +222,8 @@ test_create_repo () {
        repo="$1"
        mkdir "$repo"
        cd "$repo" || error "Cannot setup test environment"
-       "$GIT_EXEC_PATH/git" init-db --template=$GIT_EXEC_PATH/templates/blt/ 2>/dev/null ||
-       error "cannot run git init-db -- have you built things yet?"
+       "$GIT_EXEC_PATH/git" init --template=$GIT_EXEC_PATH/templates/blt/ >/dev/null 2>&1 ||
+       error "cannot run git init -- have you built things yet?"
        mv .git/hooks .git/hooks-disabled
        cd "$owd"
 }
@@ -207,21 +254,10 @@ test_done () {
 # t/ subdirectory and are run in trash subdirectory.
 PATH=$(pwd)/..:$PATH
 GIT_EXEC_PATH=$(pwd)/..
+GIT_TEMPLATE_DIR=$(pwd)/../templates/blt
 HOME=$(pwd)/trash
-export PATH GIT_EXEC_PATH HOME
-
-# Similarly use ../compat/subprocess.py if our python does not
-# have subprocess.py on its own.
-PYTHON=`sed -e '1{
-       s/^#!//
-       q
-}' ../git-merge-recursive-old` || {
-       error "You haven't built things yet, have you?"
-}
-"$PYTHON" -c 'import subprocess' 2>/dev/null || {
-       PYTHONPATH=$(pwd)/../compat
-       export PYTHONPATH
-}
+export PATH GIT_EXEC_PATH GIT_TEMPLATE_DIR HOME
+
 GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
 export GITPERLLIB
 test -d ../templates/blt || {
@@ -233,3 +269,22 @@ test=trash
 rm -fr "$test"
 test_create_repo $test
 cd "$test"
+
+this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$')
+for skp in $GIT_SKIP_TESTS
+do
+       to_skip=
+       for skp in $GIT_SKIP_TESTS
+       do
+               case "$this_test" in
+               $skp)
+                       to_skip=t
+               esac
+       done
+       case "$to_skip" in
+       t)
+               say >&3 "skipping test $this_test altogether"
+               say "skip all tests in $this_test"
+               test_done
+       esac
+done
index 9e1ae1a4e0322a37b494177474a964caf05ca4dd..0eeee43feb97ac79406d196c735e4027c4e9cdfd 100644 (file)
@@ -6,7 +6,7 @@ prefix ?= $(HOME)
 template_dir ?= $(prefix)/share/git-core/templates/
 # DESTDIR=
 
-# Shell quote (do not use $(call) to accomodate ancient setups);
+# Shell quote (do not use $(call) to accommodate ancient setups);
 DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
 template_dir_SQ = $(subst ','\'',$(template_dir))
 
index 0b906caa98f3d4deced75d196d969d7216d31961..9b04f2d69c6dc054e8e5403062fb7ef5d2117328 100644 (file)
@@ -8,6 +8,10 @@
 #
 # To enable this hook, make this file executable.
 
+# Uncomment the below to add a Signed-off-by line to the message.
+# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
+# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"
+
 # This example catches duplicate Signed-off-by lines.
 
 test "" = "$(grep '^Signed-off-by: ' "$1" |
index 76d5ac2477d669aabfef8108cfbb713fbdfef70a..e8c536fb616d9fe93fe50edf4bc862b58ad6af1b 100644 (file)
 #!/bin/sh
 #
 # An example hook script to mail out commit update information.
-# It also blocks tags that aren't annotated.
+# It can also blocks tags that aren't annotated.
 # Called by git-receive-pack with arguments: refname sha1-old sha1-new
 #
-# To enable this hook:
-# (1) change the recipient e-mail address
-# (2) make this file executable by "chmod +x update".
+# To enable this hook, make this file executable by "chmod +x update".
 #
+# Config
+# ------
+# hooks.mailinglist
+#   This is the list that all pushes will go to; leave it blank to not send
+#   emails frequently.  The log email will list every log entry in full between
+#   the old ref value and the new ref value.
+# hooks.announcelist
+#   This is the list that all pushes of annotated tags will go to.  Leave it
+#   blank to just use the mailinglist field.  The announce emails list the
+#   short log summary of the changes since the last annotated tag
+# hooks.allowunannotated
+#   This boolean sets whether unannotated tags will be allowed into the
+#   repository.  By default they won't be.
+#
+# Notes
+# -----
+# All emails have their subjects prefixed with "[SCM]" to aid filtering.
+# All emails include the headers "X-Git-Refname", "X-Git-Oldrev",
+# "X-Git-Newrev", and "X-Git-Reftype" to enable fine tuned filtering and info.
 
-project=$(cat $GIT_DIR/description)
-recipients="commit-list@somewhere.com commit-list@somewhereelse.com"
-
-ref_type=$(git cat-file -t "$3")
-
-# Only allow annotated tags in a shared repo
-# Remove this code to treat dumb tags the same as everything else
-case "$1","$ref_type" in
-refs/tags/*,commit)
-       echo "*** Un-annotated tags are not allowed in this repo" >&2
-       echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate."
-       exit 1;;
-refs/tags/*,tag)
-       echo "### Pushing version '${1##refs/tags/}' to the masses" >&2
-       # recipients="release-announce@somwehere.com announce@somewhereelse.com"
-       ;;
-esac
+# --- Constants
+EMAILPREFIX="[SCM] "
+LOGBEGIN="- Log -----------------------------------------------------------------"
+LOGEND="-----------------------------------------------------------------------"
+DATEFORMAT="%F %R %z"
+
+# --- Command line
+refname="$1"
+oldrev="$2"
+newrev="$3"
+
+# --- Safety check
+if [ -z "$GIT_DIR" ]; then
+       echo "Don't run this script from the command line." >&2
+       echo " (if you want, you could supply GIT_DIR then run" >&2
+       echo "  $0 <ref> <oldrev> <newrev>)" >&2
+       exit 1
+fi
+
+if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then
+       echo "Usage: $0 <ref> <oldrev> <newrev>" >&2
+       exit 1
+fi
+
+# --- Config
+projectdesc=$(cat $GIT_DIR/description)
+recipients=$(git-repo-config hooks.mailinglist)
+announcerecipients=$(git-repo-config hooks.announcelist)
+allowunannotated=$(git-repo-config --bool hooks.allowunannotated)
 
-# set this  to 'cat' to get a very detailed listing.
-# short only kicks in when an annotated tag is added
-short='git shortlog'
-
-# see 'date --help' for info on how to write this
-# The default is a human-readable iso8601-like format with minute
-# precision ('2006-01-25 15:58 +0100' for example)
-date_format="%F %R %z"
-
-(if expr "$2" : '0*$' >/dev/null
-then
-       # new ref
-       case "$1" in
-       refs/tags/*)
-               # a pushed and annotated tag (usually) means a new version
-               tag="${1##refs/tags/}"
-               if [ "$ref_type" = tag ]; then
-                       eval $(git cat-file tag $3 | \
-                               sed -n '4s/tagger \([^>]*>\)[^0-9]*\([0-9]*\).*/tagger="\1" ts="\2"/p')
-                       date=$(date --date="1970-01-01 00:00:00 $ts seconds" +"$date_format")
-                       echo "Tag '$tag' created by $tagger at $date"
-                       git cat-file tag $3 | sed -n '5,$p'
-                       echo
+# --- Check types
+newrev_type=$(git-cat-file -t "$newrev")
+
+case "$refname","$newrev_type" in
+       refs/tags/*,commit)
+               # un-annotated tag
+               refname_type="tag"
+               short_refname=${refname##refs/tags/}
+               if [ $allowunannotated != "true" ]; then
+                       echo "*** The un-annotated tag, $short_refname is not allowed in this repository" >&2
+                       echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2
+                       exit 1
                fi
-               prev=$(git describe "$3^" | sed 's/-g.*//')
-               # the first tag in a repo will yield no $prev
-               if [ -z "$prev" ]; then
-                       echo "Changes since the dawn of time:"
-                       git rev-list --pretty $3 | $short
-               else
-                       echo "Changes since $prev:"
-                       git rev-list --pretty $prev..$3 | $short
-                       echo ---
-                       git diff --stat $prev..$3
-                       echo ---
+               ;;
+       refs/tags/*,tag)
+               # annotated tag
+               refname_type="annotated tag"
+               short_refname=${refname##refs/tags/}
+               # change recipients
+               if [ -n "$announcerecipients" ]; then
+                       recipients="$announcerecipients"
                fi
                ;;
+       refs/heads/*,commit)
+               # branch
+               refname_type="branch"
+               short_refname=${refname##refs/heads/}
+               ;;
+       refs/remotes/*,commit)
+               # tracking branch
+               refname_type="tracking branch"
+               short_refname=${refname##refs/remotes/}
+               # Should this even be allowed?
+               echo "*** Push-update of tracking branch, $refname.  No email generated." >&2
+               exit 0
+               ;;
+       *)
+               # Anything else (is there anything else?)
+               echo "*** Update hook: unknown type of update, \"$newrev_type\", to ref $refname" >&2
+               exit 1
+               ;;
+esac
+
+# Check if we've got anyone to send to
+if [ -z "$recipients" ]; then
+       # If the email isn't sent, then at least give the user some idea of what command
+       # would generate the email at a later date
+       echo "*** No recipients found - no email will be sent, but the push will continue" >&2
+       echo "*** for $0 $1 $2 $3" >&2
+       exit 0
+fi
+
+# --- Email parameters
+committer=$(git show --pretty=full -s $newrev | grep "^Commit: " | sed -e "s/^Commit: //")
+describe=$(git describe $newrev 2>/dev/null)
+if [ -z "$describe" ]; then
+       describe=$newrev
+fi
 
-       refs/heads/*)
-               branch="${1##refs/heads/}"
-               echo "New branch '$branch' available with the following commits:"
-               git-rev-list --pretty "$3" $(git-rev-parse --not --all)
+# --- Email (all stdout will be the email)
+(
+# Generate header
+cat <<-EOF
+From: $committer
+To: $recipients
+Subject: ${EMAILPREFIX}$projectdesc $refname_type, $short_refname now at $describe
+X-Git-Refname: $refname
+X-Git-Reftype: $refname_type
+X-Git-Oldrev: $oldrev
+X-Git-Newrev: $newrev
+
+Hello,
+
+This is an automated email from the git hooks/update script, it was
+generated because a ref change was pushed to the repository.
+
+Updating $refname_type, $short_refname,
+EOF
+
+case "$refname_type" in
+       "tracking branch"|branch)
+               if expr "$oldrev" : '0*$' >/dev/null
+               then
+                       # If the old reference is "0000..0000" then this is a new branch
+                       # and so oldrev is not valid
+                       echo "  as a new  $refname_type"
+                   echo "        to  $newrev ($newrev_type)"
+                       echo ""
+                       echo $LOGBEGIN
+                       # This shows all log entries that are not already covered by
+                       # another ref - i.e. commits that are now accessible from this
+                       # ref that were previously not accessible
+                       git-rev-parse --not --all | git-rev-list --stdin --pretty $newref
+                       echo $LOGEND
+               else
+                       # oldrev is valid
+                       oldrev_type=$(git-cat-file -t "$oldrev")
+
+                       # Now the problem is for cases like this:
+                       #   * --- * --- * --- * (oldrev)
+                       #          \
+                       #           * --- * --- * (newrev)
+                       # i.e. there is no guarantee that newrev is a strict subset
+                       # of oldrev - (would have required a force, but that's allowed).
+                       # So, we can't simply say rev-list $oldrev..$newrev.  Instead
+                       # we find the common base of the two revs and list from there
+                       baserev=$(git-merge-base $oldrev $newrev)
+
+                       # Commit with a parent
+                       for rev in $(git-rev-list $newrev ^$baserev)
+                       do
+                               revtype=$(git-cat-file -t "$rev")
+                               echo "       via  $rev ($revtype)"
+                       done
+                       if [ "$baserev" = "$oldrev" ]; then
+                               echo "      from  $oldrev ($oldrev_type)"
+                       else
+                               echo "  based on  $baserev"
+                               echo "      from  $oldrev ($oldrev_type)"
+                               echo ""
+                               echo "This ref update crossed a branch point; i.e. the old rev is not a strict subset"
+                               echo "of the new rev.  This occurs, when you --force push a change in a situation"
+                               echo "like this:"
+                               echo ""
+                               echo " * -- * -- B -- O -- O -- O ($oldrev)"
+                               echo "            \\"
+                               echo "             N -- N -- N ($newrev)"
+                               echo ""
+                               echo "Therefore, we assume that you've already had alert emails for all of the O"
+                               echo "revisions, and now give you all the revisions in the N branch from the common"
+                               echo "base, B ($baserev), up to the new revision."
+                       fi
+                       echo ""
+                       echo $LOGBEGIN
+                       git-rev-list --pretty $newrev ^$baserev
+                       echo $LOGEND
+                       echo ""
+                       echo "Diffstat:"
+                       git-diff-tree --no-color --stat -M -C --find-copies-harder $newrev ^$baserev
+               fi
                ;;
-       esac
-else
-       base=$(git-merge-base "$2" "$3")
-       case "$base" in
-       "$2")
-               git diff --stat "$3" "^$base"
-               echo
-               echo "New commits:"
+       "annotated tag")
+               # Should we allow changes to annotated tags?
+               if expr "$oldrev" : '0*$' >/dev/null
+               then
+                       # If the old reference is "0000..0000" then this is a new atag
+                       # and so oldrev is not valid
+                       echo "        to  $newrev ($newrev_type)"
+               else
+                       echo "        to  $newrev ($newrev_type)"
+                       echo "      from  $oldrev"
+               fi
+
+               # If this tag succeeds another, then show which tag it replaces
+               prevtag=$(git describe $newrev^ 2>/dev/null | sed 's/-g.*//')
+               if [ -n "$prevtag" ]; then
+                       echo "  replaces  $prevtag"
+               fi
+
+               # Read the tag details
+               eval $(git cat-file tag $newrev | \
+                       sed -n '4s/tagger \([^>]*>\)[^0-9]*\([0-9]*\).*/tagger="\1" ts="\2"/p')
+               tagged=$(date --date="1970-01-01 00:00:00 +0000 $ts seconds" +"$DATEFORMAT")
+
+               echo " tagged by  $tagger"
+               echo "        on  $tagged"
+
+               echo ""
+               echo $LOGBEGIN
+               echo ""
+
+               if [ -n "$prevtag" ]; then
+                       git rev-list --pretty=short "$prevtag..$newrev" | git shortlog
+               else
+                       git rev-list --pretty=short $newrev | git shortlog
+               fi
+
+               echo $LOGEND
+               echo ""
                ;;
        *)
-               echo "Rebased ref, commits from common ancestor:"
+               # By default, unannotated tags aren't allowed in; if
+               # they are though, it's debatable whether we would even want an
+               # email to be generated; however, I don't want to add another config
+               # option just for that.
+               #
+               # Unannotated tags are more about marking a point than releasing
+               # a version; therefore we don't do the shortlog summary that we
+               # do for annotated tags above - we simply show that the point has
+               # been marked, and print the log message for the marked point for
+               # reference purposes
+               #
+               # Note this section also catches any other reference type (although
+               # there aren't any) and deals with them in the same way.
+               if expr "$oldrev" : '0*$' >/dev/null
+               then
+                       # If the old reference is "0000..0000" then this is a new tag
+                       # and so oldrev is not valid
+                       echo "  as a new  $refname_type"
+                       echo "        to  $newrev ($newrev_type)"
+               else
+                       echo "        to  $newrev ($newrev_type)"
+                       echo "      from  $oldrev"
+               fi
+               echo ""
+               echo $LOGBEGIN
+               git-show --no-color --root -s $newrev
+               echo $LOGEND
+               echo ""
                ;;
-       esac
-       git-rev-list --pretty "$3" "^$base"
-fi) |
-mail -s "$project: Changes to '${1##refs/heads/}'" $recipients
+esac
+
+# Footer
+cat <<-EOF
+
+hooks/update
+---
+Git Source Code Management System
+$0 $1 \\
+  $2 \\
+  $3
+EOF
+#) | cat >&2
+) | /usr/sbin/sendmail -t
+
+# --- Finished
 exit 0
diff --git a/templates/remotes-- b/templates/remotes--
deleted file mode 100644 (file)
index fae8870..0000000
+++ /dev/null
@@ -1 +0,0 @@
-: this is just to ensure the directory exists.
index 93e802759f1906665857c82c71f589b7d636b540..62e8f2387a1cab97ec1c71d1993d082274e17bf5 100644 (file)
@@ -1,6 +1,3 @@
-#include <stdio.h>
-#include <time.h>
-
 #include "cache.h"
 
 int main(int argc, char **argv)
index 1be8ee0c721ec35372fee4e686d9664b143b4345..16595ef0a9022b1a9c677a36205526eb3567a93d 100644 (file)
@@ -8,13 +8,7 @@
  * published by the Free Software Foundation.
  */
 
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <fcntl.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
+#include "git-compat-util.h"
 #include "delta.h"
 
 static const char usage[] =
@@ -74,7 +68,7 @@ int main(int argc, char *argv[])
        }
 
        fd = open (argv[4], O_WRONLY|O_CREAT|O_TRUNC, 0666);
-       if (fd < 0 || write(fd, out_buf, out_size) != out_size) {
+       if (fd < 0 || write_in_full(fd, out_buf, out_size) != out_size) {
                perror(argv[4]);
                return 1;
        }
diff --git a/trace.c b/trace.c
index 495e5ed92a68837a8abbd1569b2ccb59a3d50429..27fef868c4e2276638873cbda7d353cf4b8f67ed 100644 (file)
--- a/trace.c
+++ b/trace.c
@@ -101,7 +101,7 @@ void trace_printf(const char *format, ...)
        nfvasprintf(&trace_str, format, rest);
        va_end(rest);
 
-       write_or_whine(fd, trace_str, strlen(trace_str), err_msg);
+       write_or_whine_pipe(fd, trace_str, strlen(trace_str), err_msg);
 
        free(trace_str);
 
@@ -139,7 +139,7 @@ void trace_argv_printf(const char **argv, int count, const char *format, ...)
        strncpy(trace_str + format_len, argv_str, argv_len);
        strcpy(trace_str + trace_len - 1, "\n");
 
-       write_or_whine(fd, trace_str, trace_len, err_msg);
+       write_or_whine_pipe(fd, trace_str, trace_len, err_msg);
 
        free(argv_str);
        free(format_str);
index 14cc5aea6c4eefbf4b0fa9c72ccb70ec550b44b4..70f899957e8ce511f0f5a470e8b6927d58d1b63e 100644 (file)
@@ -113,7 +113,6 @@ void traverse_trees(int n, struct tree_desc *t, const char *base, traverse_callb
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
 
        for (;;) {
-               struct name_entry entry[3];
                unsigned long mask = 0;
                int i, last;
 
@@ -200,10 +199,17 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
        int retval;
        void *tree;
        struct tree_desc t;
+       unsigned char root[20];
 
-       tree = read_object_with_reference(tree_sha1, tree_type, &t.size, NULL);
+       tree = read_object_with_reference(tree_sha1, tree_type, &t.size, root);
        if (!tree)
                return -1;
+
+       if (name[0] == '\0') {
+               hashcpy(sha1, root);
+               return 0;
+       }
+
        t.buf = tree;
        retval = find_tree_entry(&t, name, sha1, mode);
        free(tree);
diff --git a/tree.c b/tree.c
index ea386e506659c65224cfe55bdc4f8d086171637a..b6f02fecc46ec4633dc1ee75f38bc90761a4fbe3 100644 (file)
--- a/tree.c
+++ b/tree.c
@@ -4,7 +4,6 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree-walk.h"
-#include <stdlib.h>
 
 const char *tree_type = "tree";
 
index ccddf1d4b0cf7fd3a699d8b33cf5bc4c5c4435b7..d24acc2a67c4b7ba112bd192680b137f30a06003 100644 (file)
@@ -17,7 +17,7 @@ static char *create_temp_file(unsigned char *sha1)
        fd = mkstemp(path);
        if (fd < 0)
                die("unable to create temp-file");
-       if (write(fd, buf, size) != size)
+       if (write_in_full(fd, buf, size) != size)
                die("unable to write temp-file");
        close(fd);
        return path;
index 7cfd628d8e1c576115cb934e72138535fb7d2db3..2e2232cbb07e61de3be74302fba67142a58a857b 100644 (file)
@@ -1,6 +1,5 @@
-#include <signal.h>
-#include <sys/time.h>
 #include "cache.h"
+#include "dir.h"
 #include "tree.h"
 #include "tree-walk.h"
 #include "cache-tree.h"
@@ -77,6 +76,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
 {
        int baselen = strlen(base);
        int src_size = len + 1;
+       int i_stk = i_stk;
+       int retval = 0;
+
+       if (o->dir)
+               i_stk = push_exclude_per_directory(o->dir, base, strlen(base));
+
        do {
                int i;
                const char *first;
@@ -143,7 +148,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                }
                /* No name means we're done */
                if (!first)
-                       return 0;
+                       goto leave_directory;
 
                pathlen = strlen(first);
                ce_size = cache_entry_size(baselen + pathlen);
@@ -240,13 +245,20 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                        newbase[baselen + pathlen] = '/';
                        newbase[baselen + pathlen + 1] = '\0';
                        if (unpack_trees_rec(subposns, len, newbase, o,
-                                            indpos, df_conflict_list))
-                               return -1;
+                                            indpos, df_conflict_list)) {
+                               retval = -1;
+                               goto leave_directory;
+                       }
                        free(newbase);
                }
                free(subposns);
                free(src);
        } while (1);
+
+ leave_directory:
+       if (o->dir)
+               pop_exclude_per_directory(o->dir, i_stk);
+       return retval;
 }
 
 /* Unlink the last component and attempt to remove leading
@@ -370,7 +382,7 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
        int i;
        struct object_list *posn = trees;
        struct tree_entry_list df_conflict_list;
-       struct cache_entry df_conflict_entry;
+       static struct cache_entry *dfc;
 
        memset(&df_conflict_list, 0, sizeof(df_conflict_list));
        df_conflict_list.next = &df_conflict_list;
@@ -381,8 +393,10 @@ int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
        state.refresh_cache = 1;
 
        o->merge_size = len;
-       memset(&df_conflict_entry, 0, sizeof(df_conflict_entry));
-       o->df_conflict_entry = &df_conflict_entry;
+
+       if (!dfc)
+               dfc = xcalloc(1, sizeof(struct cache_entry) + 1);
+       o->df_conflict_entry = dfc;
 
        if (len) {
                posns = xmalloc(len * sizeof(struct tree_entry_list *));
@@ -456,7 +470,7 @@ static void invalidate_ce_path(struct cache_entry *ce)
 
 /*
  * We do not want to remove or overwrite a working tree file that
- * is not tracked.
+ * is not tracked, unless it is ignored.
  */
 static void verify_absent(const char *path, const char *action,
                struct unpack_trees_options *o)
@@ -465,7 +479,7 @@ static void verify_absent(const char *path, const char *action,
 
        if (o->index_only || o->reset || !o->update)
                return;
-       if (!lstat(path, &st))
+       if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
                die("Untracked working tree file '%s' "
                    "would be %s by merge.", path, action);
 }
index c4601621cd71ac6e750c90bd3ac4b426d8e12369..191f7442f10683c8043288eece36f39166fedc95 100644 (file)
@@ -16,6 +16,7 @@ struct unpack_trees_options {
        int verbose_update;
        int aggressive;
        const char *prefix;
+       struct dir_struct *dir;
        merge_fn_t fn;
 
        int head_idx;
index ddaa72f0a98e9b7f424279b74798bc3cfaadbd48..3648aae1a777aec4bba77b40b9cf6da484190441 100644 (file)
@@ -1,6 +1,3 @@
-#include <signal.h>
-#include <sys/wait.h>
-#include <sys/poll.h>
 #include "cache.h"
 #include "refs.h"
 #include "pkt-line.h"
@@ -9,12 +6,25 @@
 #include "object.h"
 #include "commit.h"
 #include "exec_cmd.h"
+#include "diff.h"
+#include "revision.h"
+#include "list-objects.h"
 
 static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>";
 
-#define THEY_HAVE (1U << 0)
-#define OUR_REF (1U << 1)
-#define WANTED (1U << 2)
+/* bits #0..7 in revision.h, #8..10 in commit.c */
+#define THEY_HAVE      (1u << 11)
+#define OUR_REF                (1u << 12)
+#define WANTED         (1u << 13)
+#define COMMON_KNOWN   (1u << 14)
+#define REACHABLE      (1u << 15)
+
+#define SHALLOW                (1u << 16)
+#define NOT_SHALLOW    (1u << 17)
+#define CLIENT_SHALLOW (1u << 18)
+
+static unsigned long oldest_have;
+
 static int multi_ack, nr_our_refs;
 static int use_thin_pack, use_ofs_delta;
 static struct object_array have_obj;
@@ -45,12 +55,47 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
                /* emergency quit */
                fd = 2;
        if (fd == 2) {
+               /* XXX: are we happy to lose stuff here? */
                xwrite(fd, data, sz);
                return sz;
        }
        return safe_write(fd, data, sz);
 }
 
+FILE *pack_pipe = NULL;
+static void show_commit(struct commit *commit)
+{
+       if (commit->object.flags & BOUNDARY)
+               fputc('-', pack_pipe);
+       if (fputs(sha1_to_hex(commit->object.sha1), pack_pipe) < 0)
+               die("broken output pipe");
+       fputc('\n', pack_pipe);
+       fflush(pack_pipe);
+       free(commit->buffer);
+       commit->buffer = NULL;
+}
+
+static void show_object(struct object_array_entry *p)
+{
+       /* An object with name "foo\n0000000..." can be used to
+        * confuse downstream git-pack-objects very badly.
+        */
+       const char *ep = strchr(p->name, '\n');
+       if (ep) {
+               fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(p->item->sha1),
+                      (int) (ep - p->name),
+                      p->name);
+       }
+       else
+               fprintf(pack_pipe, "%s %s\n",
+                               sha1_to_hex(p->item->sha1), p->name);
+}
+
+static void show_edge(struct commit *commit)
+{
+       fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1));
+}
+
 static void create_pack_file(void)
 {
        /* Pipes between rev-list to pack-objects, pack-objects to us
@@ -72,48 +117,40 @@ static void create_pack_file(void)
 
        if (!pid_rev_list) {
                int i;
-               int args;
-               const char **argv;
-               const char **p;
-               char *buf;
+               struct rev_info revs;
 
-               if (create_full_pack) {
-                       args = 10;
-                       use_thin_pack = 0; /* no point doing it */
-               }
-               else
-                       args = have_obj.nr + want_obj.nr + 5;
-               p = xmalloc(args * sizeof(char *));
-               argv = (const char **) p;
-               buf = xmalloc(args * 45);
+               pack_pipe = fdopen(lp_pipe[1], "w");
 
-               dup2(lp_pipe[1], 1);
-               close(0);
-               close(lp_pipe[0]);
-               close(lp_pipe[1]);
-               *p++ = "rev-list";
-               *p++ = use_thin_pack ? "--objects-edge" : "--objects";
                if (create_full_pack)
-                       *p++ = "--all";
-               else {
+                       use_thin_pack = 0; /* no point doing it */
+               init_revisions(&revs, NULL);
+               revs.tag_objects = 1;
+               revs.tree_objects = 1;
+               revs.blob_objects = 1;
+               if (use_thin_pack)
+                       revs.edge_hint = 1;
+
+               if (create_full_pack) {
+                       const char *args[] = {"rev-list", "--all", NULL};
+                       setup_revisions(2, args, &revs, NULL);
+               } else {
                        for (i = 0; i < want_obj.nr; i++) {
                                struct object *o = want_obj.objects[i].item;
-                               *p++ = buf;
-                               memcpy(buf, sha1_to_hex(o->sha1), 41);
-                               buf += 41;
+                               /* why??? */
+                               o->flags &= ~UNINTERESTING;
+                               add_pending_object(&revs, o, NULL);
                        }
-               }
-               if (!create_full_pack)
                        for (i = 0; i < have_obj.nr; i++) {
                                struct object *o = have_obj.objects[i].item;
-                               *p++ = buf;
-                               *buf++ = '^';
-                               memcpy(buf, sha1_to_hex(o->sha1), 41);
-                               buf += 41;
+                               o->flags |= UNINTERESTING;
+                               add_pending_object(&revs, o, NULL);
                        }
-               *p++ = NULL;
-               execv_git_cmd(argv);
-               die("git-upload-pack: unable to exec git-rev-list");
+                       setup_revisions(0, NULL, &revs, NULL);
+               }
+               prepare_revision_walk(&revs);
+               mark_edges_uninteresting(revs.commits, &revs, show_edge);
+               traverse_commit_list(&revs, show_commit, show_object);
+               exit(0);
        }
 
        if (pipe(pu_pipe) < 0)
@@ -206,7 +243,7 @@ static void create_pack_file(void)
                                        *cp++ = buffered;
                                        outsz++;
                                }
-                               sz = read(pu_pipe[0], cp,
+                               sz = xread(pu_pipe[0], cp,
                                          sizeof(data) - outsz);
                                if (0 < sz)
                                                ;
@@ -231,7 +268,7 @@ static void create_pack_file(void)
                                /* Status ready; we ship that in the side-band
                                 * or dump to the standard error.
                                 */
-                               sz = read(pe_pipe[0], progress,
+                               sz = xread(pe_pipe[0], progress,
                                          sizeof(progress));
                                if (0 < sz)
                                        send_client_data(2, progress, sz);
@@ -303,11 +340,12 @@ static void create_pack_file(void)
 static int got_sha1(char *hex, unsigned char *sha1)
 {
        struct object *o;
+       int we_knew_they_have = 0;
 
        if (get_sha1_hex(hex, sha1))
                die("git-upload-pack: expected SHA1 object, got '%s'", hex);
        if (!has_sha1_file(sha1))
-               return 0;
+               return -1;
 
        o = lookup_object(sha1);
        if (!(o && o->parsed))
@@ -316,15 +354,84 @@ static int got_sha1(char *hex, unsigned char *sha1)
                die("oops (%s)", sha1_to_hex(sha1));
        if (o->type == OBJ_COMMIT) {
                struct commit_list *parents;
+               struct commit *commit = (struct commit *)o;
                if (o->flags & THEY_HAVE)
-                       return 0;
-               o->flags |= THEY_HAVE;
-               for (parents = ((struct commit*)o)->parents;
+                       we_knew_they_have = 1;
+               else
+                       o->flags |= THEY_HAVE;
+               if (!oldest_have || (commit->date < oldest_have))
+                       oldest_have = commit->date;
+               for (parents = commit->parents;
                     parents;
                     parents = parents->next)
                        parents->item->object.flags |= THEY_HAVE;
        }
-       add_object_array(o, NULL, &have_obj);
+       if (!we_knew_they_have) {
+               add_object_array(o, NULL, &have_obj);
+               return 1;
+       }
+       return 0;
+}
+
+static int reachable(struct commit *want)
+{
+       struct commit_list *work = NULL;
+
+       insert_by_date(want, &work);
+       while (work) {
+               struct commit_list *list = work->next;
+               struct commit *commit = work->item;
+               free(work);
+               work = list;
+
+               if (commit->object.flags & THEY_HAVE) {
+                       want->object.flags |= COMMON_KNOWN;
+                       break;
+               }
+               if (!commit->object.parsed)
+                       parse_object(commit->object.sha1);
+               if (commit->object.flags & REACHABLE)
+                       continue;
+               commit->object.flags |= REACHABLE;
+               if (commit->date < oldest_have)
+                       continue;
+               for (list = commit->parents; list; list = list->next) {
+                       struct commit *parent = list->item;
+                       if (!(parent->object.flags & REACHABLE))
+                               insert_by_date(parent, &work);
+               }
+       }
+       want->object.flags |= REACHABLE;
+       clear_commit_marks(want, REACHABLE);
+       free_commit_list(work);
+       return (want->object.flags & COMMON_KNOWN);
+}
+
+static int ok_to_give_up(void)
+{
+       int i;
+
+       if (!have_obj.nr)
+               return 0;
+
+       for (i = 0; i < want_obj.nr; i++) {
+               struct object *want = want_obj.objects[i].item;
+
+               if (want->flags & COMMON_KNOWN)
+                       continue;
+               want = deref_tag(want, "a want line", 0);
+               if (!want || want->type != OBJ_COMMIT) {
+                       /* no way to tell if this is reachable by
+                        * looking at the ancestry chain alone, so
+                        * leave a note to ourselves not to worry about
+                        * this object anymore.
+                        */
+                       want_obj.objects[i].item->flags |= COMMON_KNOWN;
+                       continue;
+               }
+               if (!reachable((struct commit *)want))
+                       return 0;
+       }
        return 1;
 }
 
@@ -349,7 +456,13 @@ static int get_common_commits(void)
                }
                len = strip(line, len);
                if (!strncmp(line, "have ", 5)) {
-                       if (got_sha1(line+5, sha1)) {
+                       switch (got_sha1(line+5, sha1)) {
+                       case -1: /* they have what we do not */
+                               if (multi_ack && ok_to_give_up())
+                                       packet_write(1, "ACK %s continue\n",
+                                                    sha1_to_hex(sha1));
+                               break;
+                       default:
                                memcpy(hex, sha1_to_hex(sha1), 41);
                                if (multi_ack) {
                                        const char *msg = "ACK %s continue\n";
@@ -358,6 +471,7 @@ static int get_common_commits(void)
                                }
                                else if (have_obj.nr == 1)
                                        packet_write(1, "ACK %s\n", hex);
+                               break;
                        }
                        continue;
                }
@@ -376,8 +490,9 @@ static int get_common_commits(void)
 
 static void receive_needs(void)
 {
+       struct object_array shallows = {0, 0, NULL};
        static char line[1000];
-       int len;
+       int len, depth = 0;
 
        for (;;) {
                struct object *o;
@@ -385,8 +500,29 @@ static void receive_needs(void)
                len = packet_read_line(0, line, sizeof(line));
                reset_timeout();
                if (!len)
-                       return;
+                       break;
 
+               if (!strncmp("shallow ", line, 8)) {
+                       unsigned char sha1[20];
+                       struct object *object;
+                       use_thin_pack = 0;
+                       if (get_sha1(line + 8, sha1))
+                               die("invalid shallow line: %s", line);
+                       object = parse_object(sha1);
+                       if (!object)
+                               die("did not find object for %s", line);
+                       object->flags |= CLIENT_SHALLOW;
+                       add_object_array(object, NULL, &shallows);
+                       continue;
+               }
+               if (!strncmp("deepen ", line, 7)) {
+                       char *end;
+                       use_thin_pack = 0;
+                       depth = strtol(line + 7, &end, 0);
+                       if (end == line + 7 || depth <= 0)
+                               die("Invalid deepen: %s", line);
+                       continue;
+               }
                if (strncmp("want ", line, 5) ||
                    get_sha1_hex(line+5, sha1_buf))
                        die("git-upload-pack: protocol error, "
@@ -418,11 +554,58 @@ static void receive_needs(void)
                        add_object_array(o, NULL, &want_obj);
                }
        }
+       if (depth == 0 && shallows.nr == 0)
+               return;
+       if (depth > 0) {
+               struct commit_list *result, *backup;
+               int i;
+               backup = result = get_shallow_commits(&want_obj, depth,
+                       SHALLOW, NOT_SHALLOW);
+               while (result) {
+                       struct object *object = &result->item->object;
+                       if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) {
+                               packet_write(1, "shallow %s",
+                                               sha1_to_hex(object->sha1));
+                               register_shallow(object->sha1);
+                       }
+                       result = result->next;
+               }
+               free_commit_list(backup);
+               for (i = 0; i < shallows.nr; i++) {
+                       struct object *object = shallows.objects[i].item;
+                       if (object->flags & NOT_SHALLOW) {
+                               struct commit_list *parents;
+                               packet_write(1, "unshallow %s",
+                                       sha1_to_hex(object->sha1));
+                               object->flags &= ~CLIENT_SHALLOW;
+                               /* make sure the real parents are parsed */
+                               unregister_shallow(object->sha1);
+                               object->parsed = 0;
+                               parse_commit((struct commit *)object);
+                               parents = ((struct commit *)object)->parents;
+                               while (parents) {
+                                       add_object_array(&parents->item->object,
+                                                       NULL, &want_obj);
+                                       parents = parents->next;
+                               }
+                       }
+                       /* make sure commit traversal conforms to client */
+                       register_shallow(object->sha1);
+               }
+               packet_flush(1);
+       } else
+               if (shallows.nr > 0) {
+                       int i;
+                       for (i = 0; i < shallows.nr; i++)
+                               register_shallow(shallows.objects[i].item->sha1);
+               }
+       free(shallows.objects);
 }
 
 static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data)
 {
-       static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta";
+       static const char *capabilities = "multi_ack thin-pack side-band"
+               " side-band-64k ofs-delta shallow";
        struct object *o = parse_object(sha1);
 
        if (!o)
@@ -489,7 +672,8 @@ int main(int argc, char **argv)
 
        if (!enter_repo(dir, strict))
                die("'%s': unable to chdir or not a git archive", dir);
-
+       if (is_repository_shallow())
+               die("attempt to fetch/clone from a shallow repository");
        upload_pack();
        return 0;
 }
diff --git a/usage.c b/usage.c
index 52c2e960560531b62a28a8a716e531093fd5e8a7..4dc5c77633747e0c2b3a4d04fddbd043f8f49e1e 100644 (file)
--- a/usage.c
+++ b/usage.c
@@ -29,12 +29,17 @@ static void error_builtin(const char *err, va_list params)
        report("error: ", err, params);
 }
 
+static void warn_builtin(const char *warn, va_list params)
+{
+       report("warning: ", warn, params);
+}
 
 /* If we are in a dlopen()ed .so write to a global variable would segfault
  * (ugh), so keep things static. */
 static void (*usage_routine)(const char *err) NORETURN = usage_builtin;
 static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin;
 static void (*error_routine)(const char *err, va_list params) = error_builtin;
+static void (*warn_routine)(const char *err, va_list params) = warn_builtin;
 
 void set_usage_routine(void (*routine)(const char *err) NORETURN)
 {
@@ -51,6 +56,11 @@ void set_error_routine(void (*routine)(const char *err, va_list params))
        error_routine = routine;
 }
 
+void set_warn_routine(void (*routine)(const char *warn, va_list params))
+{
+       warn_routine = routine;
+}
+
 
 void usage(const char *err)
 {
@@ -75,3 +85,12 @@ int error(const char *err, ...)
        va_end(params);
        return -1;
 }
+
+void warn(const char *warn, ...)
+{
+       va_list params;
+
+       va_start(params, warn);
+       warn_routine(warn, params);
+       va_end(params);
+}
diff --git a/utf8.c b/utf8.c
new file mode 100644 (file)
index 0000000..7c80eec
--- /dev/null
+++ b/utf8.c
@@ -0,0 +1,341 @@
+#include "git-compat-util.h"
+#include "utf8.h"
+
+/* This code is originally from http://www.cl.cam.ac.uk/~mgk25/ucs/ */
+
+struct interval {
+  int first;
+  int last;
+};
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(wchar_t ucs, const struct interval *table, int max) {
+       int min = 0;
+       int mid;
+
+       if (ucs < table[0].first || ucs > table[max].last)
+               return 0;
+       while (max >= min) {
+               mid = (min + max) / 2;
+               if (ucs > table[mid].last)
+                       min = mid + 1;
+               else if (ucs < table[mid].first)
+                       max = mid - 1;
+               else
+                       return 1;
+       }
+
+       return 0;
+}
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ *    - The null character (U+0000) has a column width of 0.
+ *
+ *    - Other C0/C1 control characters and DEL will lead to a return
+ *      value of -1.
+ *
+ *    - Non-spacing and enclosing combining characters (general
+ *      category code Mn or Me in the Unicode database) have a
+ *      column width of 0.
+ *
+ *    - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ *    - Other format characters (general category code Cf in the Unicode
+ *      database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ *    - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ *      have a column width of 0.
+ *
+ *    - Spacing characters in the East Asian Wide (W) or East Asian
+ *      Full-width (F) category as defined in Unicode Technical
+ *      Report #11 have a column width of 2.
+ *
+ *    - All remaining characters (including all printable
+ *      ISO 8859-1 and WGL4 characters, Unicode control characters,
+ *      etc.) have a column width of 1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+static int wcwidth(wchar_t ch)
+{
+       /*
+        * Sorted list of non-overlapping intervals of non-spacing characters,
+        * generated by
+        *   "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c".
+        */
+       static const struct interval combining[] = {
+               { 0x0300, 0x0357 }, { 0x035D, 0x036F }, { 0x0483, 0x0486 },
+               { 0x0488, 0x0489 }, { 0x0591, 0x05A1 }, { 0x05A3, 0x05B9 },
+               { 0x05BB, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+               { 0x05C4, 0x05C4 }, { 0x0600, 0x0603 }, { 0x0610, 0x0615 },
+               { 0x064B, 0x0658 }, { 0x0670, 0x0670 }, { 0x06D6, 0x06E4 },
+               { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, { 0x070F, 0x070F },
+               { 0x0711, 0x0711 }, { 0x0730, 0x074A }, { 0x07A6, 0x07B0 },
+               { 0x0901, 0x0902 }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
+               { 0x094D, 0x094D }, { 0x0951, 0x0954 }, { 0x0962, 0x0963 },
+               { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
+               { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 },
+               { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
+               { 0x0A4B, 0x0A4D }, { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 },
+               { 0x0ABC, 0x0ABC }, { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 },
+               { 0x0ACD, 0x0ACD }, { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 },
+               { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 },
+               { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 },
+               { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 },
+               { 0x0C46, 0x0C48 }, { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 },
+               { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 },
+               { 0x0CCC, 0x0CCD }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+               { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+               { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+               { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+               { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+               { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+               { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+               { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+               { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+               { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x1712, 0x1714 },
+               { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
+               { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
+               { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180D },
+               { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, { 0x1927, 0x1928 },
+               { 0x1932, 0x1932 }, { 0x1939, 0x193B }, { 0x200B, 0x200F },
+               { 0x202A, 0x202E }, { 0x2060, 0x2063 }, { 0x206A, 0x206F },
+               { 0x20D0, 0x20EA }, { 0x302A, 0x302F }, { 0x3099, 0x309A },
+               { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE23 },
+               { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, { 0x1D167, 0x1D169 },
+               { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B },
+               { 0x1D1AA, 0x1D1AD }, { 0xE0001, 0xE0001 },
+               { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF }
+       };
+
+       /* test for 8-bit control characters */
+       if (ch == 0)
+               return 0;
+       if (ch < 32 || (ch >= 0x7f && ch < 0xa0))
+               return -1;
+
+       /* binary search in table of non-spacing characters */
+       if (bisearch(ch, combining, sizeof(combining)
+                               / sizeof(struct interval) - 1))
+               return 0;
+
+       /*
+        * If we arrive here, ch is neither a combining nor a C0/C1
+        * control character.
+        */
+
+       return 1 +
+               (ch >= 0x1100 &&
+                    /* Hangul Jamo init. consonants */
+                (ch <= 0x115f ||
+                 ch == 0x2329 || ch == 0x232a ||
+                  /* CJK ... Yi */
+                 (ch >= 0x2e80 && ch <= 0xa4cf &&
+                  ch != 0x303f) ||
+                 /* Hangul Syllables */
+                 (ch >= 0xac00 && ch <= 0xd7a3) ||
+                 /* CJK Compatibility Ideographs */
+                 (ch >= 0xf900 && ch <= 0xfaff) ||
+                 /* CJK Compatibility Forms */
+                 (ch >= 0xfe30 && ch <= 0xfe6f) ||
+                 /* Fullwidth Forms */
+                 (ch >= 0xff00 && ch <= 0xff60) ||
+                 (ch >= 0xffe0 && ch <= 0xffe6) ||
+                 (ch >= 0x20000 && ch <= 0x2fffd) ||
+                 (ch >= 0x30000 && ch <= 0x3fffd)));
+}
+
+/*
+ * This function returns the number of columns occupied by the character
+ * pointed to by the variable start. The pointer is updated to point at
+ * the next character. If it was not valid UTF-8, the pointer is set to NULL.
+ */
+int utf8_width(const char **start)
+{
+       unsigned char *s = (unsigned char *)*start;
+       wchar_t ch;
+
+       if (*s < 0x80) {
+               /* 0xxxxxxx */
+               ch = *s;
+               *start += 1;
+       } else if ((s[0] & 0xe0) == 0xc0) {
+               /* 110XXXXx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] & 0xfe) == 0xc0)
+                       goto invalid;
+               ch = ((s[0] & 0x1f) << 6) | (s[1] & 0x3f);
+               *start += 2;
+       } else if ((s[0] & 0xf0) == 0xe0) {
+               /* 1110XXXX 10Xxxxxx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               (s[2] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] == 0xe0 && (s[1] & 0xe0) == 0x80) ||
+                               /* surrogate? */
+                               (s[0] == 0xed && (s[1] & 0xe0) == 0xa0) ||
+                               /* U+FFFE or U+FFFF? */
+                               (s[0] == 0xef && s[1] == 0xbf &&
+                                (s[2] & 0xfe) == 0xbe))
+                       goto invalid;
+               ch = ((s[0] & 0x0f) << 12) |
+                       ((s[1] & 0x3f) << 6) | (s[2] & 0x3f);
+               *start += 3;
+       } else if ((s[0] & 0xf8) == 0xf0) {
+               /* 11110XXX 10XXxxxx 10xxxxxx 10xxxxxx */
+               if ((s[1] & 0xc0) != 0x80 ||
+                               (s[2] & 0xc0) != 0x80 ||
+                               (s[3] & 0xc0) != 0x80 ||
+                               /* overlong? */
+                               (s[0] == 0xf0 && (s[1] & 0xf0) == 0x80) ||
+                               /* > U+10FFFF? */
+                               (s[0] == 0xf4 && s[1] > 0x8f) || s[0] > 0xf4)
+                       goto invalid;
+               ch = ((s[0] & 0x07) << 18) | ((s[1] & 0x3f) << 12) |
+                       ((s[2] & 0x3f) << 6) | (s[3] & 0x3f);
+               *start += 4;
+       } else {
+invalid:
+               *start = NULL;
+               return 0;
+       }
+
+       return wcwidth(ch);
+}
+
+int is_utf8(const char *text)
+{
+       while (*text) {
+               if (*text == '\n' || *text == '\t' || *text == '\r') {
+                       text++;
+                       continue;
+               }
+               utf8_width(&text);
+               if (!text)
+                       return 0;
+       }
+       return 1;
+}
+
+static void print_spaces(int count)
+{
+       static const char s[] = "                    ";
+       while (count >= sizeof(s)) {
+               fwrite(s, sizeof(s) - 1, 1, stdout);
+               count -= sizeof(s) - 1;
+       }
+       fwrite(s, count, 1, stdout);
+}
+
+/*
+ * Wrap the text, if necessary. The variable indent is the indent for the
+ * first line, indent2 is the indent for all other lines.
+ */
+void print_wrapped_text(const char *text, int indent, int indent2, int width)
+{
+       int w = indent, assume_utf8 = is_utf8(text);
+       const char *bol = text, *space = NULL;
+
+       for (;;) {
+               char c = *text;
+               if (!c || isspace(c)) {
+                       if (w < width || !space) {
+                               const char *start = bol;
+                               if (space)
+                                       start = space;
+                               else
+                                       print_spaces(indent);
+                               fwrite(start, text - start, 1, stdout);
+                               if (!c) {
+                                       putchar('\n');
+                                       return;
+                               } else if (c == '\t')
+                                       w |= 0x07;
+                               space = text;
+                               w++;
+                               text++;
+                       }
+                       else {
+                               putchar('\n');
+                               text = bol = space + 1;
+                               space = NULL;
+                               w = indent = indent2;
+                       }
+                       continue;
+               }
+               if (assume_utf8)
+                       w += utf8_width(&text);
+               else {
+                       w++;
+                       text++;
+               }
+       }
+}
+
+int is_encoding_utf8(const char *name)
+{
+       if (!name)
+               return 1;
+       if (!strcasecmp(name, "utf-8") || !strcasecmp(name, "utf8"))
+               return 1;
+       return 0;
+}
+
+/*
+ * Given a buffer and its encoding, return it re-encoded
+ * with iconv.  If the conversion fails, returns NULL.
+ */
+#ifndef NO_ICONV
+char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding)
+{
+       iconv_t conv;
+       size_t insz, outsz, outalloc;
+       char *out, *outpos, *cp;
+
+       if (!in_encoding)
+               return NULL;
+       conv = iconv_open(out_encoding, in_encoding);
+       if (conv == (iconv_t) -1)
+               return NULL;
+       insz = strlen(in);
+       outsz = insz;
+       outalloc = outsz + 1; /* for terminating NUL */
+       out = xmalloc(outalloc);
+       outpos = out;
+       cp = (char *)in;
+
+       while (1) {
+               size_t cnt = iconv(conv, &cp, &insz, &outpos, &outsz);
+
+               if (cnt == -1) {
+                       size_t sofar;
+                       if (errno != E2BIG) {
+                               free(out);
+                               iconv_close(conv);
+                               return NULL;
+                       }
+                       /* insz has remaining number of bytes.
+                        * since we started outsz the same as insz,
+                        * it is likely that insz is not enough for
+                        * converting the rest.
+                        */
+                       sofar = outpos - out;
+                       outalloc = sofar + insz * 2 + 32;
+                       out = xrealloc(out, outalloc);
+                       outpos = out + sofar;
+                       outsz = outalloc - sofar - 1;
+               }
+               else {
+                       *outpos = '\0';
+                       break;
+               }
+       }
+       iconv_close(conv);
+       return out;
+}
+#endif
diff --git a/utf8.h b/utf8.h
new file mode 100644 (file)
index 0000000..a07c5a8
--- /dev/null
+++ b/utf8.h
@@ -0,0 +1,16 @@
+#ifndef GIT_UTF8_H
+#define GIT_UTF8_H
+
+int utf8_width(const char **start);
+int is_utf8(const char *text);
+int is_encoding_utf8(const char *name);
+
+void print_wrapped_text(const char *text, int indent, int indent2, int len);
+
+#ifndef NO_ICONV
+char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding);
+#else
+#define reencode_string(a,b,c) NULL
+#endif
+
+#endif
diff --git a/var.c b/var.c
index a57a33b81ac6c9cb5ec0c833edc21bd66428d976..e585e59d315061e2e82ea8a23843c128bcce2555 100644 (file)
--- a/var.c
+++ b/var.c
@@ -4,9 +4,6 @@
  * Copyright (C) Eric Biederman, 2005
  */
 #include "cache.h"
-#include <stdio.h>
-#include <errno.h>
-#include <string.h>
 
 static const char var_usage[] = "git-var [-l | <variable>]";
 
@@ -59,7 +56,6 @@ int main(int argc, char **argv)
        }
 
        setup_git_directory();
-       setup_ident();
        val = NULL;
 
        if (strcmp(argv[1], "-l") == 0) {
index bfe4eeb6494c7076bf7f319cf11628f586142aa5..5c4bc8515ab9484131de7e065e08657315004f8c 100644 (file)
@@ -1,44 +1,71 @@
 #include "cache.h"
 
-void write_or_die(int fd, const void *buf, size_t count)
+int read_in_full(int fd, void *buf, size_t count)
 {
-       const char *p = buf;
-       ssize_t written;
+       char *p = buf;
+       ssize_t total = 0;
 
        while (count > 0) {
-               written = xwrite(fd, p, count);
-               if (written == 0)
-                       die("disk full?");
-               else if (written < 0) {
-                       if (errno == EPIPE)
-                               exit(0);
-                       die("write error (%s)", strerror(errno));
-               }
-               count -= written;
-               p += written;
+               ssize_t loaded = xread(fd, p, count);
+               if (loaded <= 0)
+                       return total ? total : loaded;
+               count -= loaded;
+               p += loaded;
+               total += loaded;
        }
+
+       return total;
 }
 
-int write_or_whine(int fd, const void *buf, size_t count, const char *msg)
+int write_in_full(int fd, const void *buf, size_t count)
 {
        const char *p = buf;
-       ssize_t written;
+       ssize_t total = 0;
 
        while (count > 0) {
-               written = xwrite(fd, p, count);
-               if (written == 0) {
-                       fprintf(stderr, "%s: disk full?\n", msg);
-                       return 0;
-               }
-               else if (written < 0) {
-                       if (errno == EPIPE)
-                               exit(0);
-                       fprintf(stderr, "%s: write error (%s)\n",
-                               msg, strerror(errno));
-                       return 0;
+               ssize_t written = xwrite(fd, p, count);
+               if (written < 0)
+                       return -1;
+               if (!written) {
+                       errno = ENOSPC;
+                       return -1;
                }
                count -= written;
                p += written;
+               total += written;
+       }
+
+       return total;
+}
+
+void write_or_die(int fd, const void *buf, size_t count)
+{
+       if (write_in_full(fd, buf, count) < 0) {
+               if (errno == EPIPE)
+                       exit(0);
+               die("write error (%s)", strerror(errno));
+       }
+}
+
+int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg)
+{
+       if (write_in_full(fd, buf, count) < 0) {
+               if (errno == EPIPE)
+                       exit(0);
+               fprintf(stderr, "%s: write error (%s)\n",
+                       msg, strerror(errno));
+               return 0;
+       }
+
+       return 1;
+}
+
+int write_or_whine(int fd, const void *buf, size_t count, const char *msg)
+{
+       if (write_in_full(fd, buf, count) < 0) {
+               fprintf(stderr, "%s: write error (%s)\n",
+                       msg, strerror(errno));
+               return 0;
        }
 
        return 1;
index de1be5bc66de634e41c7ef3dee08209631249e13..2879c3d5ecc5483dea779116381a036fd2dd0b45 100644 (file)
@@ -1,6 +1,6 @@
+#include "cache.h"
 #include "wt-status.h"
 #include "color.h"
-#include "cache.h"
 #include "object.h"
 #include "dir.h"
 #include "commit.h"
@@ -16,11 +16,19 @@ static char wt_status_colors[][COLOR_MAXLEN] = {
        "\033[31m", /* WT_STATUS_UNTRACKED: red */
 };
 
+static const char use_add_msg[] =
+"use \"git add <file>...\" to update what will be committed";
+static const char use_add_rm_msg[] =
+"use \"git add/rm <file>...\" to update what will be committed";
+static const char use_add_to_include_msg[] =
+"use \"git add <file>...\" to include in what will be committed";
+
 static int parse_status_slot(const char *var, int offset)
 {
        if (!strcasecmp(var+offset, "header"))
                return WT_STATUS_HEADER;
-       if (!strcasecmp(var+offset, "updated"))
+       if (!strcasecmp(var+offset, "updated")
+               || !strcasecmp(var+offset, "added"))
                return WT_STATUS_UPDATED;
        if (!strcasecmp(var+offset, "changed"))
                return WT_STATUS_CHANGED;
@@ -39,16 +47,22 @@ void wt_status_prepare(struct wt_status *s)
        unsigned char sha1[20];
        const char *head;
 
-       s->is_initial = get_sha1("HEAD", sha1) ? 1 : 0;
-
+       memset(s, 0, sizeof(*s));
        head = resolve_ref("HEAD", sha1, 0, NULL);
        s->branch = head ? xstrdup(head) : NULL;
-
        s->reference = "HEAD";
-       s->amend = 0;
-       s->verbose = 0;
-       s->commitable = 0;
-       s->untracked = 0;
+}
+
+static void wt_status_print_cached_header(const char *reference)
+{
+       const char *c = color(WT_STATUS_HEADER);
+       color_printf_ln(c, "# Changes to be committed:");
+       if (reference) {
+               color_printf_ln(c, "#   (use \"git reset %s <file>...\" to unstage)", reference);
+       } else {
+               color_printf_ln(c, "#   (use \"git rm --cached <file>...\" to unstage)");
+       }
+       color_printf_ln(c, "#");
 }
 
 static void wt_status_print_header(const char *main, const char *sub)
@@ -145,8 +159,7 @@ static void wt_status_print_updated_cb(struct diff_queue_struct *q,
                if (q->queue[i]->status == 'U')
                        continue;
                if (!shown_header) {
-                       wt_status_print_header("Updated but not checked in",
-                                       "will commit");
+                       wt_status_print_cached_header(s->reference);
                        s->commitable = 1;
                        shown_header = 1;
                }
@@ -160,10 +173,18 @@ static void wt_status_print_changed_cb(struct diff_queue_struct *q,
                         struct diff_options *options,
                         void *data)
 {
+       struct wt_status *s = data;
        int i;
-       if (q->nr)
-               wt_status_print_header("Changed but not updated",
-                               "use git-update-index to mark for commit");
+       if (q->nr) {
+               const char *msg = use_add_msg;
+               s->workdir_dirty = 1;
+               for (i = 0; i < q->nr; i++)
+                       if (q->queue[i]->status == DIFF_STATUS_DELETED) {
+                               msg = use_add_rm_msg;
+                               break;
+                       }
+               wt_status_print_header("Changed but not updated", msg);
+       }
        for (i = 0; i < q->nr; i++)
                wt_status_print_filepair(WT_STATUS_CHANGED, q->queue[i]);
        if (q->nr)
@@ -178,8 +199,7 @@ void wt_status_print_initial(struct wt_status *s)
        read_cache();
        if (active_nr) {
                s->commitable = 1;
-               wt_status_print_header("Updated but not checked in",
-                               "will commit");
+               wt_status_print_cached_header(NULL);
        }
        for (i = 0; i < active_nr; i++) {
                color_printf(color(WT_STATUS_HEADER), "#\t");
@@ -214,7 +234,7 @@ static void wt_status_print_changed(struct wt_status *s)
        run_diff_files(&rev, 0);
 }
 
-static void wt_status_print_untracked(const struct wt_status *s)
+static void wt_status_print_untracked(struct wt_status *s)
 {
        struct dir_struct dir;
        const char *x;
@@ -249,8 +269,9 @@ static void wt_status_print_untracked(const struct wt_status *s)
                                continue;
                }
                if (!shown_header) {
+                       s->workdir_untracked = 1;
                        wt_status_print_header("Untracked files",
-                               "use \"git add\" to add to commit");
+                                              use_add_to_include_msg);
                        shown_header = 1;
                }
                color_printf(color(WT_STATUS_HEADER), "#\t");
@@ -271,9 +292,21 @@ static void wt_status_print_verbose(struct wt_status *s)
 
 void wt_status_print(struct wt_status *s)
 {
-       if (s->branch && strcmp(s->branch, "refs/heads/master"))
+       unsigned char sha1[20];
+       s->is_initial = get_sha1(s->reference, sha1) ? 1 : 0;
+
+       if (s->branch) {
+               const char *on_what = "On branch ";
+               const char *branch_name = s->branch;
+               if (!strncmp(branch_name, "refs/heads/", 11))
+                       branch_name += 11;
+               else if (!strcmp(branch_name, "HEAD")) {
+                       branch_name = "";
+                       on_what = "Not currently on any branch.";
+               }
                color_printf_ln(color(WT_STATUS_HEADER),
-                       "# On branch %s", s->branch);
+                       "# %s%s", on_what, branch_name);
+       }
 
        if (s->is_initial) {
                color_printf_ln(color(WT_STATUS_HEADER), "#");
@@ -291,17 +324,27 @@ void wt_status_print(struct wt_status *s)
 
        if (s->verbose && !s->is_initial)
                wt_status_print_verbose(s);
-       if (!s->commitable)
-               printf("%s\n", s->amend ? "# No changes" : "nothing to commit");
+       if (!s->commitable) {
+               if (s->amend)
+                       printf("# No changes\n");
+               else if (s->workdir_dirty)
+                       printf("no changes added to commit (use \"git add\" and/or \"git commit -a\")\n");
+               else if (s->workdir_untracked)
+                       printf("nothing added to commit but untracked files present (use \"git add\" to track)\n");
+               else if (s->is_initial)
+                       printf("nothing to commit (create/copy files and use \"git add\" to track)\n");
+               else
+                       printf("nothing to commit (working directory clean)\n");
+       }
 }
 
 int git_status_config(const char *k, const char *v)
 {
-       if (!strcmp(k, "status.color")) {
+       if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) {
                wt_status_use_color = git_config_colorbool(k, v);
                return 0;
        }
-       if (!strncmp(k, "status.color.", 13)) {
+       if (!strncmp(k, "status.color.", 13) || !strncmp(k, "color.status.", 13)) {
                int slot = parse_status_slot(k, 13);
                color_parse(v, k, wt_status_colors[slot]);
        }
index 0a5a5b7ba9fc1d50c7dcab8220a4fb77ffecddb4..cfea4ae68805d74b825cae6935a4a0dd5de5134d 100644 (file)
@@ -12,10 +12,13 @@ struct wt_status {
        int is_initial;
        char *branch;
        const char *reference;
-       int commitable;
        int verbose;
        int amend;
        int untracked;
+       /* These are computed during processing of the individual sections */
+       int commitable;
+       int workdir_dirty;
+       int workdir_untracked;
 };
 
 int git_status_config(const char *var, const char *value);
index 08602f522183dc43787616f37cba9b8af4e3dade..6c1f99b149f0800a9c9ab1b45033bc1401a84766 100644 (file)
@@ -102,3 +102,22 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf)
        }
        return 0;
 }
+
+int read_mmfile(mmfile_t *ptr, const char *filename)
+{
+       struct stat st;
+       FILE *f;
+
+       if (stat(filename, &st))
+               return error("Could not stat %s", filename);
+       if ((f = fopen(filename, "rb")) == NULL)
+               return error("Could not open %s", filename);
+       ptr->ptr = xmalloc(st.st_size);
+       if (fread(ptr->ptr, st.st_size, 1, f) != 1)
+               return error("Could not read %s", filename);
+       fclose(f);
+       ptr->size = st.st_size;
+       return 0;
+}
+
+
index 1346908bea31319aabeabdfd955e2ea9aab37456..1918808081c00daf20a16a1592c0ed9be6d79be4 100644 (file)
@@ -17,5 +17,6 @@ int xdiff_outf(void *priv_, mmbuffer_t *mb, int nbuf);
 int parse_hunk_header(char *line, int len,
                      int *ob, int *on,
                      int *nb, int *nn);
+int read_mmfile(mmfile_t *ptr, const char *filename);
 
 #endif
index c9f817818a781164b63c78be2af71a5d21e35d6f..fa409d5234009bf6ddac8803e8d20ed1b23a3258 100644 (file)
@@ -49,6 +49,9 @@ extern "C" {
 #define XDL_BDOP_CPY 2
 #define XDL_BDOP_INSB 3
 
+#define XDL_MERGE_MINIMAL 0
+#define XDL_MERGE_EAGER 1
+#define XDL_MERGE_ZEALOUS 2
 
 typedef struct s_mmfile {
        char *ptr;
@@ -90,6 +93,10 @@ long xdl_mmfile_size(mmfile_t *mmf);
 int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
             xdemitconf_t const *xecfg, xdemitcb_t *ecb);
 
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
+               mmfile_t *mf2, const char *name2,
+               xpparam_t const *xpp, int level, mmbuffer_t *result);
+
 #ifdef __cplusplus
 }
 #endif /* #ifdef __cplusplus */
index d76e76a0e6d987cdec985e304ad682020fef5e1f..9aeebc473b11a3dbfa5b52930b4a8e5a35f22215 100644 (file)
@@ -45,7 +45,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
                      long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
                      xdalgoenv_t *xenv);
 static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2);
-static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
 
 
 
@@ -397,7 +396,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
 }
 
 
-static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
        long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
        char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
        xrecord_t **recs = xdf->recs;
index d3b72716b58273169904a4351f02fab7ab2fc6e9..472aeaecfab85e7276baaceb446d2095eeab01fd 100644 (file)
@@ -50,6 +50,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
                 long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv);
 int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                xdfenv_t *xe);
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
 int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr);
 void xdl_free_script(xdchange_t *xscr);
 int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
index 07995ec33e9079cbbb579947e55fe46c1521d28f..e291dc7608c4ab9b54a5d049bf3b555704e37fe8 100644 (file)
@@ -118,7 +118,7 @@ int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
                  xdemitconf_t const *xecfg) {
        long s1, s2, e1, e2, lctx;
        xdchange_t *xch, *xche;
-       char funcbuf[40];
+       char funcbuf[80];
        long funclen = 0;
 
        if (xecfg->flags & XDL_EMIT_COMMON)
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
new file mode 100644 (file)
index 0000000..b83b334
--- /dev/null
@@ -0,0 +1,426 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+typedef struct s_xdmerge {
+       struct s_xdmerge *next;
+       /*
+        * 0 = conflict,
+        * 1 = no conflict, take first,
+        * 2 = no conflict, take second.
+        */
+       int mode;
+       long i1, i2;
+       long chg1, chg2;
+} xdmerge_t;
+
+static int xdl_append_merge(xdmerge_t **merge, int mode,
+               long i1, long chg1, long i2, long chg2)
+{
+       xdmerge_t *m = *merge;
+       if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) {
+               if (mode != m->mode)
+                       m->mode = 0;
+               m->chg1 = i1 + chg1 - m->i1;
+               m->chg2 = i2 + chg2 - m->i2;
+       } else {
+               m = xdl_malloc(sizeof(xdmerge_t));
+               if (!m)
+                       return -1;
+               m->next = NULL;
+               m->mode = mode;
+               m->i1 = i1;
+               m->chg1 = chg1;
+               m->i2 = i2;
+               m->chg2 = chg2;
+               if (*merge)
+                       (*merge)->next = m;
+               *merge = m;
+       }
+       return 0;
+}
+
+static int xdl_cleanup_merge(xdmerge_t *c)
+{
+       int count = 0;
+       xdmerge_t *next_c;
+
+       /* were there conflicts? */
+       for (; c; c = next_c) {
+               if (c->mode == 0)
+                       count++;
+               next_c = c->next;
+               free(c);
+       }
+       return count;
+}
+
+static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2,
+               int line_count, long flags)
+{
+       int i;
+       xrecord_t **rec1 = xe1->xdf2.recs + i1;
+       xrecord_t **rec2 = xe2->xdf2.recs + i2;
+
+       for (i = 0; i < line_count; i++) {
+               int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size,
+                       rec2[i]->ptr, rec2[i]->size, flags);
+               if (!result)
+                       return -1;
+       }
+       return 0;
+}
+
+static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest)
+{
+       xrecord_t **recs = xe->xdf2.recs + i;
+       int size = 0;
+
+       if (count < 1)
+               return 0;
+
+       for (i = 0; i < count; size += recs[i++]->size)
+               if (dest)
+                       memcpy(dest + size, recs[i]->ptr, recs[i]->size);
+       if (add_nl) {
+               i = recs[count - 1]->size;
+               if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') {
+                       if (dest)
+                               dest[size] = '\n';
+                       size++;
+               }
+       }
+       return size;
+}
+
+static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1,
+               xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest)
+{
+       const int marker_size = 7;
+       int marker1_size = (name1 ? strlen(name1) + 1 : 0);
+       int marker2_size = (name2 ? strlen(name2) + 1 : 0);
+       int conflict_marker_size = 3 * (marker_size + 1)
+               + marker1_size + marker2_size;
+       int size, i1, j;
+
+       for (size = i1 = 0; m; m = m->next) {
+               if (m->mode == 0) {
+                       size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0,
+                                       dest ? dest + size : NULL);
+                       if (dest) {
+                               for (j = 0; j < marker_size; j++)
+                                       dest[size++] = '<';
+                               if (marker1_size) {
+                                       dest[size] = ' ';
+                                       memcpy(dest + size + 1, name1,
+                                                       marker1_size - 1);
+                                       size += marker1_size;
+                               }
+                               dest[size++] = '\n';
+                       } else
+                               size += conflict_marker_size;
+                       size += xdl_recs_copy(xe1, m->i1, m->chg1, 1,
+                                       dest ? dest + size : NULL);
+                       if (dest) {
+                               for (j = 0; j < marker_size; j++)
+                                       dest[size++] = '=';
+                               dest[size++] = '\n';
+                       }
+                       size += xdl_recs_copy(xe2, m->i2, m->chg2, 1,
+                                       dest ? dest + size : NULL);
+                       if (dest) {
+                               for (j = 0; j < marker_size; j++)
+                                       dest[size++] = '>';
+                               if (marker2_size) {
+                                       dest[size] = ' ';
+                                       memcpy(dest + size + 1, name2,
+                                                       marker2_size - 1);
+                                       size += marker2_size;
+                               }
+                               dest[size++] = '\n';
+                       }
+               } else if (m->mode == 1)
+                       size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0,
+                                       dest ? dest + size : NULL);
+               else if (m->mode == 2)
+                       size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1,
+                                       m->i1 + m->chg2 - i1, 0,
+                                       dest ? dest + size : NULL);
+               else
+                       continue;
+               i1 = m->i1 + m->chg1;
+       }
+       size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0,
+                       dest ? dest + size : NULL);
+       return size;
+}
+
+/*
+ * Sometimes, changes are not quite identical, but differ in only a few
+ * lines. Try hard to show only these few lines as conflicting.
+ */
+static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
+               xpparam_t const *xpp)
+{
+       for (; m; m = m->next) {
+               mmfile_t t1, t2;
+               xdfenv_t xe;
+               xdchange_t *xscr, *x;
+               int i1 = m->i1, i2 = m->i2;
+
+               /* let's handle just the conflicts */
+               if (m->mode)
+                       continue;
+
+               /* no sense refining a conflict when one side is empty */
+               if (m->chg1 == 0 || m->chg2 == 0)
+                       continue;
+
+               /*
+                * This probably does not work outside git, since
+                * we have a very simple mmfile structure.
+                */
+               t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr;
+               t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr
+                       + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr;
+               t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr;
+               t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr
+                       + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr;
+               if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0)
+                       return -1;
+               if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+                   xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
+                   xdl_build_script(&xe, &xscr) < 0) {
+                       xdl_free_env(&xe);
+                       return -1;
+               }
+               if (!xscr) {
+                       /* If this happens, the changes are identical. */
+                       xdl_free_env(&xe);
+                       m->mode = 4;
+                       continue;
+               }
+               x = xscr;
+               m->i1 = xscr->i1 + i1;
+               m->chg1 = xscr->chg1;
+               m->i2 = xscr->i2 + i2;
+               m->chg2 = xscr->chg2;
+               while (xscr->next) {
+                       xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t));
+                       if (!m2) {
+                               xdl_free_env(&xe);
+                               xdl_free_script(x);
+                               return -1;
+                       }
+                       xscr = xscr->next;
+                       m2->next = m->next;
+                       m->next = m2;
+                       m = m2;
+                       m->mode = 0;
+                       m->i1 = xscr->i1 + i1;
+                       m->chg1 = xscr->chg1;
+                       m->i2 = xscr->i2 + i2;
+                       m->chg2 = xscr->chg2;
+               }
+               xdl_free_env(&xe);
+               xdl_free_script(x);
+       }
+       return 0;
+}
+
+/*
+ * level == 0: mark all overlapping changes as conflict
+ * level == 1: mark overlapping changes as conflict only if not identical
+ * level == 2: analyze non-identical changes for minimal conflict set
+ *
+ * returns < 0 on error, == 0 for no conflicts, else number of conflicts
+ */
+static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
+               xdfenv_t *xe2, xdchange_t *xscr2, const char *name2,
+               int level, xpparam_t const *xpp, mmbuffer_t *result) {
+       xdmerge_t *changes, *c;
+       int i1, i2, chg1, chg2;
+
+       c = changes = NULL;
+
+       while (xscr1 && xscr2) {
+               if (!changes)
+                       changes = c;
+               if (xscr1->i1 + xscr1->chg1 < xscr2->i1) {
+                       i1 = xscr1->i2;
+                       i2 = xscr2->i2 - xscr2->i1 + xscr1->i1;
+                       chg1 = xscr1->chg2;
+                       chg2 = xscr1->chg1;
+                       if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) {
+                               xdl_cleanup_merge(changes);
+                               return -1;
+                       }
+                       xscr1 = xscr1->next;
+                       continue;
+               }
+               if (xscr2->i1 + xscr2->chg1 < xscr1->i1) {
+                       i1 = xscr1->i2 - xscr1->i1 + xscr2->i1;
+                       i2 = xscr2->i2;
+                       chg1 = xscr2->chg1;
+                       chg2 = xscr2->chg2;
+                       if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) {
+                               xdl_cleanup_merge(changes);
+                               return -1;
+                       }
+                       xscr2 = xscr2->next;
+                       continue;
+               }
+               if (level < 1 || xscr1->i1 != xscr2->i1 ||
+                               xscr1->chg1 != xscr2->chg1 ||
+                               xscr1->chg2 != xscr2->chg2 ||
+                               xdl_merge_cmp_lines(xe1, xscr1->i2,
+                                       xe2, xscr2->i2,
+                                       xscr1->chg2, xpp->flags)) {
+                       /* conflict */
+                       int off = xscr1->i1 - xscr2->i1;
+                       int ffo = off + xscr1->chg1 - xscr2->chg1;
+
+                       i1 = xscr1->i2;
+                       i2 = xscr2->i2;
+                       if (off > 0)
+                               i1 -= off;
+                       else
+                               i2 += off;
+                       chg1 = xscr1->i2 + xscr1->chg2 - i1;
+                       chg2 = xscr2->i2 + xscr2->chg2 - i2;
+                       if (ffo > 0)
+                               chg2 += ffo;
+                       else
+                               chg1 -= ffo;
+                       if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) {
+                               xdl_cleanup_merge(changes);
+                               return -1;
+                       }
+               }
+
+               i1 = xscr1->i1 + xscr1->chg1;
+               i2 = xscr2->i1 + xscr2->chg1;
+
+               if (i1 >= i2)
+                       xscr2 = xscr2->next;
+               if (i2 >= i1)
+                       xscr1 = xscr1->next;
+       }
+       while (xscr1) {
+               if (!changes)
+                       changes = c;
+               i1 = xscr1->i2;
+               i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec;
+               chg1 = xscr1->chg2;
+               chg2 = xscr1->chg1;
+               if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) {
+                       xdl_cleanup_merge(changes);
+                       return -1;
+               }
+               xscr1 = xscr1->next;
+       }
+       while (xscr2) {
+               if (!changes)
+                       changes = c;
+               i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec;
+               i2 = xscr2->i2;
+               chg1 = xscr2->chg1;
+               chg2 = xscr2->chg2;
+               if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) {
+                       xdl_cleanup_merge(changes);
+                       return -1;
+               }
+               xscr2 = xscr2->next;
+       }
+       if (!changes)
+               changes = c;
+       /* refine conflicts */
+       if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) {
+               xdl_cleanup_merge(changes);
+               return -1;
+       }
+       /* output */
+       if (result) {
+               int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2,
+                       changes, NULL);
+               result->ptr = xdl_malloc(size);
+               if (!result->ptr) {
+                       xdl_cleanup_merge(changes);
+                       return -1;
+               }
+               result->size = size;
+               xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes,
+                               result->ptr);
+       }
+       return xdl_cleanup_merge(changes);
+}
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1,
+               mmfile_t *mf2, const char *name2,
+               xpparam_t const *xpp, int level, mmbuffer_t *result) {
+       xdchange_t *xscr1, *xscr2;
+       xdfenv_t xe1, xe2;
+       int status;
+
+       result->ptr = NULL;
+       result->size = 0;
+
+       if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 ||
+                       xdl_do_diff(orig, mf2, xpp, &xe2) < 0) {
+               return -1;
+       }
+       if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 ||
+           xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 ||
+           xdl_build_script(&xe1, &xscr1) < 0) {
+               xdl_free_env(&xe1);
+               return -1;
+       }
+       if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 ||
+           xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 ||
+           xdl_build_script(&xe2, &xscr2) < 0) {
+               xdl_free_env(&xe2);
+               return -1;
+       }
+       status = 0;
+       if (xscr1 || xscr2) {
+               if (!xscr1) {
+                       result->ptr = xdl_malloc(mf2->size);
+                       memcpy(result->ptr, mf2->ptr, mf2->size);
+                       result->size = mf2->size;
+               } else if (!xscr2) {
+                       result->ptr = xdl_malloc(mf1->size);
+                       memcpy(result->ptr, mf1->ptr, mf1->size);
+                       result->size = mf1->size;
+               } else {
+                       status = xdl_do_merge(&xe1, xscr1, name1,
+                                             &xe2, xscr2, name2,
+                                             level, xpp, result);
+               }
+               xdl_free_script(xscr1);
+               xdl_free_script(xscr2);
+       }
+       xdl_free_env(&xe1);
+       xdl_free_env(&xe2);
+
+       return status;
+}
index 9e4bb47ee97a0fca5120c41365a80531ddb849e4..1b899f32c4b7c3f00c5310a161d0d9afd459f002 100644 (file)
@@ -230,7 +230,8 @@ unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
                        while (ptr + 1 < top && isspace(ptr[1])
                                        && ptr[1] != '\n')
                                ptr++;
-                       if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+                       if (flags & XDF_IGNORE_WHITESPACE_CHANGE
+                                       && ptr[1] != '\n') {
                                ha += (ha << 5);
                                ha ^= (unsigned long) ' ';
                        }